1 搭建Eureka Server高可用集群

1.1 概述

  • 在上面的章节,实现了单节点的Eureka Server的服务注册和服务发现功能。Eureka Client会定时连接到Eureka Server,获取注册表中的信息并缓存到本地。微服务在消费远程API的时候总是使用本地缓存中的数据。因此一般来说,即使Eureka Server出现宕机,也不会影响到服务之间的调用。但是如果Eureka Server宕机的时候,某些微服务也出现了不可用的情况,Eureka SClient中的缓存如果不被刷新,就可能影响到微服务的调用,甚至影响到整个应用系统的高可用。因此,在生产环境中,通常会部署一个高可用的Eureka Server集群。
  • Eureka Server可用通过运行多个实例并相互注册的方式实现高可用部署,Eureka Server实例会彼此增量的同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册时Eureka Server的默认行为。

Eureka Server集群.jpg

1.2 搭建Eureka Server高可用集群

1.2.1 修改本机的host文件

  • 由于个人计算机中进行测试很难模拟多主机的情况,Eureka配置server集群的时候需要修改host文件。在win10中host文件的路径是‪C:\Windows\System32\drivers\etc\hosts。
  1. 127.0.0.1 eureka7001.com
  2. 127.0.0.1 eureka7002.com
  3. 127.0.0.1 eureka7003.com

1.2.2 修改原先的Eureka Server

  • application.yml:
  1. server:
  2. # 单机版的端口
  3. # port: 9000
  4. port: 7001 #端口
  5. #配置Eureka Server
  6. eureka:
  7. instance:
  8. # 主机地址名称
  9. # hostname: localhost # 单机版的主机地址名称
  10. hostname: eureka7001.com
  11. client:
  12. register-with-eureka: false # 是否将自己注册到注册中心
  13. fetch-registry: false # 是否从Eureka中获取服务列表
  14. service-url: # 配置暴露给Eureka Client的请求地址
  15. # 单机版
  16. # defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  17. defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  • 将原先的Eureka的模块名修改为eureka_service7001,其pom.xml如下:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring_cloud_demo</artifactId>
  7. <groupId>org.sunxiaping</groupId>
  8. <version>1.0</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <!-- 此处修改 -->
  12. <artifactId>eureka_service7001</artifactId>
  13. <dependencies>
  14. <!-- 导入Eureka Server对应的坐标 -->
  15. <dependency>
  16. <groupId>org.springframework.cloud</groupId>
  17. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  18. </dependency>
  19. </dependencies>
  20. </project>
  • 修改启动类:
  1. package com.sunxiaping.eureka;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
  5. /**
  6. * @author 许威威
  7. * @version 1.0
  8. */
  9. @SpringBootApplication
  10. @EnableEurekaServer //开启Eureka Server
  11. //将启动类名称由EurekaApplication修改为Eureka7001Application
  12. public class Eureka7001Application {
  13. public static void main(String[] args) {
  14. SpringApplication.run(Eureka7001Application.class, args);
  15. }
  16. }
  • 修改总工程的pom.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <packaging>pom</packaging>
  7. <modules>
  8. <module>product_service</module>
  9. <module>spring_cloud_common</module>
  10. <module>order_service</module>
  11. <!-- 此处修改 -->
  12. <module>eureka_service7001</module>
  13. </modules>
  14. <parent>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-parent</artifactId>
  17. <version>2.1.6.RELEASE</version>
  18. </parent>
  19. <groupId>org.sunxiaping</groupId>
  20. <artifactId>spring_cloud_demo</artifactId>
  21. <version>1.0</version>
  22. <properties>
  23. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  24. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  25. <java.version>1.8</java.version>
  26. </properties>
  27. <dependencies>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-web</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-test</artifactId>
  35. <scope>test</scope>
  36. </dependency>
  37. <dependency>
  38. <groupId>org.projectlombok</groupId>
  39. <artifactId>lombok</artifactId>
  40. <version>1.18.4</version>
  41. <scope>provided</scope>
  42. </dependency>
  43. </dependencies>
  44. <dependencyManagement>
  45. <dependencies>
  46. <dependency>
  47. <groupId>org.springframework.cloud</groupId>
  48. <artifactId>spring-cloud-dependencies</artifactId>
  49. <version>Greenwich.RELEASE</version>
  50. <type>pom</type>
  51. <scope>import</scope>
  52. </dependency>
  53. </dependencies>
  54. </dependencyManagement>
  55. <repositories>
  56. <repository>
  57. <id>spring-snapshots</id>
  58. <name>Spring Snapshots</name>
  59. <url>http://repo.spring.io/libs-snapshot-local</url>
  60. <snapshots>
  61. <enabled>true</enabled>
  62. </snapshots>
  63. </repository>
  64. <repository>
  65. <id>spring-milestones</id>
  66. <name>Spring Milestones</name>
  67. <url>http://repo.spring.io/libs-milestone-local</url>
  68. <snapshots>
  69. <enabled>false</enabled>
  70. </snapshots>
  71. </repository>
  72. <repository>
  73. <id>spring-releases</id>
  74. <name>Spring Releases</name>
  75. <url>http://repo.spring.io/libs-release-local</url>
  76. <snapshots>
  77. <enabled>false</enabled>
  78. </snapshots>
  79. </repository>
  80. </repositories>
  81. <pluginRepositories>
  82. <pluginRepository>
  83. <id>spring-snapshots</id>
  84. <name>Spring Snapshots</name>
  85. <url>http://repo.spring.io/libs-snapshot-local</url>
  86. <snapshots>
  87. <enabled>true</enabled>
  88. </snapshots>
  89. </pluginRepository>
  90. <pluginRepository>
  91. <id>spring-milestones</id>
  92. <name>Spring Milestones</name>
  93. <url>http://repo.spring.io/libs-milestone-local</url>
  94. <snapshots>
  95. <enabled>false</enabled>
  96. </snapshots>
  97. </pluginRepository>
  98. </pluginRepositories>
  99. <build>
  100. <finalName>spring_cloud_demo</finalName>
  101. <resources>
  102. <resource>
  103. <directory>src/main/resources</directory>
  104. <filtering>true</filtering>
  105. </resource>
  106. </resources>
  107. <plugins>
  108. <plugin>
  109. <groupId>org.apache.maven.plugins</groupId>
  110. <artifactId>maven-resources-plugin</artifactId>
  111. <configuration>
  112. <delimiters>
  113. <delimit>$</delimit>
  114. </delimiters>
  115. </configuration>
  116. </plugin>
  117. </plugins>
  118. </build>
  119. </project>

