SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka

4.Eureka注册管理中心

4.0 注意事项放在前面, 在配置应用名称时,不要用下划线

比如:

spring:
  application:
    name: register_server #错误的配置方式

4.1 认识Eurea

​ Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均

​ 衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。

​ 在项目中使用Spring Cloud Euraka的原因是***它可以利用Spring Cloud Netfilix中其他的组件,如hystrix等,因为Euraka是属于Netfilix的。

4.2Euraka介绍

Eureka由多个instance(服务实例)组成,这些服务实例可以分为两种:Eureka Server和Eureka Client。为了便于理解,我们将Eureka client再分为Service Provider和Service Consumer。

  • Eureka Server 注册中心 提供服务注册和发现(属于服务中心)
  • Service Provider 服务提供方,将自身服务注册到Eureka,从而使服务消费方能够找到(属于客户端)
  • Service Consumer服务消费方,从Eureka获取注册服务列表,从而能够消费服务(属于客户端)

举个列子

| 网约车

这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多
人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。
一个想要,一个愿意给,就是缺少引子,缺乏管理啊。
此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息
(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。
此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你
面前,为你服务,完美!

|Eureka做什么?

Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉
Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务
列表中剔除。
这就实现了服务的自动注册、发现、状态监控

架构图:

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第1张图片

  • Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址

  • 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)

  • 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
    心跳(续约):服务提供者定期通过http方式向Eureka刷新自己的状态

4.3 入门

  • 步骤一: 创建一个父级工程(维护springBoot,SpringCloud版本一致性),配置pom.xml

    <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.1.4.RELEASEversion>
            <relativePath/>
     parent>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloudgroupId>
                    <artifactId>spring-cloud-dependenciesartifactId>
                    <version>Greenwich.SR1version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
            dependencies>
        dependencyManagement>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                plugin>
            plugins>
        build>
    
  • 步骤二: 创建一个子工程, 创建eureka注册中心,配置pom.xml和application.yml

    1. 配置pom.xml

              <dependency>
                  <groupId>org.springframework.cloudgroupId>
                  <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
              dependency>
      
    2. 配置application.yml

      server:
        port: 8761 #默认端口
      spring:
        application:
      	name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
      eureka:
        instance:
          hostname: localhost
        client:
          registerWithEureka: false #不注册自己
          fetchRegistry: false #不拉取服务
          serviceUrl: #注册地址
            defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      
    3. 启动类

      @EnableEurekaServer
      @SpringBootApplication
      public class RegisterApplication {
          public static void main(String[] args) {
              SpringApplication.run(RegisterApplication.class,args);
          }
      }
      
      

    4.测试

    启动 eureka-server 访问:http://127.0.0.1:8761(翻译为中文了)

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第2张图片

  • 步骤三: 部署Eureka Client,分别创建2个子级工程,分别为Service Provider(服务提供方)和Service Consumer(服务消费方)

    1、Eureka Client包括两个服务模块:Service Provider(服务提供方)和Service Consumer(服务消费方)。

    2、Eureka Client和Eureka Server目录类似, 不同点在于:

    • 启动类,使用@EnableDiscoveryClient(或者@EnableEurekaClient) 标识该服务为Euraka Client
    • 配置文件,需要指定Euraka Server地址和当前服务注册时的名称。

    3、创建一个子级工程: 服务提供方Service Provider(服务提供方)

    3.1 配置pom.xml文件和application.yml文件

    pom.xml配置

          <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    

    application.yml配置

    server:
      port: 8881
    eureka:
      client:
        register-with-eureka: true #注册服务到服务中心, 默认是true,可以不用写
        fetch-registry: true #拉取服务, 默认是true,可以不用写
        service-url:    
          defaultZone: http://localhost:8761/eureka/  #服務中心地址
    spring:
      application:
        name: step1-provider
    

    ​ 3.2 创建启动类

    @SpringBootApplication
    @EnableEurekaClient
    public class ProviderApplication {
    
      public static void main(String[] args) {
          SpringApplication.run(ProviderApplication.class,args);
      }
    }
    

    ​ 3.3 配置controller

    @RestController
    public class FilmController {
        @Resource
        InstanceInfo info;
        @RequestMapping("/film")
        public String get(){
     
            return "test---电影";
        }
    

    4.创建一个子级工程: 服务提供方Service Consumer(服务消费方)

    4.1 配置pom.xml和application.yml

    pom.xml配置

          <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    

    application.yml配置

    server:
      port: 8882
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8761/eureka/
    spring:
      application:
        name: spring_cloud_find_consume
    

    4.2 创建启动类

    @EnableEurekaClient
    @SpringBootApplication
    public class ConsumeApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConsumeApplication.class,args);
        }
    
        @Bean
        public RestTemplate get(){
            return  new  RestTemplate();
        }
    }
    
    

    4.3 测试

    方式一: 使用 RestTemplate,直接远程访问,这样的话,访问地址就写死了.

    @Controller
    public class DemoController {
    
        @Resource
        RestTemplate template;
        @RequestMapping("/demo")
        @ResponseBody
        public String testX() throws Exception{
            String uri = "http://step1-provider/film";// uri =http://localhost:8881/film
            String v  = (String)this.template.getForObject(uri,String.class);
            System.out.println(v);
            return v;
        }
    }
    

    方式二: 使用RestTemplate+DiscoveryClient discoveryClient,灵活获取服务地址uri;

     @Resource
        RestTemplate template;
        @Resource
        DiscoveryClient discoveryClient;
        @RequestMapping("/demo")
        @ResponseBody
        public String testX2() throws Exception{
            List<ServiceInstance> services = discoveryClient.getInstances("step1-provider");//服务实例,目前只注册了一个服务提供方
            ServiceInstance serviceInstance = services.get(0);//从list集合中取出服务
            String url_visit = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/film";
            return template.getForObject(url_visit,String.class);
        }
    

    在浏览器地址栏: http://localhost:8882/demo

