前面的系列文章中,我们分析源码对Eureka有了深入了解,其中获取服务列表、注册、续约等操作都涉及到client和server端的交互,今天我们通过Wireshark抓包来分析这些交互的内容,以印证之前的代码分析,加深印象,原文地址:https://blog.csdn.net/boling_cavalry/article/details/82918227

源码分析系列文章

以下是整理的Eureka源码分析系列,用于参考:

《Spring Cloud源码分析之Eureka篇第一章:准备工作》
《Spring Cloud源码分析之Eureka篇第二章:注册中心启动类上的注解EnableEurekaServer》
《Spring Cloud源码分析之Eureka篇第三章:EnableDiscoveryClient与EnableEurekaClient的区别(Edgware版本)》
《Spring Cloud源码分析之Eureka篇第四章:服务注册是如何发起的》
《Spring Cloud源码分析之Eureka篇第五章:更新服务列表》
《Spring Cloud源码分析之Eureka篇第六章:服务注册》
《Spring Cloud源码分析之Eureka篇第七章:续约》
《Spring Cloud源码分析之Eureka篇第八章:服务注册名称的来历》

实战环境简介

为了组建Spring cloud环境,本次实战用了三台电脑,部署情况如下图:
Wireshark抓包分析Eureka注册发现协议-LMLPHP

三台电脑的详细情况如下表所示:

应用源码下载

springclouddeepeureka、springclouddeepprovider、springclouddeepconsumer这三个应用,在文章《Spring Cloud源码分析之Eureka篇第一章:准备工作》中已有详细介绍,本文不多说了,您可以参考文章,也可以在github下载这三个应用的源码,地址和链接信息如下表所示:

这个git项目中有多个文件夹,本章源码分别在springclouddeepeureka、springclouddeepprovider、springclouddeepconsumer这三个文件夹下,如下图红框所示:
Wireshark抓包分析Eureka注册发现协议-LMLPHP

实战步骤

  1. 修改应用的配置文件;
  2. 编译构建三个应用,得到三个jar包;
  3. 在三台电脑上分别部署应用;
  4. 启动springclouddeepeureka;
  5. 在springclouddeepprovider所在电脑上启动Wireshark,并配置好过滤规则;
  6. 启动springclouddeepprovider;
  7. Wireshark抓包,分析注册、续约、下载服务列表等请求响应;
  8. 启动springclouddeepconsumer;
  9. Wireshark抓包,分析服务列表的变化;
  10. 停止应用springclouddeepconsumer;
  11. Wireshark抓包,分析服务列表的变化;

关于机器的小建议

三台电脑的成本不低,除了用老旧电脑东拼西凑,也可以考虑租用云服务器,我这里的Linux电脑是树莓派,装64位Linux,再装上JDK8就能部署和运行springboot应用了,树莓派安装64位系统请参考《树莓派3B安装64位操作系统(树莓派无需连接显示器键盘鼠标) 》

接下来开始实战吧;

修改应用的配置文件

  1. 根据前面提供的Github地址下载三个应用源码;
  2. springclouddeepprovider和springclouddeepconsumer这两个应用的application.yml文件都要修改,配置项eureka.client.serviceUrl.defaultZone的值都改成http://192.168.31.147:8081/eureka/,如下图红框所示:
    Wireshark抓包分析Eureka注册发现协议-LMLPHP

编译构建三个应用

在每个应用的目录下分别执行命令mvn clean package -Dmaven.test.skip=true docker:build,即可在target目录得到应用对应的jar文件,例如springclouddeepeureka工程的jar文件是springclouddeepeureka-0.0.1-SNAPSHOT.jar,如下图所示:
Wireshark抓包分析Eureka注册发现协议-LMLPHP

在三台电脑上分别部署应用

将三个jar文件分别部署到对应的三台电脑上,并保证这三台电脑上都已装有JDK1.8版本;

启动springclouddeepeureka

登录springclouddeepeureka所在的电脑,在springclouddeepeureka-0.0.1-SNAPSHOT.jar文件所在目录下执行命令java -Xms256m -Xmx256m -jar springclouddeepeureka-0.0.1-SNAPSHOT.jar,这样Eureka server就启动了;