1.2.3 搭建eureka_service7002模块

  • pom.xml(复制eureka_service7001模块的pom.xml):
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring_cloud_demo</artifactId>
  7. <groupId>org.sunxiaping</groupId>
  8. <version>1.0</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>eureka_service7002</artifactId>
  12. <dependencies>
  13. <!-- 导入Eureka Server对应的坐标 -->
  14. <dependency>
  15. <groupId>org.springframework.cloud</groupId>
  16. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  17. </dependency>
  18. </dependencies>
  19. </project>
  • application.yml:
  1. server:
  2. # 单机版的端口
  3. # port: 9000
  4. port: 7002 #端口
  5. #配置Eureka Server
  6. eureka:
  7. instance:
  8. # 主机地址名称
  9. # hostname: localhost # 单机版的主机地址名称
  10. hostname: eureka7002.com
  11. client:
  12. register-with-eureka: false # 是否将自己注册到注册中心
  13. fetch-registry: false # 是否从Eureka中获取服务列表
  14. service-url: # 配置暴露给Eureka Client的请求地址
  15. # 单机版
  16. # defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  17. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
  • 总工程的pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <packaging>pom</packaging>
  7. <modules>
  8. <module>product_service</module>
  9. <module>spring_cloud_common</module>
  10. <module>order_service</module>
  11. <!-- 此处修改 -->
  12. <module>eureka_service7001</module>
  13. <module>eureka_service7002</module>
  14. </modules>
  15. <parent>
  16. <groupId>org.springframework.boot</groupId>
  17. <artifactId>spring-boot-starter-parent</artifactId>
  18. <version>2.1.6.RELEASE</version>
  19. </parent>
  20. <groupId>org.sunxiaping</groupId>
  21. <artifactId>spring_cloud_demo</artifactId>
  22. <version>1.0</version>
  23. <properties>
  24. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  25. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  26. <java.version>1.8</java.version>
  27. </properties>
  28. <dependencies>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-web</artifactId>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.springframework.boot</groupId>
  35. <artifactId>spring-boot-starter-test</artifactId>
  36. <scope>test</scope>
  37. </dependency>
  38. <dependency>
  39. <groupId>org.projectlombok</groupId>
  40. <artifactId>lombok</artifactId>
  41. <version>1.18.4</version>
  42. <scope>provided</scope>
  43. </dependency>
  44. </dependencies>
  45. <dependencyManagement>
  46. <dependencies>
  47. <dependency>
  48. <groupId>org.springframework.cloud</groupId>
  49. <artifactId>spring-cloud-dependencies</artifactId>
  50. <version>Greenwich.RELEASE</version>
  51. <type>pom</type>
  52. <scope>import</scope>
  53. </dependency>
  54. </dependencies>
  55. </dependencyManagement>
  56. <repositories>
  57. <repository>
  58. <id>spring-snapshots</id>
  59. <name>Spring Snapshots</name>
  60. <url>http://repo.spring.io/libs-snapshot-local</url>
  61. <snapshots>
  62. <enabled>true</enabled>
  63. </snapshots>
  64. </repository>
  65. <repository>
  66. <id>spring-milestones</id>
  67. <name>Spring Milestones</name>
  68. <url>http://repo.spring.io/libs-milestone-local</url>
  69. <snapshots>
  70. <enabled>false</enabled>
  71. </snapshots>
  72. </repository>
  73. <repository>
  74. <id>spring-releases</id>
  75. <name>Spring Releases</name>
  76. <url>http://repo.spring.io/libs-release-local</url>
  77. <snapshots>
  78. <enabled>false</enabled>
  79. </snapshots>
  80. </repository>
  81. </repositories>
  82. <pluginRepositories>
  83. <pluginRepository>
  84. <id>spring-snapshots</id>
  85. <name>Spring Snapshots</name>
  86. <url>http://repo.spring.io/libs-snapshot-local</url>
  87. <snapshots>
  88. <enabled>true</enabled>
  89. </snapshots>
  90. </pluginRepository>
  91. <pluginRepository>
  92. <id>spring-milestones</id>
  93. <name>Spring Milestones</name>
  94. <url>http://repo.spring.io/libs-milestone-local</url>
  95. <snapshots>
  96. <enabled>false</enabled>
  97. </snapshots>
  98. </pluginRepository>
  99. </pluginRepositories>
  100. <build>
  101. <finalName>spring_cloud_demo</finalName>
  102. <resources>
  103. <resource>
  104. <directory>src/main/resources</directory>
  105. <filtering>true</filtering>
  106. </resource>
  107. </resources>
  108. <plugins>
  109. <plugin>
  110. <groupId>org.apache.maven.plugins</groupId>
  111. <artifactId>maven-resources-plugin</artifactId>
  112. <configuration>
  113. <delimiters>
  114. <delimit>$</delimit>
  115. </delimiters>
  116. </configuration>
  117. </plugin>
  118. </plugins>
  119. </build>
  120. </project>
  • 启动类:
  1. package com.sunxiaping.eureka;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
  5. /**
  6. * @author 许威威
  7. * @version 1.0
  8. */
  9. @SpringBootApplication
  10. @EnableEurekaServer //开启Eureka Server
  11. public class Eureka7002Application {
  12. public static void main(String[] args) {
  13. SpringApplication.run(Eureka7002Application.class, args);
  14. }
  15. }

