写点什么

Wireshark 抓包分析 Eureka 注册发现协议

作者:程序员欣宸
  • 2022 年 7 月 12 日
  • 本文字数:5739 字

    阅读完需:约 19 分钟

Wireshark抓包分析Eureka注册发现协议

欢迎访问我的 GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos


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

源码分析系列文章

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


  1. 《Spring Cloud源码分析之Eureka篇第一章:准备工作》

  2. 《Spring Cloud源码分析之Eureka篇第二章:注册中心启动类上的注解EnableEurekaServer》

  3. 《Spring Cloud源码分析之Eureka篇第三章:EnableDiscoveryClient与EnableEurekaClient的区别(Edgware版本)》

  4. 《Spring Cloud源码分析之Eureka篇第四章:服务注册是如何发起的》

  5. 《Spring Cloud源码分析之Eureka篇第五章:更新服务列表》

  6. 《Spring Cloud源码分析之Eureka篇第六章:服务注册》

  7. 《Spring Cloud源码分析之Eureka篇第七章:续约》

  8. 《Spring Cloud源码分析之Eureka篇第八章:服务注册名称的来历》

实战环境简介

  • 为了组建 Spring cloud 环境,本次实战用了三台电脑,部署情况如下图:



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


应用源码下载



  • 这个 git 项目中有多个文件夹,本章源码分别在 springclouddeepeureka、springclouddeepprovider、springclouddeepconsumer 这三个文件夹下,如下图红框所示:


实战步骤

  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 位操作系统(树莓派无需连接显示器键盘鼠标) 》

  • 接下来开始实战吧;

修改应用的配置文件

  • 根据前面提供的 Github 地址下载三个应用源码;

  • springclouddeepprovider 和 springclouddeepconsumer 这两个应用的 application.yml 文件都要修改,配置项 eureka.client.serviceUrl.defaultZone 的值都改成 http://192.168.31.147:8081/eureka/,如下图红框所示:


编译构建三个应用

  • 在每个应用的目录下分别执行命令 mvn clean package -Dmaven.test.skip=true docker:build,即可在 target 目录得到应用对应的 jar 文件,例如 springclouddeepeureka 工程的 jar 文件是 springclouddeepeureka-0.0.1-SNAPSHOT.jar,如下图所示:


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

  • 将三个 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. 首先在网卡列表中确定自己用的是哪一个,我这里用的是无线网卡,因此双击下图红框位置:



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



  • 如上图,之前的数据都没有了,内容窗口空空如也,这是因为应用 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 抓包,分析注册、续约、下载服务列表等请求响应

  • 此时再去看 Wireshark 的内容窗口,发现已有很多数据包,如下图:



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

获取服务列表的请求响应

  • Eureka client 启动后,最先向 Eureka server 发起的并不是注册请求,而是获取服务列表,先从代码层面分析,下图是 DiscoveryClient 类的构造方法:



  • 上图中,红框中的方法是获取服务列表操作,比蓝框中的注册服务代码先执行,接下来看抓包的结果是否与代码一致;

  • 下图就是 Wireshark 抓到的 springclouddeepeureka 和 springclouddeepprovider 之间的请求和响应:



  • 从上图我们能看出以下几个信息:

  • 第一:461 号数据包就是获取服务列表的请求,472 号是注册到 Eureka server 的请求,目前看来的确是获取服务列表在前,注册在后;

  • 第二:467 号数据包是 Eureka server 返回的服务列表,此时由于还没有应用注册,因此获取服务列表返回的数据中,服务列表为空、一致性哈希码也为空(更多关于一致性哈希码的信息请参考《Spring Cloud源码分析之Eureka篇第五章:更新服务列表》);

  • 《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(); } } }
复制代码


  • springclouddeepprovider 注册成功后,增量更新的服务列表中就能取得自身的信息了,如下图:



  • 上图中的 1036 就是增量更新的请求,红框中是 path,1039 是 Eureka server 的响应,有两处需要注意:

  • 第一:蓝框中的 actionType 为 ADDED,这样 springclouddeepprovider 就会将当前记录添加到本地缓存中,对应的代码在 DiscoveryClient 类的 updateDelta 方法中,如下图,红框中就是对不同 actionType 的处理逻辑:



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


服务注册的请求响应


@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();            }        }    }
复制代码


  • 以上请求在 Wireshark 上可以观察到,如下图:



  • 对于注册请求,Eureka server 的响应如下所示:



  • 上图红框可见 Status Code 为 204,在w3网站对 204 的解释如下,强调了无 body 返回:


续约的请求响应


@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();            }        }    }
复制代码


  • 以上请求在 Wireshark 上可以观察到,如下图,注意绿框中的内容:



  • Eureka server 的响应如下图,返回码 200,无内容:


启动 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 这个应用的信息,如下图:


停止应用 springclouddeepconsumer

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

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

  • springclouddeepconsumer 应用停止后,该变化在 springclouddeepprovider 的增量更新数据中应该能体现出来,去 Wireshar 上抓包,果然发现了对应的变更信息,如下图:



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

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

欢迎关注 InfoQ:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

发布于: 2022 年 07 月 12 日阅读数: 18
用户头像

搜索"程序员欣宸",一起畅游Java宇宙 2018.04.19 加入

前腾讯、前阿里员工,从事Java后台工作,对Docker和Kubernetes充满热爱,所有文章均为作者原创,个人Github:https://github.com/zq2599/blog_demos

评论

发布
暂无评论
Wireshark抓包分析Eureka注册发现协议_Java_程序员欣宸_InfoQ写作社区