启动和配置Wireshark

  1. 应用springclouddeepprovider所在的电脑是Windows10操作系统,上面安装了Wireshark2.6.3,现在请运行它;

  2. 首先在网卡列表中确定自己用的是哪一个,我这里用的是无线网卡,因此双击下图红框位置:
    Wireshark抓包分析Eureka注册发现协议-LMLPHP

  3. 配置过滤规则,本次抓的包是HTTP的,并且只关注和Eureka server之间的请求响应,因此过滤规则是ip.addr == 192.168.31.147 && http,将此规则填入下图红框中的位置,再按下回车键:
    Wireshark抓包分析Eureka注册发现协议-LMLPHP

如上图,之前的数据都没有了,内容窗口空空如也,这是因为应用springclouddeepprovider还未启动,和Eureka server之间并没有什么请求响应;

启动springclouddeepprovider

执行以下命令启动应用springclouddeepprovider

java -Xms256m -Xmx256m -jar springclouddeepprovider-0.0.1-SNAPSHOT.jar

启动后,在Eureka server的管理页面http://192.168.31.147:8081/,可见springclouddeepprovider的注册信息,如下图红框所示:
Wireshark抓包分析Eureka注册发现协议-LMLPHP

Wireshark抓包,分析注册、续约、下载服务列表等请求响应

此时再去看Wireshark的内容窗口,发现已有很多数据包,如下图:
Wireshark抓包分析Eureka注册发现协议-LMLPHP

接下来依次分析获取服务列表、注册、续约等请求响应;

获取服务列表的请求响应

  1. Eureka client启动后,最先向Eureka server发起的并不是注册请求,而是获取服务列表,先从代码层面分析,下图是DiscoveryClient类的构造方法:
    Wireshark抓包分析Eureka注册发现协议-LMLPHP
    上图中,红框中的方法是获取服务列表操作,比蓝框中的注册服务代码先执行,接下来看抓包的结果是否与代码一致;

  2. 下图就是Wireshark抓到的springclouddeepeureka和springclouddeepprovider之间的请求和响应:
    Wireshark抓包分析Eureka注册发现协议-LMLPHP

从上图我们能看出以下几个信息:
第一:461号数据包就是获取服务列表的请求,472号是注册到Eureka server的请求,目前看来的确是获取服务列表在前,注册在后;
第二:467号数据包是Eureka server返回的服务列表,此时由于还没有应用注册,因此获取服务列表返回的数据中,服务列表为空、一致性哈希码也为空(更多关于一致性哈希码的信息请参考《Spring Cloud源码分析之Eureka篇第五章:更新服务列表》);

  1. 《Spring Cloud源码分析之Eureka篇第五章:更新服务列表》一文中已看过更新服务列表的源码,分为全量更新和增量更新两部分,调用的都是AbstractJerseyEurekaHttpClient类的getApplicationsInternal方法,只是urlPath参数不同而已:
private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
        ClientResponse response = null;
        String regionsParamValue = null;
        try {
            WebResource webResource = jerseyClient.resource(serviceUrl).path(urlPath);
            if (regions != null && regions.length > 0) {
                regionsParamValue = StringUtil.join(regions);
                webResource = webResource.queryParam("regions", regionsParamValue);
            }
            Builder requestBuilder = webResource.getRequestBuilder();
            addExtraHeaders(requestBuilder);
            response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);

            Applications applications = null;
            if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
                applications = response.getEntity(Applications.class);
            }
            return anEurekaHttpResponse(response.getStatus(), Applications.class)
                    .headers(headersOf(response))
                    .entity(applications)
                    .build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP GET {}/{}?{}; statusCode={}",
                        serviceUrl, urlPath,
                        regionsParamValue == null ? "" : "regions=" + regionsParamValue,
                        response == null ? "N/A" : response.getStatus()
                );
            }
            if (response != null) {
                response.close();
            }
        }
    }
  1. springclouddeepprovider注册成功后,增量更新的服务列表中就能取得自身的信息了,如下图:
    Wireshark抓包分析Eureka注册发现协议-LMLPHP

上图中的1036就是增量更新的请求,红框中是path,1039是Eureka server的响应,有两处需要注意:
第一:蓝框中的actionType为ADDED,这样springclouddeepprovider就会将当前记录添加到本地缓存中,对应的代码在DiscoveryClient类的updateDelta方法中,如下图,红框中就是对不同actionType的处理逻辑:
Wireshark抓包分析Eureka注册发现协议-LMLPHP