1.2.4 搭建eureka_service7003模块

  • pom.xml(复制eureka_service7001模块的pom.xml):
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring_cloud_demo</artifactId>
  7. <groupId>org.sunxiaping</groupId>
  8. <version>1.0</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>eureka_service7003</artifactId>
  12. <dependencies>
  13. <!-- 导入Eureka Server对应的坐标 -->
  14. <dependency>
  15. <groupId>org.springframework.cloud</groupId>
  16. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  17. </dependency>
  18. </dependencies>
  19. </project>
  • application.yml:
  1. server:
  2. # 单机版的端口
  3. # port: 9000
  4. port: 7003 #端口
  5. #配置Eureka Server
  6. eureka:
  7. instance:
  8. # 主机地址名称
  9. # hostname: localhost # 单机版的主机地址名称
  10. hostname: eureka7003.com
  11. client:
  12. register-with-eureka: false # 是否将自己注册到注册中心
  13. fetch-registry: false # 是否从Eureka中获取服务列表
  14. service-url: # 配置暴露给Eureka Client的请求地址
  15. # 单机版
  16. # defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  17. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
  • 总工程的pom.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <packaging>pom</packaging>
  7. <modules>
  8. <module>product_service</module>
  9. <module>spring_cloud_common</module>
  10. <module>order_service</module>
  11. <!-- 此处修改 -->
  12. <module>eureka_service7001</module>
  13. <module>eureka_service7002</module>
  14. <module>eureka_service7003</module>
  15. </modules>
  16. <parent>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-parent</artifactId>
  19. <version>2.1.6.RELEASE</version>
  20. </parent>
  21. <groupId>org.sunxiaping</groupId>
  22. <artifactId>spring_cloud_demo</artifactId>
  23. <version>1.0</version>
  24. <properties>
  25. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  26. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  27. <java.version>1.8</java.version>
  28. </properties>
  29. <dependencies>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-web</artifactId>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-starter-test</artifactId>
  37. <scope>test</scope>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.projectlombok</groupId>
  41. <artifactId>lombok</artifactId>
  42. <version>1.18.4</version>
  43. <scope>provided</scope>
  44. </dependency>
  45. </dependencies>
  46. <dependencyManagement>
  47. <dependencies>
  48. <dependency>
  49. <groupId>org.springframework.cloud</groupId>
  50. <artifactId>spring-cloud-dependencies</artifactId>
  51. <version>Greenwich.RELEASE</version>
  52. <type>pom</type>
  53. <scope>import</scope>
  54. </dependency>
  55. </dependencies>
  56. </dependencyManagement>
  57. <repositories>
  58. <repository>
  59. <id>spring-snapshots</id>
  60. <name>Spring Snapshots</name>
  61. <url>http://repo.spring.io/libs-snapshot-local</url>
  62. <snapshots>
  63. <enabled>true</enabled>
  64. </snapshots>
  65. </repository>
  66. <repository>
  67. <id>spring-milestones</id>
  68. <name>Spring Milestones</name>
  69. <url>http://repo.spring.io/libs-milestone-local</url>
  70. <snapshots>
  71. <enabled>false</enabled>
  72. </snapshots>
  73. </repository>
  74. <repository>
  75. <id>spring-releases</id>
  76. <name>Spring Releases</name>
  77. <url>http://repo.spring.io/libs-release-local</url>
  78. <snapshots>
  79. <enabled>false</enabled>
  80. </snapshots>
  81. </repository>
  82. </repositories>
  83. <pluginRepositories>
  84. <pluginRepository>
  85. <id>spring-snapshots</id>
  86. <name>Spring Snapshots</name>
  87. <url>http://repo.spring.io/libs-snapshot-local</url>
  88. <snapshots>
  89. <enabled>true</enabled>
  90. </snapshots>
  91. </pluginRepository>
  92. <pluginRepository>
  93. <id>spring-milestones</id>
  94. <name>Spring Milestones</name>
  95. <url>http://repo.spring.io/libs-milestone-local</url>
  96. <snapshots>
  97. <enabled>false</enabled>
  98. </snapshots>
  99. </pluginRepository>
  100. </pluginRepositories>
  101. <build>
  102. <finalName>spring_cloud_demo</finalName>
  103. <resources>
  104. <resource>
  105. <directory>src/main/resources</directory>
  106. <filtering>true</filtering>
  107. </resource>
  108. </resources>
  109. <plugins>
  110. <plugin>
  111. <groupId>org.apache.maven.plugins</groupId>
  112. <artifactId>maven-resources-plugin</artifactId>
  113. <configuration>
  114. <delimiters>
  115. <delimit>$</delimit>
  116. </delimiters>
  117. </configuration>
  118. </plugin>
  119. </plugins>
  120. </build>
  121. </project>
  • 启动类:
  1. package com.sunxiaping.eureka;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
  5. /**
  6. * @author 许威威
  7. * @version 1.0
  8. */
  9. @SpringBootApplication
  10. @EnableEurekaServer //开启Eureka Server
  11. public class Eureka7003Application {
  12. public static void main(String[] args) {
  13. SpringApplication.run(Eureka7003Application.class, args);
  14. }
  15. }