4.4 Eureka高可用负载均衡配置

刚才我们在入门案例中会发现 服务注册中心,服务提供方只有一个,那么如果出现问题,就不能访问了.

那么这个问题该怎么解决呢?

Eureka是spring cloud中的一个负责服务注册与发现的组件。遵循着CAP理论中的A(可用性)P(分区容错性)。

那么本着高可用和容错性的原则,我们可以搭建eureka集群:

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第3张图片

| 服务同步
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把
服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一
个节点,都可以获取到完整的服务列表信息。

所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务,注册到其它EurekaServer上,这样多个
EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:

比如: 单机部署注册中心集群(保证单机有两个以及两个以上网卡),创建两个服务注册中心

注册中心一:

application.yml

#标准的配置方式
server:
  port: ${port:8761}
eureka:
  instance:
    hostname: localhost
    prefer-ip-address: true
    ip-address: 127.0.0.1
  client:
    serviceUrl:
      defaultZone: "http://192.168.13.131:8762/eureka"
spring:
  application:
    name: register-server
#非标准配置方式(这样的话,自己会成为自己的副本,比如:8761的副本有8761和8762)
server:
  port: ${port:8761}
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: ${defaultZone:http://localhost:8761/eureka/},${defaultZone:http://localhost:8762/eureka/}
spring:
  application:
    name: register-server

注册中心二:

application.yml

同时修改服务提供方的yml地址

#标准的配置方式
server:
  port: ${port:8762}
eureka:
  instance:
    hostname: localhost
    prefer-ip-address: true
    ip-address:  192.168.13.131
  client:
    serviceUrl:
      defaultZone: "http://127.0.0.1:8761/eureka/"
spring:
  application:
    name: register-server
#非标准的配置方式(这样的话,自己会成为自己的副本,比如:8762的副本有8761和8762)
server:
  port: ${port:8762}
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: ${defaultZone:http://localhost:8762/eureka/},${defaultZone:http://localhost:8761/eureka/}
spring:
  application:
    name: register-server

那么服务提供方(当然可以配置多个,形成高可用)或者消费方都需要修改注册中心的地址

比如:

eureka:
  client:
	service-url: # EurekaServer地址,多个地址以','隔开
defaultZone: http://127.0.0.1:8671/eureka,http://127.0.0.1:8672/eureka

标准方式配置测试:

  1. 访问http://localhost:8671/eureka

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第4张图片

  1. 访问http://localhost:8672/eureka

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第5张图片

非标准方式配置测试:

​ 1.访问http://localhost:8671/eureka

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第6张图片

​ 2.访问http://localhost:8671/eureka

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第7张图片

4.5 Eureka客户端理解

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第8张图片

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第9张图片

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第10张图片

4.6 失效剔除和自我保护

如下的配置都是在Eureka Server服务端进行:
|服务下线
当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线
了”。服务中心接受到请求之后,将该服务置为下线状态。

|失效剔除
有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下
线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间
(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。
可以通过 eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。

|自我保护

我们关停一个服务,很可能会在Eureka面板看到一条警告:

在这里插入图片描述

这是触发了Eureka的自我保护机制。当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的比例是否低于了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。
可以通过下面的配置来关停自我保护:

eureka:
 server:
	enable-self-preservation: false # 关闭自我保护模式(缺省为打开)

详解参考官网:https://github.com/Netflix/eureka/wiki/Understanding-Eureka-Peer-to-Peer-Communication

保护模式,是Eureka 提供的一个特性,在默认的情况下,这个属性是打开的,而且也建议线上都使用这个特性。

如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,此时会触发Eureka Server进入保护模式,进入自我保护模式后,将会保护服务注册表中的信息,不再删除服务注册表中的数据。

如果上面的看完4.5,4.6不是很理解,看下面的图片:

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第11张图片

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第12张图片

4.7 Eureka常见参数配置

4.7.1Eureka Server常用配置
#服务端开启自我保护模式,前面章节有介绍
eureka.server.enable-self-preservation=true
#扫描失效服务的间隔时间(单位毫秒,默认是60*1000)即60秒
eureka.server.eviction-interval-timer-in-ms= 60000
#间隔多长时间,清除过期的 delta 数据
eureka.server.delta-retention-timer-interval-in-ms=0
#请求频率限制器
eureka.server.rate-limiter-burst-size=10
#是否开启请求频率限制器
eureka.server.rate-limiter-enabled=false
#请求频率的平均值
eureka.server.rate-limiter-full-fetch-average-rate=100
#是否对标准的client进行频率请求限制。如果是false,则只对非标准client进行限制
eureka.server.rate-limiter-throttle-standard-clients=false
#注册服务、拉去服务列表数据的请求频率的平均值
eureka.server.rate-limiter-registry-fetch-average-rate=500
#设置信任的client list
eureka.server.rate-limiter-privileged-clients=
#在设置的时间范围类,期望与client续约的百分比。
eureka.server.renewal-percent-threshold=0.85
#多长时间更新续约的阈值
eureka.server.renewal-threshold-update-interval-ms=0
#对于缓存的注册数据,多长时间过期
eureka.server.response-cache-auto-expiration-in-seconds=180
#多长时间更新一次缓存中的服务注册数据
eureka.server.response-cache-update-interval-ms=0
#缓存增量数据的时间,以便在检索的时候不丢失信息
eureka.server.retention-time-in-m-s-in-delta-queue=0
#当时间戳不一致的时候,是否进行同步
eureka.server.sync-when-timestamp-differs=true
#是否采用只读缓存策略,只读策略对于缓存的数据不会过期。
eureka.server.use-read-only-response-cache=true


################server node 与 node 之间关联的配置#####################33
#发送复制数据是否在request中,总是压缩
eureka.server.enable-replicated-request-compression=false
#指示群集节点之间的复制是否应批处理以提高网络效率。
eureka.server.batch-replication=false
#允许备份到备份池的最大复制事件数量。而这个备份池负责除状态更新的其他事件。可以根据内存大小,超时和复制流量,来设置此值得大小
eureka.server.max-elements-in-peer-replication-pool=10000
#允许备份到状态备份池的最大复制事件数量
eureka.server.max-elements-in-status-replication-pool=10000
#多个服务中心相互同步信息线程的最大空闲时间
eureka.server.max-idle-thread-age-in-minutes-for-peer-replication=15
#状态同步线程的最大空闲时间
eureka.server.max-idle-thread-in-minutes-age-for-status-replication=15
#服务注册中心各个instance相互复制数据的最大线程数量
eureka.server.max-threads-for-peer-replication=20
#服务注册中心各个instance相互复制状态数据的最大线程数量
eureka.server.max-threads-for-status-replication=1
#instance之间复制数据的通信时长
eureka.server.max-time-for-replication=30000
#正常的对等服务instance最小数量。-1表示服务中心为单节点。
eureka.server.min-available-instances-for-peer-replication=-1
#instance之间相互复制开启的最小线程数量
eureka.server.min-threads-for-peer-replication=5
#instance之间用于状态复制,开启的最小线程数量
eureka.server.min-threads-for-status-replication=1
#instance之间复制数据时可以重试的次数
eureka.server.number-of-replication-retries=5
#eureka节点间间隔多长时间更新一次数据。默认10分钟。
eureka.server.peer-eureka-nodes-update-interval-ms=600000
#eureka服务状态的相互更新的时间间隔。
eureka.server.peer-eureka-status-refresh-time-interval-ms=0
#eureka对等节点间连接超时时间
eureka.server.peer-node-connect-timeout-ms=200
#eureka对等节点连接后的空闲时间
eureka.server.peer-node-connection-idle-timeout-seconds=30
#节点间的读数据连接超时时间
eureka.server.peer-node-read-timeout-ms=200
#eureka server 节点间连接的总共最大数量
eureka.server.peer-node-total-connections=1000
#eureka server 节点间连接的单机最大数量
eureka.server.peer-node-total-connections-per-host=10
#在服务节点启动时,eureka尝试获取注册信息的次数
eureka.server.registry-sync-retries=
#在服务节点启动时,eureka多次尝试获取注册信息的间隔时间
eureka.server.registry-sync-retry-wait-ms=
#当eureka server启动的时候,不能从对等节点获取instance注册信息的情况,应等待多长时间。
eureka.server.wait-time-in-ms-when-sync-empty=0
4.7.2 Eureka Client 常用配置
#该客户端是否可用
eureka.client.enabled=true
#实例是否在eureka服务器上注册自己的信息以供其他服务发现,默认为true
eureka.client.register-with-eureka=false
#此客户端是否获取eureka服务器注册表上的注册信息,默认为true
eureka.client.fetch-registry=false
#是否过滤掉,非UP的实例。默认为true
eureka.client.filter-only-up-instances=true
#与Eureka注册服务中心的通信zone和url地址
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

#client连接Eureka服务端后的空闲等待时间,默认为30 秒
eureka.client.eureka-connection-idle-timeout-seconds=30
#client连接eureka服务端的连接超时时间,默认为5秒
eureka.client.eureka-server-connect-timeout-seconds=5
#client对服务端的读超时时长
eureka.client.eureka-server-read-timeout-seconds=8
#client连接all eureka服务端的总连接数,默认200
eureka.client.eureka-server-total-connections=200
#client连接eureka服务端的单机连接数量,默认50
eureka.client.eureka-server-total-connections-per-host=50
#执行程序指数回退刷新的相关属性,是重试延迟的最大倍数值,默认为10
eureka.client.cache-refresh-executor-exponential-back-off-bound=10
#执行程序缓存刷新线程池的大小,默认为5
eureka.client.cache-refresh-executor-thread-pool-size=2
#心跳执行程序回退相关的属性,是重试延迟的最大倍数值,默认为10
eureka.client.heartbeat-executor-exponential-back-off-bound=10
#心跳执行程序线程池的大小,默认为5
eureka.client.heartbeat-executor-thread-pool-size=5
# 询问Eureka服务url信息变化的频率(s),默认为300秒
eureka.client.eureka-service-url-poll-interval-seconds=300
#最初复制实例信息到eureka服务器所需的时间(s),默认为40秒
eureka.client.initial-instance-info-replication-interval-seconds=40
#间隔多长时间再次复制实例信息到eureka服务器,默认为30秒
eureka.client.instance-info-replication-interval-seconds=30
#从eureka服务器注册表中获取注册信息的时间间隔(s),默认为30秒
eureka.client.registry-fetch-interval-seconds=30

# 获取实例所在的地区。默认为us-east-1
eureka.client.region=us-east-1
#实例是否使用同一zone里的eureka服务器,默认为true,理想状态下,eureka客户端与服务端是在同一zone下
eureka.client.prefer-same-zone-eureka=true
# 获取实例所在的地区下可用性的区域列表,用逗号隔开。(AWS)
eureka.client.availability-zones.china=defaultZone,defaultZone1,defaultZone2
#eureka服务注册表信息里的以逗号隔开的地区名单,如果不这样返回这些地区名单,则客户端启动将会出错。默认为null
eureka.client.fetch-remote-regions-registry=
#服务器是否能够重定向客户端请求到备份服务器。 如果设置为false,服务器将直接处理请求,如果设置为true,它可能发送HTTP重定向到客户端。默认为false
eureka.client.allow-redirects=false
#客户端数据接收
eureka.client.client-data-accept=
#增量信息是否可以提供给客户端看,默认为false
eureka.client.disable-delta=false
#eureka服务器序列化/反序列化的信息中获取“_”符号的的替换字符串。默认为“__“
eureka.client.escape-char-replacement=__
#eureka服务器序列化/反序列化的信息中获取“$”符号的替换字符串。默认为“_-”
eureka.client.dollar-replacement="_-"
#当服务端支持压缩的情况下,是否支持从服务端获取的信息进行压缩。默认为true
eureka.client.g-zip-content=true
#是否记录eureka服务器和客户端之间在注册表的信息方面的差异,默认为false
eureka.client.log-delta-diff=false
# 如果设置为true,客户端的状态更新将会点播更新到远程服务器上,默认为true
eureka.client.on-demand-update-status-change=true
#此客户端只对一个单一的VIP注册表的信息感兴趣。默认为null
eureka.client.registry-refresh-single-vip-address=
#client是否在初始化阶段强行注册到服务中心,默认为false
eureka.client.should-enforce-registration-at-init=false
#client在shutdown的时候是否显示的注销服务从服务中心,默认为true
eureka.client.should-unregister-on-shutdown=true
4.7.3Eureka Instance 常用配置
#服务注册中心实例的主机名
eureka.instance.hostname=localhost
#注册在Eureka服务中的应用组名
eureka.instance.app-group-name=
#注册在的Eureka服务中的应用名称
eureka.instance.appname=
#该实例注册到服务中心的唯一ID
eureka.instance.instance-id=
#该实例的IP地址
eureka.instance.ip-address=
#该实例,相较于hostname是否优先使用IP
eureka.instance.prefer-ip-address=false

4.8 [Eureka 缓存机制详细配置]

Eureka Server 内部有二层缓存机制,那这些机制是如何工作的,以及 Eureka Server 是如何存储服务的注册信息,本节会给大家揭晓。

Eureka 在使用过程中有一些非常重要的配置项,本节也会整理出来,方便大家以后在生产环境根据项目场景来调整。

Eureka Server 数据存储

我们知道 Eureka Server 在运行期间就是一个普通的 Java 项目,并没有使用数据库之类的存储软件,那么在运行期间是如何存储数据的呢?

Eureka Server 的数据存储分了两层:数据存储层和缓存层。数据存储层记录注册到 Eureka Server 上的服务信息,缓存层是经过包装后的数据,可以直接在 Eureka Client 调用时返回。我们先来看看数据存储层的数据结构。

Eureka Server 的数据存储层是双层的 ConcurrentHashMap,我们知道 ConcurrentHashMap 是线程安全高效的 Map 集合。

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();

第一层的 ConcurrentHashMap 的 key=spring.application.name 也就是客户端实例注册的应用名;value 为嵌套的 ConcurrentHashMap。

第二层嵌套的 ConcurrentHashMap 的 key=instanceId 也就是服务的唯一实例 ID,value 为 Lease 对象,Lease 对象存储着这个实例的所有注册信息,包括 ip 、端口、属性等。

根据这个存储结构我们可以发现,Eureka Server 第一层都是存储着所有的服务名,以及服务名对应的实例信息,也就是说第一层都是按照服务应用名这个维度来切分存储:

在这里插入图片描述

第二层是根据实例的唯一 ID 来存储的,那么按照这个结构最终的存储数据格式为:

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第13张图片

数据存储层数据结构如下图所示:

SpringCloud学习第三章: 从懵懂到深入理解和运用Eureka_第14张图片

当如服务的状态发生变更时,会同步 Eureka Server 中的 registry 数据信息,比如服务注册、剔除服务时。

Eureka Server 缓存机制

Eureka Server 为了提供响应效率,提供了两层的缓存结构,将 Eureka Client 所需要的注册信息,直接存储在缓存结构中。

第一层缓存:readOnlyCacheMap,本质上是 ConcurrentHashMap,依赖定时从 readWriteCacheMap 同步数据,默认时间为 30 秒。

readOnlyCacheMap : 是一个 CurrentHashMap 只读缓存,这个主要是为了供客户端获取注册信息时使用,其缓存更新,依赖于定时器的更新,通过和 readWriteCacheMap 的值做对比,如果数据不一致,则以 readWriteCacheMap 的数据为准。

第二层缓存:readWriteCacheMap,本质上是 Guava 缓存。

readWriteCacheMap:readWriteCacheMap 的数据主要同步于存储层。当获取缓存时判断缓存中是否没有数据,如果不存在此数据,则通过 CacheLoader 的 load 方法去加载,加载成功之后将数据放入缓存,同时返回数据。

readWriteCacheMap 缓存过期时间,默认为 180 秒,当服务下线、过期、注册、状态变更,都会来清除此缓存中的数据。

Eureka Client 获取全量或者增量的数据时,会先从一级缓存中获取;如果一级缓存中不存在,再从二级缓存中获取;如果二级缓存也不存在,这时候先将存储层的数据同步到缓存中,再从缓存中获取。

通过 Eureka Server 的二层缓存机制,可以非常有效地提升 Eureka Server 的响应时间,通过数据存储层和缓存层的数据切割,根据使用场景来提供不同的数据支持。

其它缓存设计

除过 Eureka Server 端存在缓存外,Eureka Client 也同样存在着缓存机制,Eureka Client 启动时会全量拉取服务列表,启动后每隔 30 秒从 Eureka Server 量获取服务列表信息,并保持在本地缓存中。

Eureka Client 增量拉取失败,或者增量拉取之后对比 hashcode 发现不一致,就会执行全量拉取,这样避免了网络某时段分片带来的问题,同样会更新到本地缓存。

同时对于服务调用,如果涉及到 ribbon 负载均衡,那么 ribbon 对于这个实例列表也有自己的缓存,这个缓存定时(默认30秒)从 Eureka Client 的缓存更新。

这么多的缓存机制可能就会造成一些问题,一个服务启动后可能最长需要 90s 才能被其它服务感知到:

1、首先,Eureka Server 维护每 30s 更新的响应缓存

2、Eureka Client 对已经获取到的注册信息也做了 30s 缓存

3、负载均衡组件 Ribbon 也有 30s 缓存

这三个缓存加起来,就有可能导致服务注册最长延迟 90s ,这个需要我们在特殊业务场景中注意其产生的影响。

你可能感兴趣的:(eureka,java,spring)