第二:绿框中是Eureka server计算出的一致性哈希码,Eureka client更新了服务列表的本地缓存后,也会计算出一致性哈希码,然后与Eureka server返回的进行对比,正常情况下应该是一致的,如果Eureka client的增量更新出了问题,就会导致双方的一致性哈希码不同,这时Eureka client就会发起一次全量更新,对应的代码在DiscoveryClient类的getAndUpdateDelta方法中,如下图所示,红框中就是比较逻辑,而蓝框中的注释是很重要的提醒–reconcileAndLogDifference方法中有远程请求:
Wireshark抓包分析Eureka注册发现协议-LMLPHP

服务注册的请求响应

  1. 《Spring Cloud源码分析之Eureka篇第六章:服务注册》一文中已看过服务注册的源码,是AbstractJerseyEurekaHttpClient类的register方法,源码如下,可见是个POST请求,请求参数是个InstanceInfo类型的对象:
@Override
    public EurekaHttpResponse<Void> register(InstanceInfo info) {
        String urlPath = "apps/" + info.getAppName();
        ClientResponse response = null;
        try {
            Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
            addExtraHeaders(resourceBuilder);
            response = resourceBuilder
                    .header("Accept-Encoding", "gzip")
                    .type(MediaType.APPLICATION_JSON_TYPE)
                    .accept(MediaType.APPLICATION_JSON)
                    .post(ClientResponse.class, info);//info就是POST到服务端的数据
            return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                        response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }
  1. 以上请求在Wireshark上可以观察到,如下图:
    Wireshark抓包分析Eureka注册发现协议-LMLPHP

  2. 对于注册请求,Eureka server的响应如下所示:
    Wireshark抓包分析Eureka注册发现协议-LMLPHP

上图红框可见Status Code为204,在w3网站对204的解释如下,强调了无body返回:
Wireshark抓包分析Eureka注册发现协议-LMLPHP

续约的请求响应

  1. 《Spring Cloud源码分析之Eureka篇第七章:续约》一文中已看过续约的源码,是AbstractJerseyEurekaHttpClient类的sendHeartBeat方法,源码如下,可见是个PUT请求:
@Override
    public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
        String urlPath = "apps/" + appName + '/' + id;
        ClientResponse response = null;
        try {
            WebResource webResource = jerseyClient.resource(serviceUrl)
                    .path(urlPath)
                    .queryParam("status", info.getStatus().toString())
                    .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
            if (overriddenStatus != null) {
                webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
            }
            Builder requestBuilder = webResource.getRequestBuilder();
            addExtraHeaders(requestBuilder);
            response = requestBuilder.put(ClientResponse.class);
            EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
            if (response.hasEntity()) {
                eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
            }
            return eurekaResponseBuilder.build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }
  1. 以上请求在Wireshark上可以观察到,如下图,注意绿框中的内容:
    Wireshark抓包分析Eureka注册发现协议-LMLPHP

  2. Eureka server的响应如下图,返回码200,无内容:
    Wireshark抓包分析Eureka注册发现协议-LMLPHP

启动springclouddeepconsumer

登录springclouddeepconsumer应用所在机器,在springclouddeepconsumer-0.0.1-SNAPSHOT.jar文件所在目录执行如下命令,启动应用:

java -Xms256m -Xmx256m -jar springclouddeepconsumer-0.0.1-SNAPSHOT.jar

Wireshark抓包,分析服务列表的变化

现在springclouddeepprovider和springclouddeepconsumer这两个应用都已启动,并且注册到Eureka server,此时再去看Wireshark抓的增量更新接口的响应包,应该会有springclouddeepconsumer这个应用的信息,如下图:
Wireshark抓包分析Eureka注册发现协议-LMLPHP

停止应用springclouddeepconsumer

在启动springclouddeepconsumer的控制台上,Ctrl+c键同时按下,结束springclouddeepconsumer应用;

Wireshark抓包,分析服务列表的变化

springclouddeepconsumer应用停止后,该变化在springclouddeepprovider的增量更新数据中应该能体现出来,去Wireshar上抓包,果然发现了对应的变更信息,如下图:
Wireshark抓包分析Eureka注册发现协议-LMLPHP

如上图所示,本次增量更新收到了一条类型为DELETED的记录,于是Eureka client就会在本地缓存中寻找此服务信息,如果找到就删除掉;

至此,本次Wireshar抓包分析实战就完成了,在学习Eureka源码时,我们可以通过这种方式来验证和分析代码,对学习起到了很好的辅助作用;

10-03 21:08