1.2.5 修改商品微服务的yml,将注册地址改为Eureka Server集群地址

  • application.yml:
  1. server:
  2. port: 9001 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-product # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.237.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. # 配置 eureka
  17. eureka:
  18. instance:
  19. # 主机名称:服务名称修改,其实就是向eureka server中注册的实例id
  20. instance-id: service-product:9001
  21. # 显示IP信息
  22. prefer-ip-address: true
  23. client:
  24. service-url: # 此处修改为 Eureka Server的集群地址
  25. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  26. # 微服务info内容详细信息
  27. info:
  28. app.name: xxx
  29. company.name: xxx
  30. build.artifactId: $project.artifactId$
  31. build.version: $project.version$

2 Eureka中的常见问题

2.1 服务注册慢

  • 默认情况下,服务注册到Eureka Server的过程较慢。
  • 服务的注册设计到心跳,默认心跳的间隔为30s。在实例、服务器和客户端都在本地缓存中具有相同的元数据之前,服务不可用于客户端发现。可用通过配置eureka.instance.lease-renewal-interval-in-seconds(心跳续约间隔)来加快客户端连接到其他服务的过程。在生产中,最好使用默认值,因为在服务器内部有一些计算,它们对续约做出假设。

2.2 服务节点剔除问题

  • 默认情况下,由于Eureka Server剔除失效服务间隔时间为90s且存在自我保护机制。所以不能有效而迅速的剔除失效节点,这对于开发或测试会造成困扰。解决方案如下:
  • 对于Eureka Client:配置开启健康检查,并设置续约时间。
  1. # 配置 eureka
  2. eureka:
  3. instance:
  4. # 主机名称:服务名称修改,其实就是向eureka server中注册的实例id
  5. instance-id: service-product:9001
  6. # 显示IP信息
  7. prefer-ip-address: true
  8. lease-renewal-interval-in-seconds: 5 # 发送心跳续约间隔(默认30秒)
  9. lease-expiration-duration-in-seconds: 10 # Eureka Client发送心跳给Eureka Server端后,续约到期时间(默认90秒)
  10. client:
  11. healthcheck:
  12. enable: true #开启健康检查(依赖spring-boot-actuator)
  13. service-url: # 此处修改为 Eureka Server的集群地址
  14. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  • 对于Eureka Server:配置关闭自我保护,设置剔除无效节点的时间间隔。
  1. #配置Eureka Server
  2. eureka:
  3. instance:
  4. # 主机地址名称
  5. # hostname: localhost # 单机版的主机地址名称
  6. hostname: eureka7001.com
  7. client:
  8. register-with-eureka: false # 是否将自己注册到注册中心
  9. fetch-registry: false # 是否从Eureka中获取服务列表
  10. service-url: # 配置暴露给Eureka Client的请求地址
  11. # 单机版
  12. # defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  13. defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  14. server:
  15. enable-self-preservation: false # 关闭自我保护
  16. eviction-interval-timer-in-ms: 4000 #剔除时间间隔,单位:毫秒

3 Eureka的源码解析

3.1 Eureka服务注册核心源码解析

3.1.1 @EnableEurekaServer注解的作用

  • 通过@EnableEurekaServer注解激活EurekaServer。
  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Import({EurekaServerMarkerConfiguration.class})
  5. public @interface EnableEurekaServer {
  6. }
  • @EnableEurekaServer注解向Spring容器中导入了一个EurekaServerMarkerConfiguration组件,EurekaServerMarkerConfiguration组件的源码如下:
  1. @Configuration
  2. public class EurekaServerMarkerConfiguration {
  3. //向容器中导入了Marker组件,Marker组件是实例化核心配置类的前提条件
  4. @Bean
  5. public Marker eurekaServerMarkerBean() {
  6. return new Marker();
  7. }
  8. class Marker {
  9. }
  10. }

3.1.2 自动装载核心配置类

  • SpringCloud对EurekaServer的封装使得发布EurekaServer变得无比简单,根据自动状态的原则可以在spring-cloud-netflix-eureka-server-2.1.0.RELEASE.jar下找到spring.factories文件。

spring-cloud-netflix-eureka-server-2.1.0.RELEASE.jar的spring.factories文件.png

  • 根据SpringBoot自动配置原理,即SpringBoot启动的时候会加载所有jar包下的META-INF下的spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的自动配置类。所以,SpringBoot启动的时候会加载EurekaServerAutoConfiguration(EurekaServer的自动配置类),其源码如下:
  1. @Configuration
  2. @Import(EurekaServerInitializerConfiguration.class)
  3. @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
  4. @EnableConfigurationProperties({ EurekaDashboardProperties.class,InstanceRegistryProperties.class })
  5. @PropertySource("classpath:/eureka/server.properties")
  6. public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
  7. //其他略
  8. }
  • 接下来解释EurekaServerAutoConfiguration(Eureka Server自动配置类)的作用:
  • 服务注册Eureka高级(已过时) - 图3EurekaServerAutoConfiguration(Eureka Server自动配置类)生效的前提是容器中存在EurekaServerMarkerConfiguration.Marker组件(可以从@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)这段代码看出),而@EnableEurekaServer注解的作用就是向容器中添加EurekaServerMarkerConfiguration.Marker组件,所以如果不在启动类中配置@EnableEurekaServer的话,那么EurekaServerAutoConfiguration(Eureka Server自动配置类)就不会起作用。
  • 服务注册Eureka高级(已过时) - 图4@EnableConfigurationProperties({ EurekaDashboardProperties.class,InstanceRegistryProperties.class })代码中,我们知道EurekaServerAutoConfiguration(Eureka Server)向容器中导入了EurekaDashboardProperties和InstanceRegistryProperties两个组件。
    • EurekaDashboardProperties:配置EurekaServer管理台。
    • InstanceRegistryProperties:配置期望续约数量和默认的通信数量。
  • 服务注册Eureka高级(已过时) - 图5通过@Import(EurekaServerInitializerConfiguration.class)引入启动类。

