• 实战监听 Eureka client 的缓存更新


    从 Eureka server 获取服务列表

    • Spring cloud 环境中的应用,如果注册到 Eureka server,就会从 Eureka server 获取所有应用的注册信息(也叫服务列表),然后保存到本地,这个操作是周期性的,默认每三十秒一次;
    • 以下是来自官方的架构图,可以看到 Application Service 向 Eureka Server 有 Get Registry 的请求:

    实战内容

    • 本文是一篇实战的文章,实战内容如下:

    1. 启动 Eureka server;
    2. 开发一个应用 springcloudcustomizelistener,启动后会注册到 Eureka server;
    3. 此时该应用身份为 Eureka client,会周期性的从 Eureka server 获取服务列表(已有逻辑);
    4. 每次成功获取的服务列表成功都会存入本地缓存(已有逻辑);
    5. 存入缓存后,会在 spring 容器内发送广播(已有逻辑);
    6. 本次实战的重点就是自定义一个监听器来接收上述广播,收到广播后把详情用日志打印出来;
    7. 再启动另一个应用 springclouddeepprovider,也会注册到 Eureka server;
    8. 再去观察 springcloudcustomizelistener 的广播监听日志,会发现 springclouddeepprovider 的注册信息;

    Eureka client 缓存服务列表的源码简介

    • 实战前,先对 Eureka client 缓存服务列表的实现源码做个简介,这样才能做出匹配的监听器;
    • 应用作为 Eureka Client 的启动时,在 com.netflix.discovery.DiscoveryClient 类的 initScheduledTasks 方法中,会启动周期性任务,每隔 30 秒从 Eureka server 获取服务列表信息,如下图,红框中的 TimedSupervisorTask 负责周期性执行,绿框中的 CacheRefreshThread 负责具体的更新逻辑:

    • 在 CacheRefreshThread 类中经过层层调用,获取服务列表并更新本地缓存的逻辑在 fetchRegistry 方法中实现,如下图,红框中的 getAndStoreFullRegistry 方法负责全量更新,绿框中的 getAndUpdateDelta 方法负责增量更新,黄框中的 onCacheRefreshed 方法就是今天的重点:发送广播,广播类型是服务列表的本地缓存已更新

    • onCacheRefreshed 方法在子类 CloudEurekaClient 中被重写,可见这里发送了一个普通的 spring 容器内广播,类型是 HeartbeatEvent,我们可以自定义监听类来接收广播,并通过泛型规定只接受 HeartbeatEvent 类型:

    1. @Override
    2. protected void onCacheRefreshed() {
    3. if (this.cacheRefreshedCount != null) { //might be called during construction and will be null
    4. long newCount = this.cacheRefreshedCount.incrementAndGet();
    5. log.trace("onCacheRefreshed called with count: " + newCount);
    6. //spring容器内广播,HeartbeatEvent实例在创建时收到两个参数:CloudEurekaClient实例和缓存刷新次数
    7. this.publisher.publishEvent(new HeartbeatEvent(this, newCount));
    8. }
    9. }

    复制代码

    实战应用设定

    • 本次实战要搭建一个小的 Spring Cloud 环境,包括以下应用:

    源码下载

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

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

    启动 springclouddeepeureka

    • 应用 springclouddeepeureka 开发完成后就立即启动,在浏览器访问地址:http://localhost:8081,可见 Eureka server 已经启动,不过还没有任何应用注册上来,如下图:

    开发 springcloudcustomizelistener

    • 接下来一起开发应用 springcloudcustomizelistener,在此应用中添加自定义的 spring 广播监听器,如果您不想敲代码,也可以从 github 上直接下载源码,地址和链接信息如下表所示:

    • 这个 git 项目中有多个文件夹,本章源码在 springcloudcustomizelistener 文件夹下,如下图红框所示:

    • 一起来开发吧:
    • 创建一个 springboot 的 web 应用,pom.xml 内容如下,注意为了在日志中展示更详细的内容,依赖了 fastjson 库:

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    4. <modelVersion>4.0.0</modelVersion>
    5. <groupId>com.bolingcavalry</groupId>
    6. <artifactId>springcloudcustomizelistener</artifactId>
    7. <version>0.0.1-SNAPSHOT</version>
    8. <packaging>jar</packaging>
    9. <name>springcloudcustomizelistener</name>
    10. <description>Demo project for Spring Boot</description>
    11. <parent>
    12. <groupId>org.springframework.boot</groupId>
    13. <artifactId>spring-boot-starter-parent</artifactId>
    14. <version>1.5.9.RELEASE</version>
    15. </parent>
    16. <properties>
    17. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    18. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    19. <java.version>1.8</java.version>
    20. </properties>
    21. <dependencies>
    22. <dependency>
    23. <groupId>org.springframework.boot</groupId>
    24. <artifactId>spring-boot-starter-web</artifactId>
    25. </dependency>
    26. <dependency>
    27. <groupId>org.springframework.cloud</groupId>
    28. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    29. </dependency>
    30. <dependency>
    31. <groupId>com.alibaba</groupId>
    32. <artifactId>fastjson</artifactId>
    33. <version>1.2.28</version>
    34. </dependency>
    35. <dependency>
    36. <groupId>org.springframework.boot</groupId>
    37. <artifactId>spring-boot-starter-test</artifactId>
    38. <scope>test</scope>
    39. </dependency>
    40. </dependencies>
    41. <dependencyManagement>
    42. <dependencies>
    43. <dependency>
    44. <groupId>org.springframework.cloud</groupId>
    45. <artifactId>spring-cloud-dependencies</artifactId>
    46. <version>Edgware.RELEASE</version>
    47. <type>pom</type>
    48. <scope>import</scope>
    49. </dependency>
    50. </dependencies>
    51. </dependencyManagement>
    52. <build>
    53. <plugins>
    54. <plugin>
    55. <groupId>org.springframework.boot</groupId>
    56. <artifactId>spring-boot-maven-plugin</artifactId>
    57. </plugin>
    58. </plugins>
    59. </build>
    60. </project>

    复制代码

    • 应用配置文件 application.yml 的内容如下:

    1. server:
    2. port: 8085
    3. spring:
    4. application:
    5. name: springcloud-customize-listener
    6. eureka:
    7. client:
    8. serviceUrl:
    9. defaultZone: http://localhost:8081/eureka/
    10. instance:
    11. prefer-ip-address: true

    复制代码

    • 创建监听器 EurekaCacheRefreshListener.java,前面已经分析过 HeartbeatEvent 实例的两个成员变量,值为缓存刷新次数和 CloudEurekaClient 实例,在收到广播时,将这两个成员变量都在日志中打印出来:

    1. @Component
    2. public class EurekaCacheRefreshListener implements ApplicationListener<HeartbeatEvent> {
    3. private static final Logger logger = LoggerFactory.getLogger(EurekaCacheRefreshListener.class);
    4. @Override
    5. public void onApplicationEvent(HeartbeatEvent event) {
    6. Object count = event.getValue();
    7. Object source = event.getSource();
    8. logger.info("start onApplicationEvent, count [{}], source :\n{}", count, JSON.toJSON(source));
    9. }
    10. }

    复制代码

    • 启动应用,等待大约 30 秒左右,EurekaCacheRefreshListener 中的日志就会在控制台输出,内容十分丰富,建议您复制这些内容去在线格式化 JSON 的网站做一下格式化再来看,下面列出部分关键信息,其他的内容已经略去:

    1. {
    2. "instanceRemoteStatus": "UP",
    3. "lastSuccessfulHeartbeatTimePeriod": 316,
    4. "allKnownRegions": [
    5. "us-east-1"
    6. ],
    7. ...
    8. 此处省略部分内容
    9. ...
    10. "applications": {
    11. "appsHashCode": "UP_1_",
    12. "registeredApplications": [
    13. {
    14. "instances": [
    15. {
    16. "hostName": "192.168.31.104",
    17. "overriddenStatus": "UNKNOWN",
    18. "metadata": {
    19. "jmx.port": "64656",
    20. "management.port": "8085"
    21. },
    22. "statusPageUrl": "http://192.168.31.104:8085/info",
    23. "secureVipAddress": "springcloud-customize-listener",
    24. "leaseInfo": {
    25. "renewalIntervalInSecs": 30,
    26. "registrationTimestamp": 1537827838033,
    27. "evictionTimestamp": 0,
    28. "renewalTimestamp": 1537827987419,
    29. "durationInSecs": 90,
    30. "serviceUpTimestamp": 1537827837461
    31. },
    32. "homePageUrl": "http://192.168.31.104:8085/",
    33. "countryId": 1,
    34. "sID": "na",
    35. "securePort": 443,
    36. "dataCenterInfo": {
    37. "name": "MyOwn"
    38. },
    39. "instanceId": "DESKTOP-82CCEBN:springcloud-customize-listener:8085",
    40. "coordinatingDiscoveryServer": false,
    41. "id": "DESKTOP-82CCEBN:springcloud-customize-listener:8085",
    42. "vIPAddress": "springcloud-customize-listener",
    43. "dirty": false,
    44. "lastUpdatedTimestamp": 1537827838033,
    45. "healthCheckUrl": "http://192.168.31.104:8085/health",
    46. "appName": "SPRINGCLOUD-CUSTOMIZE-LISTENER",
    47. "lastDirtyTimestamp": 1537827837401,
    48. "iPAddr": "192.168.31.104",
    49. "version": "unknown",
    50. "actionType": "ADDED",
    51. "port": 8085,
    52. "healthCheckUrls": [
    53. "http://192.168.31.104:8085/health"
    54. ],
    55. "status": "UP"
    56. }
    57. ]

    复制代码

    • 如上所示,当前应用的实例信息在本地已经缓存了;

    启动 springclouddeepprovider

    • 应用 springclouddeepprovider 是最后一个启动的应用,启动该应用后,再去观察 springcloudcustomizelistener 的日志,发现应用 springclouddeepprovider 的注册信息已经获取到了:

    1. {
    2. "instances": [
    3. {
    4. "hostName": "192.168.119.1",
    5. "overriddenStatus": "UNKNOWN",
    6. "metadata": {
    7. "jmx.port": "58420",
    8. "management.port": "8082"
    9. },
    10. "statusPageUrl": "http://192.168.119.1:8082/info",
    11. "secureVipAddress": "springcloud-deep-provider",
    12. "leaseInfo": {
    13. "renewalIntervalInSecs": 30,
    14. "registrationTimestamp": 1537840715486,
    15. "evictionTimestamp": 0,
    16. "renewalTimestamp": 1537840715486,
    17. "durationInSecs": 90,
    18. "serviceUpTimestamp": 1537840715486
    19. },
    20. "homePageUrl": "http://192.168.119.1:8082/",
    21. "countryId": 1,
    22. "sID": "na",
    23. "securePort": 443,
    24. "dataCenterInfo": {
    25. "name": "MyOwn"
    26. },
    27. "instanceId": "localhost:springcloud-deep-provider:8082",
    28. "coordinatingDiscoveryServer": false,
    29. "id": "localhost:springcloud-deep-provider:8082",
    30. "vIPAddress": "springcloud-deep-provider",
    31. "dirty": false,
    32. "lastUpdatedTimestamp": 1537840715486,
    33. "healthCheckUrl": "http://192.168.119.1:8082/health",
    34. "appName": "SPRINGCLOUD-DEEP-PROVIDER",
    35. "lastDirtyTimestamp": 1537840715451,
    36. "iPAddr": "192.168.119.1",
    37. "version": "unknown",
    38. "actionType": "ADDED",
    39. "port": 8082,
    40. "healthCheckUrls": [
    41. "http://192.168.119.1:8082/health"
    42. ],
    43. "status": "UP"
    44. }
    45. ],
    46. "name": "SPRINGCLOUD-DEEP-PROVIDER",
    47. "instancesAsIsFromEureka": [
    48. {
    49. "hostName": "192.168.119.1",
    50. "overriddenStatus": "UNKNOWN",
    51. "metadata": {
    52. "jmx.port": "58420",
    53. "management.port": "8082"
    54. },
    55. "statusPageUrl": "http://192.168.119.1:8082/info",
    56. "secureVipAddress": "springcloud-deep-provider",
    57. "leaseInfo": {
    58. "renewalIntervalInSecs": 30,
    59. "registrationTimestamp": 1537840715486,
    60. "evictionTimestamp": 0,
    61. "renewalTimestamp": 1537840715486,
    62. "durationInSecs": 90,
    63. "serviceUpTimestamp": 1537840715486
    64. },
    65. "homePageUrl": "http://192.168.119.1:8082/",
    66. "countryId": 1,
    67. "sID": "na",
    68. "securePort": 443,
    69. "dataCenterInfo": {
    70. "name": "MyOwn"
    71. },
    72. "instanceId": "localhost:springcloud-deep-provider:8082",
    73. "coordinatingDiscoveryServer": false,
    74. "id": "localhost:springcloud-deep-provider:8082",
    75. "vIPAddress": "springcloud-deep-provider",
    76. "dirty": false,
    77. "lastUpdatedTimestamp": 1537840715486,
    78. "healthCheckUrl": "http://192.168.119.1:8082/health",
    79. "appName": "SPRINGCLOUD-DEEP-PROVIDER",
    80. "lastDirtyTimestamp": 1537840715451,
    81. "iPAddr": "192.168.119.1",
    82. "version": "unknown",
    83. "actionType": "ADDED",
    84. "port": 8082,
    85. "healthCheckUrls": [
    86. "http://192.168.119.1:8082/health"
    87. ],
    88. "status": "UP"
    89. }
    90. ]
    91. }

    复制代码

    • 至此,本次实战就完成了,通过开发自定义的广播监听器,我们对 Eureka 的注册发现机制有了进一步了解,在您的 Spring Cloud 学习过程中,希望本文能祝您一臂之力;
  • 相关阅读:
    Seamlessly Manipulate SVG Files in Python
    Win10:禁用 Automatic Restart on System Failure
    Linux 的常用命令大全
    No141.精选前端面试题,享受每天的挑战和学习
    Linux文件特殊权限与特殊属性
    人工神经网络与神经网络,人工神经网络入门书籍
    Bash脚本,Docker项目
    前端研习录(10)——CSS相对定位、绝对定位、固定定位以及Z-index属性、opacity属性讲解及示例说明
    git 取消待推送内容
    利用nginx发布静态服务
  • 原文地址:https://blog.csdn.net/l688899886/article/details/125448189