• 服务注册Eureka高级


    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集群

    1.2 搭建Eureka Server高可用集群

    1.2.1 修改本机的host文件

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

    1.2.2 修改原先的Eureka Server

    • application.yml:
    server:
      # 单机版的端口
    #  port: 9000
      port: 7001 #端口
    
    #配置Eureka Server
    eureka:
      instance:
        # 主机地址名称
    #    hostname: localhost # 单机版的主机地址名称
        hostname: eureka7001.com
      client:
        register-with-eureka: false # 是否将自己注册到注册中心
        fetch-registry: false # 是否从Eureka中获取服务列表
        service-url:  # 配置暴露给Eureka Client的请求地址
           # 单机版
    #      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
           defaultZone:  http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
    
    • 将原先的Eureka的模块名修改为eureka_service7001,其pom.xml如下:
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>spring_cloud_demo</artifactId>
            <groupId>org.sunxiaping</groupId>
            <version>1.0</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    	<!-- 此处修改 -->	
        <artifactId>eureka_service7001</artifactId>
    
        <dependencies>
            <!--   导入Eureka Server对应的坐标     -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
        </dependencies>
    
    
    </project>
    
    • 修改启动类:
    package com.sunxiaping.eureka;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    /**
     * @author 许威威
     * @version 1.0
     */
    @SpringBootApplication
    @EnableEurekaServer //开启Eureka Server
    //将启动类名称由EurekaApplication修改为Eureka7001Application
    public class Eureka7001Application {
        public static void main(String[] args) {
            SpringApplication.run(Eureka7001Application.class, args);
        }
    }
    
    • 修改总工程的pom.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <packaging>pom</packaging>
        <modules>
            <module>product_service</module>
            <module>spring_cloud_common</module>
            <module>order_service</module>
            <!--    此处修改    -->
            <module>eureka_service7001</module>
        </modules>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.6.RELEASE</version>
        </parent>
    
    
        <groupId>org.sunxiaping</groupId>
        <artifactId>spring_cloud_demo</artifactId>
        <version>1.0</version>
    
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.4</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Greenwich.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <repositories>
            <repository>
                <id>spring-snapshots</id>
                <name>Spring Snapshots</name>
                <url>http://repo.spring.io/libs-snapshot-local</url>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </repository>
            <repository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>http://repo.spring.io/libs-milestone-local</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
            <repository>
                <id>spring-releases</id>
                <name>Spring Releases</name>
                <url>http://repo.spring.io/libs-release-local</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
        </repositories>
        <pluginRepositories>
            <pluginRepository>
                <id>spring-snapshots</id>
                <name>Spring Snapshots</name>
                <url>http://repo.spring.io/libs-snapshot-local</url>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </pluginRepository>
            <pluginRepository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>http://repo.spring.io/libs-milestone-local</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </pluginRepository>
        </pluginRepositories>
    
    
        <build>
            <finalName>spring_cloud_demo</finalName>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <filtering>true</filtering>
                </resource>
            </resources>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <delimiters>
                            <delimit>$</delimit>
                        </delimiters>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    1.2.3 搭建eureka_service7002模块

    • pom.xml(复制eureka_service7001模块的pom.xml):
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>spring_cloud_demo</artifactId>
            <groupId>org.sunxiaping</groupId>
            <version>1.0</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>eureka_service7002</artifactId>
        <dependencies>
            <!--   导入Eureka Server对应的坐标     -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    
    • application.yml:
    server:
      # 单机版的端口
    #  port: 9000
      port: 7002 #端口
    
    #配置Eureka Server
    eureka:
      instance:
        # 主机地址名称
    #    hostname: localhost # 单机版的主机地址名称
        hostname: eureka7002.com
      client:
        register-with-eureka: false # 是否将自己注册到注册中心
        fetch-registry: false # 是否从Eureka中获取服务列表
        service-url:  # 配置暴露给Eureka Client的请求地址
           # 单机版
    #      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
           defaultZone:  http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
    
    • 总工程的pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <packaging>pom</packaging>
        <modules>
            <module>product_service</module>
            <module>spring_cloud_common</module>
            <module>order_service</module>
            <!--    此处修改    -->
            <module>eureka_service7001</module>
            <module>eureka_service7002</module>
        </modules>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.6.RELEASE</version>
        </parent>
    
    
        <groupId>org.sunxiaping</groupId>
        <artifactId>spring_cloud_demo</artifactId>
        <version>1.0</version>
    
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.4</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Greenwich.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <repositories>
            <repository>
                <id>spring-snapshots</id>
                <name>Spring Snapshots</name>
                <url>http://repo.spring.io/libs-snapshot-local</url>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </repository>
            <repository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>http://repo.spring.io/libs-milestone-local</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
            <repository>
                <id>spring-releases</id>
                <name>Spring Releases</name>
                <url>http://repo.spring.io/libs-release-local</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
        </repositories>
        <pluginRepositories>
            <pluginRepository>
                <id>spring-snapshots</id>
                <name>Spring Snapshots</name>
                <url>http://repo.spring.io/libs-snapshot-local</url>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </pluginRepository>
            <pluginRepository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>http://repo.spring.io/libs-milestone-local</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </pluginRepository>
        </pluginRepositories>
    
    
        <build>
            <finalName>spring_cloud_demo</finalName>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <filtering>true</filtering>
                </resource>
            </resources>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <delimiters>
                            <delimit>$</delimit>
                        </delimiters>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    • 启动类:
    package com.sunxiaping.eureka;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    /**
     * @author 许威威
     * @version 1.0
     */
    @SpringBootApplication
    @EnableEurekaServer //开启Eureka Server
    public class Eureka7002Application {
        public static void main(String[] args) {
            SpringApplication.run(Eureka7002Application.class, args);
        }
    }
    

    1.2.4 搭建eureka_service7003模块

    • pom.xml(复制eureka_service7001模块的pom.xml):
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>spring_cloud_demo</artifactId>
            <groupId>org.sunxiaping</groupId>
            <version>1.0</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>eureka_service7003</artifactId>
    
        <dependencies>
            <!--   导入Eureka Server对应的坐标     -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
        </dependencies>
    </project>
    
    • application.yml:
    server:
      # 单机版的端口
    #  port: 9000
      port: 7003 #端口
    
    #配置Eureka Server
    eureka:
      instance:
        # 主机地址名称
    #    hostname: localhost # 单机版的主机地址名称
        hostname: eureka7003.com
      client:
        register-with-eureka: false # 是否将自己注册到注册中心
        fetch-registry: false # 是否从Eureka中获取服务列表
        service-url:  # 配置暴露给Eureka Client的请求地址
           # 单机版
    #      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
           defaultZone:  http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
    
    • 总工程的pom.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <packaging>pom</packaging>
        <modules>
            <module>product_service</module>
            <module>spring_cloud_common</module>
            <module>order_service</module>
            <!--    此处修改    -->
            <module>eureka_service7001</module>
            <module>eureka_service7002</module>
            <module>eureka_service7003</module>
        </modules>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.6.RELEASE</version>
        </parent>
    
    
        <groupId>org.sunxiaping</groupId>
        <artifactId>spring_cloud_demo</artifactId>
        <version>1.0</version>
    
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.4</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Greenwich.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <repositories>
            <repository>
                <id>spring-snapshots</id>
                <name>Spring Snapshots</name>
                <url>http://repo.spring.io/libs-snapshot-local</url>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </repository>
            <repository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>http://repo.spring.io/libs-milestone-local</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
            <repository>
                <id>spring-releases</id>
                <name>Spring Releases</name>
                <url>http://repo.spring.io/libs-release-local</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
        </repositories>
        <pluginRepositories>
            <pluginRepository>
                <id>spring-snapshots</id>
                <name>Spring Snapshots</name>
                <url>http://repo.spring.io/libs-snapshot-local</url>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </pluginRepository>
            <pluginRepository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>http://repo.spring.io/libs-milestone-local</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </pluginRepository>
        </pluginRepositories>
    
    
        <build>
            <finalName>spring_cloud_demo</finalName>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <filtering>true</filtering>
                </resource>
            </resources>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <delimiters>
                            <delimit>$</delimit>
                        </delimiters>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    • 启动类:
    package com.sunxiaping.eureka;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    /**
     * @author 许威威
     * @version 1.0
     */
    @SpringBootApplication
    @EnableEurekaServer //开启Eureka Server
    public class Eureka7003Application {
        public static void main(String[] args) {
            SpringApplication.run(Eureka7003Application.class, args);
        }
    }
    

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

    • application.yml:
    server:
      port: 9001 # 微服务的端口号
    
    spring:
      application:
        name: service-product # 微服务的名称
      datasource:
        url: jdbc:mysql://192.168.237.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: 123456
      jpa:
        generate-ddl: true
        show-sql: true
        open-in-view: true
        database: mysql
    
    # 配置 eureka
    eureka:
      instance:
        # 主机名称:服务名称修改,其实就是向eureka server中注册的实例id
        instance-id: service-product:9001
        # 显示IP信息
        prefer-ip-address: true
      client:
        service-url: # 此处修改为 Eureka Server的集群地址
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
    
    # 微服务info内容详细信息
    info:
      app.name: xxx
      company.name: xxx
      build.artifactId: $project.artifactId$
      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:配置开启健康检查,并设置续约时间。
    # 配置 eureka
    eureka:
      instance:
        # 主机名称:服务名称修改,其实就是向eureka server中注册的实例id
        instance-id: service-product:9001
        # 显示IP信息
        prefer-ip-address: true
        lease-renewal-interval-in-seconds: 5 # 发送心跳续约间隔(默认30秒)
        lease-expiration-duration-in-seconds: 10 # Eureka Client发送心跳给Eureka Server端后,续约到期时间(默认90秒)
      client:
        healthcheck: 
          enable: true 	#开启健康检查(依赖spring-boot-actuator)
        service-url: # 此处修改为 Eureka Server的集群地址
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
    
    • 对于Eureka Server:配置关闭自我保护,设置剔除无效节点的时间间隔。
    #配置Eureka Server
    eureka:
      instance:
        # 主机地址名称
    #    hostname: localhost # 单机版的主机地址名称
        hostname: eureka7001.com
      client:
        register-with-eureka: false # 是否将自己注册到注册中心
        fetch-registry: false # 是否从Eureka中获取服务列表
        service-url:  # 配置暴露给Eureka Client的请求地址
           # 单机版
    #      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
           defaultZone:  http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
      server:
        enable-self-preservation: false # 关闭自我保护
        eviction-interval-timer-in-ms: 4000 #剔除时间间隔,单位:毫秒
    

    3 Eureka的源码解析

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

    3.1.1 @EnableEurekaServer注解的作用

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

    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文件

    • 根据SpringBoot自动配置原理,即SpringBoot启动的时候会加载所有jar包下的META-INF下的spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的自动配置类。所以,SpringBoot启动的时候会加载EurekaServerAutoConfiguration(EurekaServer的自动配置类),其源码如下:
    @Configuration
    @Import(EurekaServerInitializerConfiguration.class)
    @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
    @EnableConfigurationProperties({ EurekaDashboardProperties.class,InstanceRegistryProperties.class })
    @PropertySource("classpath:/eureka/server.properties")
    public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
    	//其他略
    }
    
    • 接下来解释EurekaServerAutoConfiguration(Eureka Server自动配置类)的作用:

    • 1️⃣EurekaServerAutoConfiguration(Eureka Server自动配置类)生效的前提是容器中存在EurekaServerMarkerConfiguration.Marker组件(可以从@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)这段代码看出),而@EnableEurekaServer注解的作用就是向容器中添加EurekaServerMarkerConfiguration.Marker组件,所以如果不在启动类中配置@EnableEurekaServer的话,那么EurekaServerAutoConfiguration(Eureka Server自动配置类)就不会起作用。

    • 2️⃣从@EnableConfigurationProperties({ EurekaDashboardProperties.class,InstanceRegistryProperties.class })代码中,我们知道EurekaServerAutoConfiguration(Eureka Server)向容器中导入了EurekaDashboardProperties和InstanceRegistryProperties两个组件。

      • EurekaDashboardProperties:配置EurekaServer管理台。
      • InstanceRegistryProperties:配置期望续约数量和默认的通信数量。
    • 3️⃣通过@Import(EurekaServerInitializerConfiguration.class)引入启动类。

    3.1.3 EurekaServerInitializerConfiguration

    • EurekaServerInitializerConfiguration.java
    @Configuration
    public class EurekaServerInitializerConfiguration
    		implements ServletContextAware, SmartLifecycle, Ordered {
    
    	private static final Log log = LogFactory.getLog(EurekaServerInitializerConfiguration.class);
    
    	@Autowired
    	private EurekaServerConfig eurekaServerConfig;
    
    	private ServletContext servletContext;
    
    	@Autowired
    	private ApplicationContext applicationContext;
    
    	@Autowired
    	private EurekaServerBootstrap eurekaServerBootstrap;
    
    	private boolean running;
    
    	private int order = 1;
    
    	@Override
    	public void setServletContext(ServletContext servletContext) {
    		this.servletContext = servletContext;
    	}
    
    	@Override
    	public void start() {
    		new Thread(new Runnable() {
    			@Override
    			public void run() {
    				try {
    					//TODO: is this class even needed now?
    					eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
    					log.info("Started Eureka Server");
    
    					publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
    					EurekaServerInitializerConfiguration.this.running = true;
    					publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
    				}
    				catch (Exception ex) {
    					// Help!
    					log.error("Could not initialize Eureka servlet context", ex);
    				}
    			}
    		}).start();
    	}
    
    	private EurekaServerConfig getEurekaServerConfig() {
    		return this.eurekaServerConfig;
    	}
    
    	private void publish(ApplicationEvent event) {
    		this.applicationContext.publishEvent(event);
    	}
    
    	@Override
    	public void stop() {
    		this.running = false;
    		eurekaServerBootstrap.contextDestroyed(this.servletContext);
    	}
    
    	@Override
    	public boolean isRunning() {
    		return this.running;
    	}
    
    	@Override
    	public int getPhase() {
    		return 0;
    	}
    
    	@Override
    	public boolean isAutoStartup() {
    		return true;
    	}
    
    	@Override
    	public void stop(Runnable callback) {
    		callback.run();
    	}
    
    	@Override
    	public int getOrder() {
    		return this.order;
    	}
    }
    
    • 从源码中,可以看出EurekaServerInitializerConfiguration实现了SmartLifecycle,意味着Spring容器启动时会指定start()方法。加载所有的EurekaServer的配置。

    3.1.4 EurekaServerAutoConfiguration

    • EurekaServerAutoConfiguration的部分源码如下:
    @Configuration
    @Import(EurekaServerInitializerConfiguration.class)
    @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
    @EnableConfigurationProperties({ EurekaDashboardProperties.class,
    		InstanceRegistryProperties.class })
    @PropertySource("classpath:/eureka/server.properties")
    public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
        
        //实例化了Eureka Server的管控台Controller类EurekaController
        @Bean
    	@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
    	public EurekaController eurekaController() {
    		return new EurekaController(this.applicationInfoManager);
    	}
    	
        //实例化EurekaServerBootstrap类
        @Bean
    	public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
    			EurekaServerContext serverContext) {
    		return new EurekaServerBootstrap(this.applicationInfoManager,
    				this.eurekaClientConfig, this.eurekaServerConfig, registry,
    				serverContext);
    	}
        
        //实例化jersey相关配置类
    	@Bean
    	public FilterRegistrationBean jerseyFilterRegistration(
    			javax.ws.rs.core.Application eurekaJerseyApp) {
    		FilterRegistrationBean bean = new FilterRegistrationBean();
    		bean.setFilter(new ServletContainer(eurekaJerseyApp));
    		bean.setOrder(Ordered.LOWEST_PRECEDENCE);
    		bean.setUrlPatterns(
    				Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));
    
    		return bean;
    	}
        
        //jerseyApplication方法,在容器中存放了一个jerseyApplication对象,
        //jerseyApplication方法和Spring源码扫描@Component逻辑类似,扫描@Path和@Provider标签,然后封装成BeanDefinition,
        //封装到APPlication里面的Set容器。然后Filter过滤器来过滤URL进行映射到对象的Controller
        @Bean
    	public javax.ws.rs.core.Application jerseyApplication(Environment environment,
    			ResourceLoader resourceLoader) {
    
    		ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
    				false, environment);
    
    		// Filter to include only classes that have a particular annotation.
    		//
    		provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
    		provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
    
    		// Find classes in Eureka packages (or subpackages)
    		//
    		Set<Class<?>> classes = new HashSet<>();
    		for (String basePackage : EUREKA_PACKAGES) {
    			Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
    			for (BeanDefinition bd : beans) {
    				Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
    						resourceLoader.getClassLoader());
    				classes.add(cls);
    			}
    		}
        
    
    }   
    

    3.1.5 暴露的服务端接口

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

    eureka-core-1.9.8.jar中的XxxResource

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

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

    • 在ApplicationResource的addInstance()方法中可以看到this.registry.register(info, "true".equals(isReplication));
    @Produces({"application/xml", "application/json"})
    public class ApplicationResource {
     	@POST
        @Consumes({"application/json", "application/xml"})
        public Response addInstance(InstanceInfo info, @HeaderParam("x-netflix-discovery-replication") String isReplication) {
            logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
            if (this.isBlank(info.getId())) {
                return Response.status(400).entity("Missing instanceId").build();
            } else if (this.isBlank(info.getHostName())) {
                return Response.status(400).entity("Missing hostname").build();
            } else if (this.isBlank(info.getIPAddr())) {
                return Response.status(400).entity("Missing ip address").build();
            } else if (this.isBlank(info.getAppName())) {
                return Response.status(400).entity("Missing appName").build();
            } else if (!this.appName.equals(info.getAppName())) {
                return Response.status(400).entity("Mismatched appName, expecting " + this.appName + " but was " + info.getAppName()).build();
            } else if (info.getDataCenterInfo() == null) {
                return Response.status(400).entity("Missing dataCenterInfo").build();
            } else if (info.getDataCenterInfo().getName() == null) {
                return Response.status(400).entity("Missing dataCenterInfo Name").build();
            } else {
                DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
                if (dataCenterInfo instanceof UniqueIdentifier) {
                    String dataCenterInfoId = ((UniqueIdentifier)dataCenterInfo).getId();
                    if (this.isBlank(dataCenterInfoId)) {
                        boolean experimental = "true".equalsIgnoreCase(this.serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
                        if (experimental) {
                            String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
                            return Response.status(400).entity(entity).build();
                        }
    
                        if (dataCenterInfo instanceof AmazonInfo) {
                            AmazonInfo amazonInfo = (AmazonInfo)dataCenterInfo;
                            String effectiveId = amazonInfo.get(MetaDataKey.instanceId);
                            if (effectiveId == null) {
                                amazonInfo.getMetadata().put(MetaDataKey.instanceId.getName(), info.getId());
                            }
                        } else {
                            logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
                        }
                    }
                }
    
                this.registry.register(info, "true".equals(isReplication));
                return Response.status(204).build();
            }
        }   
    }    
    
    • register()方法的有关源码如下:
    @Singleton
    public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
        
        @Override
        public void register(final InstanceInfo info, final boolean isReplication) {
            //默认有效时长为90秒
            int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
            if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
                leaseDuration = info.getLeaseInfo().getDurationInSecs();
            }
            //注册实例
            super.register(info, leaseDuration, isReplication);
            //同步到其他的Eureka Server服务
            replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
        }
        
        //其他略
    }    
    
    • 继续找到父类的register方法可以看到整个注册过程:
    public abstract class AbstractInstanceRegistry implements InstanceRegistry {
            
            //线程安全的Map,存放所有注册的实例对象
            private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
                = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
            
            public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
            try {
                read.lock();
                Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
                REGISTER.increment(isReplication);
                //如果第一个实例注册会给registry put进去一个空的
                if (gMap == null) {
                    final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
                    gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
                    if (gMap == null) {
                        gMap = gNewMap;
                    }
                }
                // 根据注册的实例对象id,获取已经存在的Lease
                Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
                // Retain the last dirty timestamp without overwriting it, if there is already a lease
                if (existingLease != null && (existingLease.getHolder() != null)) {
                    Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
                    Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
                    logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
    
                    // this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
                    // InstanceInfo instead of the server local copy.
                    if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
                        logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
                                " than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
                        logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
                        registrant = existingLease.getHolder();
                    }
                } else {
                    // The lease does not exist and hence it is a new registration
                    synchronized (lock) {
                        if (this.expectedNumberOfClientsSendingRenews > 0) {
                            // Since the client wants to register it, increase the number of clients sending renews
                            this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
                            updateRenewsPerMinThreshold();
                        }
                    }
                    logger.debug("No previous lease information found; it is new registration");
                }
                Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
                if (existingLease != null) {
                    lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
                }
                //将lease存入gMap
                gMap.put(registrant.getId(), lease);
                synchronized (recentRegisteredQueue) {
                    recentRegisteredQueue.add(new Pair<Long, String>(
                            System.currentTimeMillis(),
                            registrant.getAppName() + "(" + registrant.getId() + ")"));
                }
                // This is where the initial state transfer of overridden status happens
                if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
                    logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
                                    + "overrides", registrant.getOverriddenStatus(), registrant.getId());
                    if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
                        logger.info("Not found overridden id {} and hence adding it", registrant.getId());
                        overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
                    }
                }
                InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
                if (overriddenStatusFromMap != null) {
                    logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
                    registrant.setOverriddenStatus(overriddenStatusFromMap);
                }
    
                // Set the status based on the overridden status rules
                InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
                registrant.setStatusWithoutDirty(overriddenInstanceStatus);
    
                // If the lease is registered with UP status, set lease service up timestamp
                if (InstanceStatus.UP.equals(registrant.getStatus())) {
                    lease.serviceUp();
                }
                registrant.setActionType(ActionType.ADDED);
                recentlyChangedQueue.add(new RecentlyChangedItem(lease));
                registrant.setLastUpdatedTimestamp();
                invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
                logger.info("Registered instance {}/{} with status {} (replication={})",
                        registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
            } finally {
                read.unlock();
            }
        }
    
       //其他略   
    }    
    

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

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

    3.1.8 服务剔除

    • 在AbstractInstanceRegistry的postInit()方法中开启了一个每60秒调用一次EvictionTask的evict的定时器。
    public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    	public void evict(long additionalLeaseMs) {
            logger.debug("Running the evict task");
    
            if (!isLeaseExpirationEnabled()) {
                logger.debug("DS: lease expiration is currently disabled.");
                return;
            }
    
            // We collect first all expired items, to evict them in random order. For large eviction sets,
            // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
            // the impact should be evenly distributed across all applications.
            List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
            for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
                Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
                if (leaseMap != null) {
                    for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
                        Lease<InstanceInfo> lease = leaseEntry.getValue();
                        if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
                            expiredLeases.add(lease);
                        }
                    }
                }
            }
    
            // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
            // triggering self-preservation. Without that we would wipe out full registry.
            int registrySize = (int) getLocalRegistrySize();
            int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
            int evictionLimit = registrySize - registrySizeThreshold;
    
            int toEvict = Math.min(expiredLeases.size(), evictionLimit);
            if (toEvict > 0) {
                logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);
    
                Random random = new Random(System.currentTimeMillis());
                for (int i = 0; i < toEvict; i++) {
                    // Pick a random item (Knuth shuffle algorithm)
                    int next = i + random.nextInt(expiredLeases.size() - i);
                    Collections.swap(expiredLeases, i, next);
                    Lease<InstanceInfo> lease = expiredLeases.get(i);
    
                    String appName = lease.getHolder().getAppName();
                    String id = lease.getHolder().getId();
                    EXPIRED.increment();
                    logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
                    internalCancel(appName, id, false);
                }
            }
        }
        //其他略
    }
    

    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的所有自动装载的配置类

    3.2.2 服务注册

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

    3.2.3 服务下架

    • DiscoveryClient中有shutdown方法,即服务下架方法:
    @Singleton
    public class DiscoveryClient implements EurekaClient {
        @PreDestroy
        public synchronized void shutdown() {
            if (this.isShutdown.compareAndSet(false, true)) {
                logger.info("Shutting down DiscoveryClient ...");
                if (this.statusChangeListener != null && this.applicationInfoManager != null) {
                    this.applicationInfoManager.unregisterStatusChangeListener(this.statusChangeListener.getId());
                }
    
                this.cancelScheduledTasks();
                if (this.applicationInfoManager != null && this.clientConfig.shouldRegisterWithEureka() && this.clientConfig.shouldUnregisterOnShutdown()) {
                    this.applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
                    this.unregister();
                }
    
                if (this.eurekaTransport != null) {
                    this.eurekaTransport.shutdown();
                }
    
                this.heartbeatStalenessMonitor.shutdown();
                this.registryStalenessMonitor.shutdown();
                logger.info("Completed shut down of DiscoveryClient");
            }
    
        }
     	//其他略   
    }    
    

    3.2.4 心跳续约

    • DiscoveryClient的HeartbeatThread中定义了续约的操作:
    @Singleton
    public class DiscoveryClient implements EurekaClient {
        private class HeartbeatThread implements Runnable {
            private HeartbeatThread() {
            }
    
            public void run() {
                if (DiscoveryClient.this.renew()) {
                    DiscoveryClient.this.lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
                }
    
            }
        }
        
        boolean renew() {
            try {
                EurekaHttpResponse<InstanceInfo> httpResponse = this.eurekaTransport.registrationClient.sendHeartBeat(this.instanceInfo.getAppName(), this.instanceInfo.getId(), this.instanceInfo, (InstanceStatus)null);
                logger.debug("DiscoveryClient_{} - Heartbeat status: {}", this.appPathIdentifier, httpResponse.getStatusCode());
                if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
                    this.REREGISTER_COUNTER.increment();
                    logger.info("DiscoveryClient_{} - Re-registering apps/{}", this.appPathIdentifier, this.instanceInfo.getAppName());
                    long timestamp = this.instanceInfo.setIsDirtyWithTime();
                    boolean success = this.register();
                    if (success) {
                        this.instanceInfo.unsetIsDirty(timestamp);
                    }
    
                    return success;
                } else {
                    return httpResponse.getStatusCode() == Status.OK.getStatusCode();
                }
            } catch (Throwable var5) {
                logger.error("DiscoveryClient_{} - was unable to send heartbeat!", this.appPathIdentifier, var5);
                return false;
            }
        }
    	//其他略      
    }    
    
  • 相关阅读:
    Atitit 函数式编程与命令行语言的区别与优缺点 目录 1. 常见的函数式语言 2 1.1. 命令行 bat 2 1.2. Sql h5 css 正则表达式 2 1.3. 工作流语言 anno注
    Atitit 职业资格证书分类等级 目录 1. 等级 :初级(五级)、中级(四级)、高级(三级)、技师(二级)和高级技师(一级)。 1 2. 折叠分类 2 2.1. 生产、运输设备操作人员 2 2
    Atitit 价值观与理念总结 Diy自力更生尽可能 提高独立性 减少依赖 大而全优先于小而美 适度分权防止集权导致大决策失误 方式多种多样 综合是王道 简单快速优先 努力实现人们喜闻乐见的服务 信
    Atitit webdav应用场景 提升效率 小型数据管理 目录 1.1. 显示datalist 1 1.2. Ajax填充数据 1 1.3. 编辑数据 2 1.1.显示datalist
    Atitit 企业战略目标的艺术 目录 1. 企业战略目标 1 2.  特点 ▪ 宏观性 ▪ 长期性 ▪ 全面性 稳定性 1 3. 内容 2 3.1. 彼得·德鲁克在《管理实践》一书中提出了八个
    Atitit 多线程 什么时候使用多进程的选择场景 目录 1.1. 看实现,比如你的用node.js实现,那就没得选了,只能多进程 1 1.2. 如果用java这一类,可以选择多进程与多线程模式,或
    Atitit 短信验证的漏洞 目录 1.1. APP读取短信 1 1.2. 手机上访问的业务来说,短信验证码就没那么独立了 1 1.3. 短信保管箱” 1 1.4. 自动把短信备份到云端的功能。 2
    Atitit 提升水平 把代码写的有技术含量 目录 1. 提高可读性(重要) 2 1.1. 异常模式代替返回值 2 1.2. Dsl 2 1.3. 流畅接口方法链 2 1.4. 层次结构抽象 2 1
    Atitit 函数式常用子操作与组合 目录 1. 集合类的操作 1 1.1. Transform、map 1 1.2. paip.提升效率filter map reduce 的java 函
    高级人才、专业技术人才、技能人才 目录 1. 高级人才, 1 1.1. 专业技术人才 2 2. 专业技术人才 2 3. 高技能人才 3 1.高级人才, 可迁入本市市区落户,其配偶、未婚子女(含离
  • 原文地址:https://www.cnblogs.com/xuweiweiwoaini/p/13726541.html
Copyright © 2020-2023  润新知