3.1.3 EurekaServerInitializerConfiguration

  • EurekaServerInitializerConfiguration.java
  1. @Configuration
  2. public class EurekaServerInitializerConfiguration
  3. implements ServletContextAware, SmartLifecycle, Ordered {
  4. private static final Log log = LogFactory.getLog(EurekaServerInitializerConfiguration.class);
  5. @Autowired
  6. private EurekaServerConfig eurekaServerConfig;
  7. private ServletContext servletContext;
  8. @Autowired
  9. private ApplicationContext applicationContext;
  10. @Autowired
  11. private EurekaServerBootstrap eurekaServerBootstrap;
  12. private boolean running;
  13. private int order = 1;
  14. @Override
  15. public void setServletContext(ServletContext servletContext) {
  16. this.servletContext = servletContext;
  17. }
  18. @Override
  19. public void start() {
  20. new Thread(new Runnable() {
  21. @Override
  22. public void run() {
  23. try {
  24. //TODO: is this class even needed now?
  25. eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
  26. log.info("Started Eureka Server");
  27. publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
  28. EurekaServerInitializerConfiguration.this.running = true;
  29. publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
  30. }
  31. catch (Exception ex) {
  32. // Help!
  33. log.error("Could not initialize Eureka servlet context", ex);
  34. }
  35. }
  36. }).start();
  37. }
  38. private EurekaServerConfig getEurekaServerConfig() {
  39. return this.eurekaServerConfig;
  40. }
  41. private void publish(ApplicationEvent event) {
  42. this.applicationContext.publishEvent(event);
  43. }
  44. @Override
  45. public void stop() {
  46. this.running = false;
  47. eurekaServerBootstrap.contextDestroyed(this.servletContext);
  48. }
  49. @Override
  50. public boolean isRunning() {
  51. return this.running;
  52. }
  53. @Override
  54. public int getPhase() {
  55. return 0;
  56. }
  57. @Override
  58. public boolean isAutoStartup() {
  59. return true;
  60. }
  61. @Override
  62. public void stop(Runnable callback) {
  63. callback.run();
  64. }
  65. @Override
  66. public int getOrder() {
  67. return this.order;
  68. }
  69. }
  • 从源码中,可以看出EurekaServerInitializerConfiguration实现了SmartLifecycle,意味着Spring容器启动时会指定start()方法。加载所有的EurekaServer的配置。

3.1.4 EurekaServerAutoConfiguration

  • EurekaServerAutoConfiguration的部分源码如下:
  1. @Configuration
  2. @Import(EurekaServerInitializerConfiguration.class)
  3. @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
  4. @EnableConfigurationProperties({ EurekaDashboardProperties.class,
  5. InstanceRegistryProperties.class })
  6. @PropertySource("classpath:/eureka/server.properties")
  7. public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
  8. //实例化了Eureka Server的管控台Controller类EurekaController
  9. @Bean
  10. @ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
  11. public EurekaController eurekaController() {
  12. return new EurekaController(this.applicationInfoManager);
  13. }
  14. //实例化EurekaServerBootstrap类
  15. @Bean
  16. public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
  17. EurekaServerContext serverContext) {
  18. return new EurekaServerBootstrap(this.applicationInfoManager,
  19. this.eurekaClientConfig, this.eurekaServerConfig, registry,
  20. serverContext);
  21. }
  22. //实例化jersey相关配置类
  23. @Bean
  24. public FilterRegistrationBean jerseyFilterRegistration(
  25. javax.ws.rs.core.Application eurekaJerseyApp) {
  26. FilterRegistrationBean bean = new FilterRegistrationBean();
  27. bean.setFilter(new ServletContainer(eurekaJerseyApp));
  28. bean.setOrder(Ordered.LOWEST_PRECEDENCE);
  29. bean.setUrlPatterns(
  30. Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));
  31. return bean;
  32. }
  33. //jerseyApplication方法,在容器中存放了一个jerseyApplication对象,
  34. //jerseyApplication方法和Spring源码扫描@Component逻辑类似,扫描@Path和@Provider标签,然后封装成BeanDefinition,
  35. //封装到APPlication里面的Set容器。然后Filter过滤器来过滤URL进行映射到对象的Controller
  36. @Bean
  37. public javax.ws.rs.core.Application jerseyApplication(Environment environment,
  38. ResourceLoader resourceLoader) {
  39. ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
  40. false, environment);
  41. // Filter to include only classes that have a particular annotation.
  42. //
  43. provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
  44. provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
  45. // Find classes in Eureka packages (or subpackages)
  46. //
  47. Set<Class<?>> classes = new HashSet<>();
  48. for (String basePackage : EUREKA_PACKAGES) {
  49. Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
  50. for (BeanDefinition bd : beans) {
  51. Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
  52. resourceLoader.getClassLoader());
  53. classes.add(cls);
  54. }
  55. }
  56. }

3.1.5 暴露的服务端接口

  • 由于集成了Jersey,我们可以找到Eureka Server的依赖包中的eureka-core-1.9.8.jar,可以看到一些列的XxxResource。

eureka-core-1.9.8.jar中的XxxResource.png

  • 上面的这些类都是通过Jersey发布的供客户端调用的服务接口。

3.1.6 服务端接收客户端的注册

  • 在ApplicationResource的addInstance()方法中可以看到this.registry.register(info, "true".equals(isReplication));
  1. @Produces({"application/xml", "application/json"})
  2. public class ApplicationResource {
  3. @POST
  4. @Consumes({"application/json", "application/xml"})
  5. public Response addInstance(InstanceInfo info, @HeaderParam("x-netflix-discovery-replication") String isReplication) {
  6. logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
  7. if (this.isBlank(info.getId())) {
  8. return Response.status(400).entity("Missing instanceId").build();
  9. } else if (this.isBlank(info.getHostName())) {
  10. return Response.status(400).entity("Missing hostname").build();
  11. } else if (this.isBlank(info.getIPAddr())) {
  12. return Response.status(400).entity("Missing ip address").build();
  13. } else if (this.isBlank(info.getAppName())) {
  14. return Response.status(400).entity("Missing appName").build();
  15. } else if (!this.appName.equals(info.getAppName())) {
  16. return Response.status(400).entity("Mismatched appName, expecting " + this.appName + " but was " + info.getAppName()).build();
  17. } else if (info.getDataCenterInfo() == null) {
  18. return Response.status(400).entity("Missing dataCenterInfo").build();
  19. } else if (info.getDataCenterInfo().getName() == null) {
  20. return Response.status(400).entity("Missing dataCenterInfo Name").build();
  21. } else {
  22. DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
  23. if (dataCenterInfo instanceof UniqueIdentifier) {
  24. String dataCenterInfoId = ((UniqueIdentifier)dataCenterInfo).getId();
  25. if (this.isBlank(dataCenterInfoId)) {
  26. boolean experimental = "true".equalsIgnoreCase(this.serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
  27. if (experimental) {
  28. String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
  29. return Response.status(400).entity(entity).build();
  30. }
  31. if (dataCenterInfo instanceof AmazonInfo) {
  32. AmazonInfo amazonInfo = (AmazonInfo)dataCenterInfo;
  33. String effectiveId = amazonInfo.get(MetaDataKey.instanceId);
  34. if (effectiveId == null) {
  35. amazonInfo.getMetadata().put(MetaDataKey.instanceId.getName(), info.getId());
  36. }
  37. } else {
  38. logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
  39. }
  40. }
  41. }
  42. this.registry.register(info, "true".equals(isReplication));
  43. return Response.status(204).build();
  44. }
  45. }
  46. }
  • register()方法的有关源码如下:
  1. @Singleton
  2. public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
  3. @Override
  4. public void register(final InstanceInfo info, final boolean isReplication) {
  5. //默认有效时长为90秒
  6. int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
  7. if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
  8. leaseDuration = info.getLeaseInfo().getDurationInSecs();
  9. }
  10. //注册实例
  11. super.register(info, leaseDuration, isReplication);
  12. //同步到其他的Eureka Server服务
  13. replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
  14. }
  15. //其他略
  16. }
  • 继续找到父类的register方法可以看到整个注册过程:
  1. public abstract class AbstractInstanceRegistry implements InstanceRegistry {
  2. //线程安全的Map,存放所有注册的实例对象
  3. private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
  4. = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
  5. public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
  6. try {
  7. read.lock();
  8. Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
  9. REGISTER.increment(isReplication);
  10. //如果第一个实例注册会给registry put进去一个空的
  11. if (gMap == null) {
  12. final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
  13. gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
  14. if (gMap == null) {
  15. gMap = gNewMap;
  16. }
  17. }
  18. // 根据注册的实例对象id,获取已经存在的Lease
  19. Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
  20. // Retain the last dirty timestamp without overwriting it, if there is already a lease
  21. if (existingLease != null && (existingLease.getHolder() != null)) {
  22. Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
  23. Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
  24. logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
  25. // this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
  26. // InstanceInfo instead of the server local copy.
  27. if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
  28. logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
  29. " than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
  30. logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
  31. registrant = existingLease.getHolder();
  32. }
  33. } else {
  34. // The lease does not exist and hence it is a new registration
  35. synchronized (lock) {
  36. if (this.expectedNumberOfClientsSendingRenews > 0) {
  37. // Since the client wants to register it, increase the number of clients sending renews
  38. this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
  39. updateRenewsPerMinThreshold();
  40. }
  41. }
  42. logger.debug("No previous lease information found; it is new registration");
  43. }
  44. Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
  45. if (existingLease != null) {
  46. lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
  47. }
  48. //将lease存入gMap
  49. gMap.put(registrant.getId(), lease);
  50. synchronized (recentRegisteredQueue) {
  51. recentRegisteredQueue.add(new Pair<Long, String>(
  52. System.currentTimeMillis(),
  53. registrant.getAppName() + "(" + registrant.getId() + ")"));
  54. }
  55. // This is where the initial state transfer of overridden status happens
  56. if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
  57. logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
  58. + "overrides", registrant.getOverriddenStatus(), registrant.getId());
  59. if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
  60. logger.info("Not found overridden id {} and hence adding it", registrant.getId());
  61. overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
  62. }
  63. }
  64. InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
  65. if (overriddenStatusFromMap != null) {
  66. logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
  67. registrant.setOverriddenStatus(overriddenStatusFromMap);
  68. }
  69. // Set the status based on the overridden status rules
  70. InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
  71. registrant.setStatusWithoutDirty(overriddenInstanceStatus);
  72. // If the lease is registered with UP status, set lease service up timestamp
  73. if (InstanceStatus.UP.equals(registrant.getStatus())) {
  74. lease.serviceUp();
  75. }
  76. registrant.setActionType(ActionType.ADDED);
  77. recentlyChangedQueue.add(new RecentlyChangedItem(lease));
  78. registrant.setLastUpdatedTimestamp();
  79. invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
  80. logger.info("Registered instance {}/{} with status {} (replication={})",
  81. registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
  82. } finally {
  83. read.unlock();
  84. }
  85. }
  86. //其他略
  87. }

3.1.7 服务端接收客户端的续约

  • 在InstanceResource的renewLease方法中完成客户端的心跳(续约)处理,其中最关键的方法就是registry.renew(app.getName(), id, isFromReplicaNode);
  1. @Produces({"application/xml", "application/json"})
  2. public class InstanceResource {
  3. @PUT
  4. public Response renewLease(
  5. @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
  6. @QueryParam("overriddenstatus") String overriddenStatus,
  7. @QueryParam("status") String status,
  8. @QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
  9. boolean isFromReplicaNode = "true".equals(isReplication);
  10. //服务端接收客户端的续约
  11. boolean isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);
  12. // Not found in the registry, immediately ask for a register
  13. if (!isSuccess) {
  14. logger.warn("Not Found (Renew): {} - {}", app.getName(), id);
  15. return Response.status(Status.NOT_FOUND).build();
  16. }
  17. // Check if we need to sync based on dirty time stamp, the client
  18. // instance might have changed some value
  19. Response response;
  20. if (lastDirtyTimestamp != null && serverConfig.shouldSyncWhenTimestampDiffers()) {
  21. response = this.validateDirtyTimestamp(Long.valueOf(lastDirtyTimestamp), isFromReplicaNode);
  22. // Store the overridden status since the validation found out the node that replicates wins
  23. if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()
  24. && (overriddenStatus != null)
  25. && !(InstanceStatus.UNKNOWN.name().equals(overriddenStatus))
  26. && isFromReplicaNode) {
  27. registry.storeOverriddenStatusIfRequired(app.getAppName(), id, InstanceStatus.valueOf(overriddenStatus));
  28. }
  29. } else {
  30. response = Response.ok().build();
  31. }
  32. logger.debug("Found (Renew): {} - {}; reply status={}", app.getName(), id, response.getStatus());
  33. return response;
  34. }
  35. }
  • renew()方法的源码如下:
  1. @Singleton
  2. public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
  3. public boolean renew(final String appName, final String id, final boolean isReplication) {
  4. //客户端续约
  5. if (super.renew(appName, id, isReplication)) {
  6. //同步到其他的Eureka Server服务
  7. replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
  8. return true;
  9. }
  10. return false;
  11. }
  12. //其他略
  13. }
  • 继续找到父类的renew方法可以看到整个续约过程:
  1. public abstract class AbstractInstanceRegistry implements InstanceRegistry {
  2. public boolean renew(String appName, String id, boolean isReplication) {
  3. RENEW.increment(isReplication);
  4. Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
  5. Lease<InstanceInfo> leaseToRenew = null;
  6. if (gMap != null) {
  7. //从Map中根据id获取实例对象的Lease对象
  8. leaseToRenew = gMap.get(id);
  9. }
  10. if (leaseToRenew == null) {
  11. RENEW_NOT_FOUND.increment(isReplication);
  12. logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
  13. return false;
  14. } else {
  15. //获取实例对象
  16. InstanceInfo instanceInfo = leaseToRenew.getHolder();
  17. if (instanceInfo != null) {
  18. // touchASGCache(instanceInfo.getASGName());
  19. InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
  20. instanceInfo, leaseToRenew, isReplication);
  21. if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
  22. logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
  23. + "; re-register required", instanceInfo.getId());
  24. RENEW_NOT_FOUND.increment(isReplication);
  25. return false;
  26. }
  27. if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
  28. logger.info(
  29. "The instance status {} is different from overridden instance status {} for instance {}. "
  30. + "Hence setting the status to overridden status", instanceInfo.getStatus().name(),
  31. instanceInfo.getOverriddenStatus().name(),
  32. instanceInfo.getId());
  33. //设置实例状态
  34. instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);
  35. }
  36. }
  37. //设置续约次数
  38. renewsLastMin.increment();
  39. leaseToRenew.renew();
  40. return true;
  41. }
  42. }
  43. }

3.1.8 服务剔除

  • 在AbstractInstanceRegistry的postInit()方法中开启了一个每60秒调用一次EvictionTask的evict的定时器。
  1. public abstract class AbstractInstanceRegistry implements InstanceRegistry {
  2. public void evict(long additionalLeaseMs) {
  3. logger.debug("Running the evict task");
  4. if (!isLeaseExpirationEnabled()) {
  5. logger.debug("DS: lease expiration is currently disabled.");
  6. return;
  7. }
  8. // We collect first all expired items, to evict them in random order. For large eviction sets,
  9. // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
  10. // the impact should be evenly distributed across all applications.
  11. List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
  12. for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
  13. Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
  14. if (leaseMap != null) {
  15. for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
  16. Lease<InstanceInfo> lease = leaseEntry.getValue();
  17. if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
  18. expiredLeases.add(lease);
  19. }
  20. }
  21. }
  22. }
  23. // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
  24. // triggering self-preservation. Without that we would wipe out full registry.
  25. int registrySize = (int) getLocalRegistrySize();
  26. int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
  27. int evictionLimit = registrySize - registrySizeThreshold;
  28. int toEvict = Math.min(expiredLeases.size(), evictionLimit);
  29. if (toEvict > 0) {
  30. logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);
  31. Random random = new Random(System.currentTimeMillis());
  32. for (int i = 0; i < toEvict; i++) {
  33. // Pick a random item (Knuth shuffle algorithm)
  34. int next = i + random.nextInt(expiredLeases.size() - i);
  35. Collections.swap(expiredLeases, i, next);
  36. Lease<InstanceInfo> lease = expiredLeases.get(i);
  37. String appName = lease.getHolder().getAppName();
  38. String id = lease.getHolder().getId();
  39. EXPIRED.increment();
  40. logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
  41. internalCancel(appName, id, false);
  42. }
  43. }
  44. }
  45. //其他略
  46. }

3.2 Eureka服务发现核心源码解析

3.2.1 自动装载

  • 在消费者导入的坐标有spring-cloud-netflix-eureka-client-2.1.0.RELEASE.jar找到的spring.factories可以看到所有自动装载的配置类:

spring-cloud-netflix-eureka-client-2.1.0.RELEASE.jar的所有自动装载的配置类.png

3.2.2 服务注册

  • EurekaClientAutoConfiguration中注册了DiscoveryClient组件。
  1. @Configuration
  2. @EnableConfigurationProperties
  3. @ConditionalOnClass(EurekaClientConfig.class)
  4. @Import(DiscoveryClientOptionalArgsConfiguration.class)
  5. @ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
  6. @ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
  7. @AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
  8. CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
  9. @AutoConfigureAfter(name = {"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
  10. "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
  11. "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration"})
  12. public class EurekaClientAutoConfiguration {
  13. @Bean
  14. public DiscoveryClient discoveryClient(EurekaClient client, EurekaClientConfig clientConfig) {
  15. return new EurekaDiscoveryClient(client, clientConfig);
  16. }
  17. //其他leukemia
  18. }
  • DiscoveryClient中有register方法,即服务注册方法:
  1. @Singleton
  2. public class DiscoveryClient implements EurekaClient {
  3. //服务注册
  4. boolean register() throws Throwable {
  5. logger.info("DiscoveryClient_{}: registering service...", this.appPathIdentifier);
  6. EurekaHttpResponse httpResponse;
  7. try {
  8. httpResponse = this.eurekaTransport.registrationClient.register(this.instanceInfo);
  9. } catch (Exception var3) {
  10. logger.warn("DiscoveryClient_{} - registration failed {}", new Object[]{this.appPathIdentifier, var3.getMessage(), var3});
  11. throw var3;
  12. }
  13. if (logger.isInfoEnabled()) {
  14. logger.info("DiscoveryClient_{} - registration status: {}", this.appPathIdentifier, httpResponse.getStatusCode());
  15. }
  16. return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
  17. }
  18. //其他略
  19. }

3.2.3 服务下架

  • DiscoveryClient中有shutdown方法,即服务下架方法:
  1. @Singleton
  2. public class DiscoveryClient implements EurekaClient {
  3. @PreDestroy
  4. public synchronized void shutdown() {
  5. if (this.isShutdown.compareAndSet(false, true)) {
  6. logger.info("Shutting down DiscoveryClient ...");
  7. if (this.statusChangeListener != null && this.applicationInfoManager != null) {
  8. this.applicationInfoManager.unregisterStatusChangeListener(this.statusChangeListener.getId());
  9. }
  10. this.cancelScheduledTasks();
  11. if (this.applicationInfoManager != null && this.clientConfig.shouldRegisterWithEureka() && this.clientConfig.shouldUnregisterOnShutdown()) {
  12. this.applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
  13. this.unregister();
  14. }
  15. if (this.eurekaTransport != null) {
  16. this.eurekaTransport.shutdown();
  17. }
  18. this.heartbeatStalenessMonitor.shutdown();
  19. this.registryStalenessMonitor.shutdown();
  20. logger.info("Completed shut down of DiscoveryClient");
  21. }
  22. }
  23. //其他略
  24. }

3.2.4 心跳续约

  • DiscoveryClient的HeartbeatThread中定义了续约的操作:
  1. @Singleton
  2. public class DiscoveryClient implements EurekaClient {
  3. private class HeartbeatThread implements Runnable {
  4. private HeartbeatThread() {
  5. }
  6. public void run() {
  7. if (DiscoveryClient.this.renew()) {
  8. DiscoveryClient.this.lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
  9. }
  10. }
  11. }
  12. boolean renew() {
  13. try {
  14. EurekaHttpResponse<InstanceInfo> httpResponse = this.eurekaTransport.registrationClient.sendHeartBeat(this.instanceInfo.getAppName(), this.instanceInfo.getId(), this.instanceInfo, (InstanceStatus)null);
  15. logger.debug("DiscoveryClient_{} - Heartbeat status: {}", this.appPathIdentifier, httpResponse.getStatusCode());
  16. if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
  17. this.REREGISTER_COUNTER.increment();
  18. logger.info("DiscoveryClient_{} - Re-registering apps/{}", this.appPathIdentifier, this.instanceInfo.getAppName());
  19. long timestamp = this.instanceInfo.setIsDirtyWithTime();
  20. boolean success = this.register();
  21. if (success) {
  22. this.instanceInfo.unsetIsDirty(timestamp);
  23. }
  24. return success;
  25. } else {
  26. return httpResponse.getStatusCode() == Status.OK.getStatusCode();
  27. }
  28. } catch (Throwable var5) {
  29. logger.error("DiscoveryClient_{} - was unable to send heartbeat!", this.appPathIdentifier, var5);
  30. return false;
  31. }
  32. }
  33. //其他略
  34. }