Spring Cloud Eureka

1. 概述

Spring Cloud针对服务注册与发现,进行了一层抽象,并提供了三种实现:

  • Eureka(支持得最好)
  • Consul
  • Zookeeper

1.1 角色

Eureka 是 Netflix 开源的服务注册发现组件,服务端通过 REST 协议暴露服务,提供应用服务的注册和发现的功能。 所有的Eureka服务都被称为实例(instance)。Eureka服务又分为两类角色:

  • Eureka Server
  • Eureka Client

Eureka-Client又分为Application Provider 和Application Consumer。

  • Application Provider :服务提供者,内嵌 Eureka-Client ,它向 Eureka-Server 注册自身服务、续约、下线等操作。
  • Application Consumer :服务消费者,内嵌 Eureka-Client ,它从 Eureka-Server 获取服务列表,获取方式:全量增量

注意:Application Provider 和 Application Consumer 只是角色,同一个服务即可以是服务的提供者,又可以是服务的消费者。甚至,在注册中心的集群环境下,Eureka Server,即可以Eureka Server,又是Eureka Client。 Eureka Server比较好理解,Eureka Client是他向其它Eureak Server同步消息,它又变成了Eureker Client

1.2 架构

在这里插入图片描述
在这里插入图片描述

  • Eureka Server
    提供服务发现的能力,各个微服务启动时,会向Eureka Server注册信息,比如ip、端口、微服务名称等。 Eureka Server会存储这些信息
  • Eureka Client
    用于简化与Eureka Server的交互微服务启动后,会周期性(默认30S)向Eureka Server发送心跳以续约自己的“租期”,如果Eureka Server在一定时间内(默认90S)没有接收到某个微服务实例的心跳,Eureka Server将注销该实例。

默认情况下,Eureka Server 同时也是 Eureka Client . 多个Eureka Server之间通过复制的方式来实现服务注册表中数据的同步Eureka Client会缓存服务注册表中的信息,两个好处 第一,微服务无需每次都请求查询Eureka Server ,降低Server的压力。 第二,即使Eureka Server所有节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者并完成调用。

各个微服务启动时,会通过 Eureka Client 向 Eureka Server 注册自己,Eureka Server 会存储该服务的信息,即每个微服务的客户端和服务端,都会注册到 Eureka Server,这就衍生出了微服务相互识别的话题。

  • 同步
    每个 Eureka Server 同时也是 Eureka Client(逻辑上的),多个 Eureka Server 之间通过复制的方式完成服务注册表的同步,形成 Eureka 的高可用。
  • 识别
    Eureka Client 会缓存 Eureka Server 中的信息,即使所有 Eureka Server 节点都宕掉,服务消费者仍可使用缓存中的信息找到服务提供者。
  • 续约
    微服务会周期性(默认30s)地向 Eureka Server 发送心跳以Renew(续约)信息(类似于heartbeat)。
  • 续期
    Eureka Server 会定期(默认60s)执行一次失效服务检测功能,它会检查超过一定时间(默认90s)没有Renew的微服务,发现则会注销该微服务节点。

1.2.1 首次注册

  • Eureka Server: 提供服务发现的能力,各个微服务启动时,会向Eureka Server注册信息,比如ip、端口、微服务名称等。 Eureka Server会存储这些信息.
  • 服务消费者根据Eureka service id向Eureka Server获取要访问服务的注册信息,第一次请求会一次性拉取对应Eureka service id的全部服务实例信息。
  • Eureka Server收到请求后,会首先在只读缓存查找,如果找到则直接返回,否则再查找读写缓存,如果找到则将再存入到只读缓存中,然后返回查找结果。
  • 服务消费者将服务实例信息缓存到本地,在使用时根据算法从N个服务中中选取一个服务并向这个服务发送请求。
# 本服务注册到服务器的名称, 此名称就是后面调用服务时的服务标识符,即Eureka Service Id
spring.application.name=app-provider

# 配置Eureka-Server地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:9000/eureka/  
# 是否向Eureka-Server注册自己(默认值true)
eureka.client.register-with-eureka=true
# 是否向Eureka-Server获取所有的注册信息表(默认值:true)
eureka.client.fetch-registry=true

1.2.2 续约/心跳/下线

  • 服务提供者会定时向Eureka Server发送心跳,默认30s
  • Eureka Server收到心跳后,会更新对应的服务实例信息,如果服务的状态有变化则将实例的变化加入到"最近租约变更记录队列"中
  • Eureka Server有个实例过期清理定时器,如果在指定时间内没有收到心跳(默认90s),则认为服务已经下线,会从读写缓存中移除此实例,将并此变化更新“最近租约变更记录队列”。通常建议将存活的最长时间设置为3个心跳
  • 服务消费者也有个定时任务会定时去更新服务实例信息(默认30s),第一次全量拉取服务实例,会读取所有的实例信息; 之后使用增量拉取服务实例信息,Eureka Server根据"最近租约变更记录队列",告诉请求方哪些服务实例有变化,只返回变化的实例信息。客户端根据返回的增量信息更新本地缓存。我们也可以禁用增量获取服务m实例实例,每次使用全量获取服务实例信息
  • 服务实例下线,又分为两种:
    • 主动下线,在服务实例结束前,主动通知Eureka Server,在默认配置下,此时服务消费者最长在30s左右知道此服务已经下线
    • 被动下线,服务进度崩溃/网络异常,此时服务消费者最长在(3次心跳+一次刷新频率30s)(共约120s左右)内知道此服务已经下线
# 是否向Eureka-Server获取所有的注册信息表(默认值:true)
eureka.client.fetch-registry=true
# 从Eureka-Server拉取注册服务实例信息频率(默认值:30秒)
eureka.client.registry-fetch-interval-seconds=30 
# 是否禁用增量获取服务实例注册信息
eureka.disableDelta: false

# 心跳,租约续约频率(默认值:30秒)
eureka.instance.lease-renewal-interval-in-seconds: 30
# Eureka-Server多久没有收到心跳,则表示对应的实例过期(默认值:90秒)
eureka.instance.lease-expiration-duration-in-seconds: : 90s
# Eureka-Server端服务实例租约过期定时任务执行频率(默认值:60秒)
eureka.server.eviction-interval-timer-in-ms: 60s 

1.2.3 缓存

Eureka Server有两个缓存,一个只读缓存,一个是读写缓存。

  • 读写缓存:每次有服务实例状态变化(如注册服务、下线等)只会更新读写缓存
  • 只读缓存永不过期,但是可能存在过期数据。此时为了保证只读数据的准确性,会有个定时器定时同步两个缓存,然后将状态变化的服务实例添加到"最近租约变更记录队列"。执行频率默认30s

为了提高实时性,可以关闭只读缓存。
"最近租约变更记录队列"里的数据也有有效期,默认为180s。

  • 当有注册、状态变更、下线时,会将创建最近租约变更记录加入此队列中
  • 用于注册信息增量获取
  • 后台任务定时顺序扫描队列,当数据最后更新时间超过一定时长后进行移除
# 是否开启只读请求响应缓存。响应缓存 ( ResponseCache ) 机制目前使用两层缓存策略。优先读取只读缓存,读取不到后读取固定过期的读写缓存。
eureka.server.use-read-only-response-cache: true
# 只读缓存更新频率,单位:毫秒。只读缓存定时更新任务只更新读取过请求 ,因此虽然永不过期,也会存在读取不到的情况。
eureka.server.response-cache-update-interval-ms: 30s
# 读写缓存写入后过期时间,单位:秒。
eureka.server.response-cache-auto-expiration-in-seconds: 180s
# 移除队列里过期的租约变更记录的定时任务执行频率,单位:毫秒。默认值 :30 * 1000 毫秒。
eureka.server.delta-retention-timer-interval-in-ms: 30s
# 租约变更记录过期时长,单位:毫秒。默认值 : 3 * 60 * 1000 毫秒。
eureka.server.retention-time-in-m-s-in-delta-queue: 180s

1.2.4 自我保护机制

概念

  • Renews threshold: Eureka Server 期望每分钟最少收到服务实例心跳的总数
    Renews threshold = 当前注册的服务实例数 x 2 * 自我保护系数( eureka.renewalPercentThreshold )
    之所以 x 2,是因为Eureka 硬编码认为每个实例每 30 秒 1 个心跳,即1分钟2个心跳
  • Renews (last min): Eureka Server 最近 1 分钟收到服务实例心跳的总数

当启动自我保护时,如果Eureka Server最近1分钟收到服务实心跳的次数小于阈值(即预期的最小值),则会触发自我保护模式,此时Eureka Server此时会认为这是网络问题,它不会注销任何过期的实例。等到最近收到心跳的次数大于阈值后,则Eureka Server退出自我保护模式。

刚开始接触spring cloud时,碰到这样的情况:服务提供者明明已经挂掉了,但是服务消息方要过好久(如30s)才知道。更糟糕的是服务提供者进度奔溃,没来的及发送下线请求到注册中心,则服务消费者可能需要120s时间才知道。面对这样的情况,我们总是尝试将心跳频率、刷新频率、注册信息在注册中心的生存时间等配置改到最小,以达到服务提供方一挂掉,服务调用方书上马上知道。但是这个方案有缺陷,中间的时差总时存在,无法完全消除。而且频繁的心跳会增加网络和服务的不必要的性能消耗,如果网络有抖动,则有可能出现误报。 不推荐这么修改的一个重要原因是Eureka 是AP系统,不是CP系统,为了可用性,它不保证实时一致性,它只保证最终一致性。还有一个原因是Eureka中有一部分硬编码,默认认为每分钟每个服务实例的心跳是2次,此值用于计算Eureka每分钟理论上应该收到的心跳的个数,此值会用于计算Eureka开启自我保护。

在生产环境,推荐开启,避免网络抖动对服务的影响。有这样的极端场景,比如注册中心单独部署在一个集群中,所有服务向其注册服务完毕后,突然注册中心所在的集群网络和其它服务网络断开,但是服务实例之间是可达的,整个服务可以正常工作。此时如果没有开启自我保护机制,经过Ns后,注册中心清理所有的注册服务,然后网络恢复了,那么其它服务进行更新时,会认为其它服务已经掉线,则从本地移除所有的服务请求,则整个服务变的不可用 关于自我保护阈值,需要设置合理值。比如我们将全部服务均匀地部署在2台服务器,则需要设置阈值小于50%,否则如果出来一台服务器坏掉了,则此时就要求注册中心移除一般的服务注册中心是合理的,不应该启动自我保护

# 配置在Eureka Server端的配置文件 
# 是否开启自我保护模式。
eureka.server.enable-self-preservation: false
# 开启自我保护模式比例,超过该比例后开启自我保护模式。
eureka.server.renewal-percent-threshold: 0.85
# 自我保护模式比例更新定时任务执行频率。
eureka.server.renewal-threshold-update-interval-ms: 900s

1.2.5 执行清理过期租约逻辑

计算最大允许清理租约的数量,后计算允许清理租约的数量 本次最大允许清理租约数量 = 当前注册的应用实例数 -(当前注册的应用实例数 * renewalPercentThreshold) 实际清理租约数量 = Math.min(所有过期注册信息个数,最大允许清理租约数量) 随机清理过期的租约 如果同时过期大量注册信息,则需要好久才能将注册信息清理完毕。
renewalPercentThreshold = 0.85 默认配置,服务实例会分批逐步过期,是否开启自我保护的差别,在于是否执行清理过期租约逻辑。如果想关闭分批逐步过期,设置 renewalPercentThreshold = 0 。

1.2.6 Eureka集群

为了提高高可用,Eureka是提供集群功能。不仅Eureka Client端可以集群,做为注册中心的Eureka Server也可以集群 。多个Eureka Client进程向注册中心注册的Eureka Service Id相同,则被认为是一个集群。多个Eureka Server相互注册,组成一个集群,每个Eureka Server注册的消息发生变化时,会各个服务之间定时同步,中间过程每个服务的数据可能不一致,但是最终会变得一致 Eureka Server集群之间的状态是采用异步方式同步的,所以不保证节点间的状态一定是一致的,不过基本能保证最终状态是一致的。

1.2.6.1 新Eureka Server节点加入集群后的影响

当有新的节点加入到集群中,会对现在Eureka Server和Eureka Client有什么影响以及他们如何发现新增的Eureka Server节点:

  • 新增的Eureka Server:在Eureka Server重启或新的Eureka Server节点加进来的,它会从集群里其它节点获取所有的实例注册信息。如果获取同步失败,它会在一定时间(此值由决定决定)内拒绝服务。
  • 已有Eureka Server如何发现新的Eureka Server:
    • 已有的Eureka Server:在运行过程中,Eureka Server之间会定时同步实例的注册信息。这样即使新的Application Service只向集群中一台注册服务,则经过一段时间会集群中所有的Eureka Server都会有这个实例的信息。那么Eureka Server节点之间如何相互发现,各个节点之间定时(时间由eureka.server.peer-eureka-nodes-update-interval-ms决定)更新节点信息,进行相互发现。
    • Service Consumer:Service Consumer刚启动时,它会从配置文件读取Eureka Server的地址信息。当集群中新增一个Eureka Server中时,那么Service Provider如何发现这个Eureka Server?Service Consumer会定时(此值由eureka.client.eureka-service-url-poll-interval-seconds决定)调用Eureka Server集群接口,获取所有的Eureka Server信息的并更新本地配置。
# Eureka-Server启动时,从远程Eureka-Server读取不到注册信息时,多长时间不允许 Eureka-Client访问(默认值:5分钟)
eureka.server.wait-time-in-ms-when-sync-empty: 5m
# Eureka-Server集群节点更新频率,单位:毫秒。
eureka.server.peer-eureka-nodes-update-interval-ms:
# 初始化实例信息到Eureka服务端的间隔时间,单位为秒
eureka.client.initial-instance-info-replication-interval-seconds: 40s
# 更新实例信息的变化到Eureka服务端的间隔时间,单位为秒
eureka.client.instance-info-replication-interval-seconds: 30s

2. 配置

2.1 pom.xml

2.1.1 server

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.yanggle</groupId>
    <artifactId>springcloud-eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springcloud-eureka-server</name>
    <description>Spring Cloud Eureka Server</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

2.1.2 client

<dependency>
	<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2.2 Server

2.2.1 application-server.yml(单点)

 spring:
  application:
    # 服务名称
    name: eureka-server

  # 开启安全控制
  security:
    user:
      # 用户名
      name: hello
      # 用户密码
      password: world

server:
  # 服务端口
  port: 9000

# Eureka配置
eureka:

  instance:
    hostname: eureka-server-${server.port}.com

    # 设置注册中心微服务名,默认值:机器主机名:应用名称:应用端口
    instance-id: eureka-server-${server.port}

    # 是否微服务调用地址为IP优先(缺省为false)
    prefer-ip-address: false

    # 客户端向服务器发送心跳的频率(缺省值30s)
    lease-renewal-interval-in-seconds: 30

    # 服务器在收到最后一次心跳之后等待的持续时间(缺省值90s),然后才能从其注册表中删除实例
    lease-expiration-duration-in-seconds: 90

  client:
    # 是否将自己作为客户端注册到注册中心(缺省值true), 单点设置为false
    register-with-eureka: false

    # 是否从注册中心获取注册信息(缺省值true), 单点设置为false
    fetch-registry: false

    # 从注册中心获取注册信息的间隔时间(缺省值30s)
    # 测试环境,可以适当提高Client端拉取Server注册信息的频率
    registry-fetch-interval-seconds: 30

    # 注册地址
    #service-url:
      # 默认注册分区地址
      # defaultZone: http://localhost:${server.port}/eureka/

  server:
    # 是否开启自我保护模式(缺省值true), 开发阶段建议设置为false
    enable-self-preservation: true

    # 调度程序EvictionTask会比较这两个值并确定系统是否处于自我保存模式。
    # 这个调度程序以eviction-interval-timer-in-ms(缺省值60*1000ms)
    # 定义的时间频率运行并删除过期的实例,但它会在删除之前检查系统是否已
    # 达到自我保护模式(通过比较实际和预期的心跳数)。
    eviction-interval-timer-in-ms: 10000

    # 用于计算每分钟的预期心跳(缺省值0.85)
    renewal-percent-threshold: 0.85

2.2.2 application-server.yml(集群)

config:
  # 集群地址列表
  serverA: eureka-server-9001.com #127.0.0.1
  serverB: eureka-server-9002.com #127.0.0.1
  serverC: eureka-server-9003.com #127.0.0.1

  # 集群端口列表
  portA: 9001
  portB: 9002
  portC: 9003

  ssl:
    client:
      key-store: client.p12
      key-store-password: client
    server:
      key-store: classpath:server.p12
      key-alias: server
      key-store-type: PKCS12
      key-store-password: server
      
spring:
  profiles: https-eurekaserver-A
  application:
    # 服务名称
    name: eureka-server

  # 开启安全控制
  security:
    user:
      # 用户名
      name: hello
      # 用户密码
      password: world

server:
  # 服务端口
  port: ${config.portA}
  # SSL配置
  ssl:
    # 是否开启SSL认证
    enabled: true
    # 证书存放路径
    key-store: ${config.ssl.server.key-store}
    # 证书别名
    key-alias: ${config.ssl.server.key-alias}
    # 证书存储类型
    key-store-type: ${config.ssl.server.key-store-type}
    # 证书密码
    key-store-password: ${config.ssl.server.key-store-password}

# Eureka配置
eureka:

  instance:
    # 主机名称
    hostname: eureka-server-${server.port}.com

    # 是否微服务调用地址为IP优先(缺省为false)
    prefer-ip-address: false

    # 设置注册中心微服务名(默认为:机器主机名:应用名称:应用端口)
    instance-id: eureka-server-${server.port}

    # Client向Server发送心跳的频率(缺省值30s)
    lease-renewal-interval-in-seconds: 30

    # 服务器在收到最后一次心跳之后等待的持续时间(缺省值90s),然后才能从其注册表中删除实例
    lease-expiration-duration-in-seconds: 90

    # 安全端口
    secure-port: ${server.port}

    # 是否启用安全端口
    secure-port-enabled: true

    # 是否启用非安全端口
    non-secure-port-enabled: false

  client:
    # 是否将自己作为客户端注册到注册中心(缺省值true)
    register-with-eureka: true

    # 是否从注册中心获取注册信息(缺省值true)
    fetch-registry: true

    # 从注册中心获取注册信息的间隔时间(缺省值30s)
    # 测试环境,可以适当提高Client端拉取Server注册信息的频率
    registry-fetch-interval-seconds: 30

    # 注册地址
    service-url:
      # 默认注册分区地址
      defaultZone: "https://${config.serverB}:${config.portB}/eureka/,\
                    https://${config.serverC}:${config.portC}/eureka/"

  server:
    # 是否开启自我保护模式(缺省值true), 开发阶段建议设置为false
    enable-self-preservation: false

    # 调度程序EvictionTask会比较这两个值并确定系统是否处于自我保存模式。
    # 这个调度程序以eviction-interval-timer-in-ms(缺省值60*1000ms)
    # 定义的时间频率运行并删除过期的实例,但它会在删除之前检查系统是否已
    # 达到自我保护模式(通过比较实际和预期的心跳)。
    eviction-interval-timer-in-ms: 10000

    # 用于计算每分钟的预期心跳(缺省值0.85)
    renewal-percent-threshold: 0.85

    # 预期心跳调度程序以此频率运行,计算每分钟的预期心跳(缺省为15 * 60 * 1000)
    renewal-threshold-update-interval-ms: 900000

    # 同步为空时,等待时间
    wait-time-in-ms-when-sync-empty: 0

    # 设置read Write CacheMap的expire After Write参数,指定写入多长时间后过期
    # 有效防止的问题是:应用实例下线时有告知Eureka server下线,但是由于Eureka server
    # 的REST API有response cache,因此需要等待缓存过期才能更新
    response-cache-auto-expiration-in-seconds: 60

    # 是否开启只读缓存(缺省值true)
    use-read-only-response-cache: true

2.3 Client

2.3.1 application-client.yml(非安全)

spring:
  application:
    # 服务名称
    name: app-provider

server:
  # 服务端口
  port: 10002

# Eureka配置
eureka:
  instance:
    # 使用IP注册到注册中心实例化(缺省值false)
    prefer-ip-address: false

    # 设置机器IP
    ip-address: 127.0.0.1
    
    # 设置注册中心微服务名,默认为:机器主机名:应用名称:应用端口
    instance-id: ${eureka.instance.ip-address}:${spring.application.name}:${server.port}

    #客户端向服务器发送心跳的频率
    lease-renewal-interval-in-seconds: 30

  client:
    # 是否将自己作为客户端注册到注册中心(缺省值true)
    register-with-eureka: true

    # 是否从注册中心获取注册信息(缺省值true)
    fetch-registry: true

    # 从注册中心获取注册信息的间隔时间(缺省值30s)
    # 测试环境,可以适当提高Client端拉取Server注册信息的频率
    registry-fetch-interval-seconds: 30

    service-url:
      defaultZone: http://localhost:9001/eureka/

2.3.2 application-client.yml(安全)

config:
  ssl:
    client:
      key-store: client.p12
      key-store-password: client

spring:
  application:
    # 服务名称
    name: app-consumer
  profiles:
    active: dev

server:
  # 服务端口
  port: 10002

# Eureka配置
eureka:
  instance:
    # 是否微服务调用地址为IP优先(缺省值false)
    prefer-ip-address: false

    # 设置机器IP
    ip-address: 127.0.0.1

    # 设置注册中心微服务名,默认值:机器主机名:应用名称:应用端口
    instance-id: ${eureka.instance.ip-address}:${spring.application.name}:${server.port}

    # 客户端向服务器发送心跳的频率(缺省值30s)
    lease-renewal-interval-in-seconds: 30

    # 安全端口
    secure-port: ${server.port}

    # 是否启用安全端口
    secure-port-enabled: true

    # 是否启用非安全端口
    non-secure-port-enabled: false

  client:
    # 是否将自己作为客户端注册到注册中心(缺省值true)
    register-with-eureka: true

    # 是否从注册中心获取注册信息(缺省值true)
    fetch-registry: true

    # 从注册中心获取注册信息的间隔时间(缺省值30s)
    # 测试环境,可以适当提高Client端拉取Server注册信息的频率
    registry-fetch-interval-seconds: 30

    service-url:
      defaultZone: https://localhost:9001/eureka/

3. Eureka配置

3.1 配置

  • eureka.instance.*
    Eureka实例注册配置项,这些是无论Eureka是做Server,Client都需要的公共配置项。对应的配置类为org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean。
  • eureka.server.*
    Eureka做为Eureka Server才需要配置的选项,即使用eureka做注册中心时才需要配置这个选项。对应的配置类为org.springframework.cloud.netflix.eureka.EurekaClientConfigBean。
  • eureka.client.*
    Eureka做为Eureka Client才需要配置的选项,即服务需要向注册中心注册或使用注册中心中的服务时才需要配置这些选项。对应的配置类为org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean

Eureka包含四个部分的配置

  • instance:当前Eureka Instance实例信息配置
    在这里插入图片描述
  • client:Eureka Client客户端特性配置
    在这里插入图片描述
  • server:Eureka Server注册中心特性配置
    在这里插入图片描述
  • dashboard:Eureka Server注册中心仪表盘配置

参数详细说明

  • eureka.client.registry-fetch-interval-seconds
    表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
  • eureka.instance.lease-expiration-duration-in-seconds
    leaseExpirationDurationInSeconds,表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance。默认为90秒,如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了。如果该值设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉。该值至少应该大于leaseRenewalIntervalInSeconds
  • eureka.instance.lease-renewal-interval-in-seconds
    leaseRenewalIntervalInSeconds,表示eureka client发送心跳给server端的频率。如果在leaseExpirationDurationInSeconds后,server端没有收到client的心跳,则将摘除该instance。除此之外,如果该instance实现了HealthCheckCallback,并决定让自己unavailable的话,则该instance也不会接收到流量。默认30秒
  • eureka.server.enable-self-preservation
    是否开启自我保护模式,默认为true。
    默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。
    Eureka通过“自我保护模式”来解决这个问题——当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
    综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
  • eureka.server.eviction-interval-timer-in-ms
    eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒

常见问题

unavailable-replicas

Eureka高可用部署,启动多个注册中心后,节点均出现在unavailable-replicas,查阅各类资料测试,提供方案:

  • 1.eureka.client.serviceUrl.defaultZone配置项的地址,不能使用localhost,要使用域名,DNS解析请自行配置。

  • 2.spring.application.name要一致(这个个人测试默认不配也可以)

  • 3.如下两个参数需配为true(个人测试默认不配也可行)

eureka:
  client:
    register-with-eureka: true
    fetch-registry:  true
  • 4.配置eureka.instance.hostname(好像看到过正常eureka会自动拉取设备host,但各节点在同一机器下时请务必添加,注意各节点配置自己节点的host)
eureka:
  instance:
    hostname:host1
  • 5.千折腾万折腾还是不好使的时候,请去掉下面这个参数或者改为false(神坑),未找到官方原因。。
eureka:
  instance:
    prefer-ip-address: true

prefer-ip-address: true为不使用主机名来定义注册中心的地址,而使用IP地址的形式,而defaultZone中是以域名的方式向注册中心注册的(测试了下使用IP注册到备份节点不可识别),最终导致分片节点不能识别匹配(IP地址与域名),而认为分片均处于不可达状态。

Eurake的自我保护机制

从CAP定理角度看,Eureka是一个AP系统,以高可用性为主,而zookeeper则是CP,以高一致性为主,所以如果使用ZK在服务发现和注册方面,可用服务信息虽然很及时,但是会出现不可用情形,造成无法克服的生产事故。Eureka则是在出现网络分区期间(无法通讯,出现需要重试的情况就是由于网络已经分区了),注册表中的信息在服务器之间不一致,自我保护(self-preservation)功能旨在最大限度地减少这种不一致性。

  • 自我保护
    自我保护是一种功能,当Eureka服务器超过某个时间阈值没有收到心跳(来自同级和客户端微服务)时,它会停止失效注册表中的实例,因为按一般常理,如果没有收到某个实例的心跳,应该失效那个实例的心跳。
    在这里插入图片描述
    假设所有微服务都处于健康状态并在Eureka服务器1上注册。如果您想知为啥只注册到server1原因 :因为注册时配置导致,心跳只发送到service-url列表中配置的第一台服务器。即

    eureka.client.service-url.defaultZone = server1,server2

    Eureka服务器与相邻对等方之间者采取复制注册表信息方式,并且这两个服务器注册表都指示所有微服务实例都处于UP状态。上图还说明实例2从Eureka注册表中发现实例4之后还调用了它。

  • 遇到网络分区
    假设发生网络分区,系统转换到以下状态。
    在这里插入图片描述
    上图中由于网络分区实例4和5与服务器失去连接,但是实例2仍然连接到实例4,这时Eureka服务器会按照常理从注册表中删除实例4和5,因为它不再接收到这两个实例心跳,这时它也进入观察期,如果在一段观察期内又失去超过15%的心跳,它就会进入自我保护模式。

    从现在开始,即使剩余的实例也出现故障,Eureka服务器也不会删除注册表中的这些实例了。
    在这里插入图片描述
    上图中实例3已关闭,但它在服务器注册表中保持活动状态。不过,服务器还是能够接受新的注册实例。

  • 自我保护背后的理由
    注意:没有接受心跳意味着两种可能:网络故障;原实例真的下线了。如何判断这两个故障其实很难,这是分布式系统典型拜占庭将军问题,解决方式一般两个:接受现状或重试。接受现状就是允许网络故障的两端数据不一致,根据CAP定理,就是选择了AP,而如果不断重试,则是为了让两端数据尽快一致起来,实际就是选择 了CP,Dubbo的源码中使用Netty长连接以及ZoopKeeper本身的都是CA,这些都是符合人的自觉,但是Eureka选择了接受现状,选择了AP,对网络分区的容忍性提高,可用性也提高了。

    具体来说,由于以下两个原因,自我保护功能可以证明是合理的。

    • 没有接收心跳的服务器可能是由于网络分区,接受分区,保留暂时的不一致,提高可用性。
    • 即使服务器和某些客户端之间的连接丢失,客户端也可能相互连接。即,在网络分区期间,实例2还是具有与实例4的连接,如上图所示。
      自我保护的默认配置

    下面列出的是可以直接或间接影响自我保护行为的配置。

    eureka.instance.lease-renewal-interval-in-seconds = 30
    表示客户端向服务器发送心跳的频率,不建议更改此值,因为自我保护假设始终以30秒的间隔接收心跳。

    eureka.instance.lease-expiration-duration-in-seconds = 90
    表示服务器在收到最后一次心跳之后等待的持续时间,然后才能从其注册表中删除实例。该值应大于lease-renewal-interval-in-seconds。此值设置得长会影响每分钟实际心跳的精确性,在下一节中会有描述的,因为注册表的活力取决于这个值。将此值设置得太小可能会使系统无法容忍临时网络故障。

    eureka.server.eviction-interval-timer-in-ms = 60 * 1000
    调度程序以此频率运行,如果实例的租约按照这种配置时间发生过期现象,这将从注册表中删除。将此值设置得太长会延迟系统进入自保护模式。

    eureka.server.renewal-percent-threshold = 0.85
    此值用于计算每分钟的预期心跳。

    eureka.server.renewal-threshold-update-interval-ms = 15 * 60 * 1000
    调度程序以此频率运行,计算每分钟的预期心跳。

    eureka.server.enable-self-preservation = true
    最后但并非最不重要的是,如果需要,可以禁用自我保护。

  • 理解配置
    如果actual number of heartbeats in last minute小于expected number of heartbeats per minute,Eureka服务器就会进入自我保护模式,

    预期的每分钟心跳次数
    我们可以看到计算每分钟心跳预期阈值的方法。Netflix代码假定此计算始终以30秒的间隔接收心跳。

    假设某个时间点的已注册应用程序实例数为N,配置renewal-percent-threshold为0.85。

    一个实例预期的心跳数/ 分钟= 2
    N个实例预期的心跳数/分钟= 2 * N.
    预期最小心跳/ 分钟 = 2 * N * 0.85
    由于N是变量,因此默认情况下每15分钟计算一次2 * N * 0.85结果。或基于renewal-threshold-update-interval-ms的设置时间计算。

    最后一分钟的实际心跳次数
    这是由调度程序计算的,调度程序以一分钟的频率运行。

    同样如上所述,两个调度器独立运行以便计算实际和预期的心跳数。有另一个调度程序会比较这两个值并确定系统是否处于自我保存模式 - 也就是EvictionTask。这个调度程序以eviction-interval-timer-in-ms定义的时间频率运行并删除过期的实例,但它会在删除之前检查系统是否已达到自我保护模式(通过比较实际和预期的心跳)。

    每次启动时,eureka仪表板也会进行此比较,会显示消息“INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE”。

  • 结论
    在大多数情况下,eureka会误报微服务实例下线是因为糟糕的网络分区。
    自我保护永不失效,也就是一直保持着,直到并且除非关闭微服务(或解决网络故障)。
    如果启用了自我保护,我们无法微调实例心跳间隔,因为自我保护假定心跳以30秒的间隔接收。
    除非你的环境中常有网络故障,否则将其关闭(即使大多数人建议将其保留),由于本地调试很容易触发保护机制,出现警告信息:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE, 可设置eureka.server.enable-self-preservation=false参数来关闭保护机制。

Eureka心跳健康检查机制

运行阶段执行健康检查的目的是为了从Eureka服务器注册表中识别并删除不可访问的微服务,Eureka 服务器并不是向客户端发送心跳请求,而是反过来,Eureka 客户端将心跳发送到Eureka服务器,让服务器了解其状态。

这些心跳机制就需要在微服务嵌入一个客户端,用来发送心跳,但是客户端本身必须确定其健康状态,而且Eureka服务器必须为客户端公开一些REST操作以让其发布心跳。

Eureka服务器向客户端公开下面资源以让其发送心跳:

PUT /eureka/apps/{app id}/{instance id}?status={status}

{instance id}采用 hostname:app id:port,其中app id代表标识唯一的Eureka客户端实例,Eureka服务器会识别一些状态数值:UP; DOWN; STARTING; OUT_OF_SERVICE; UNKNOWN.

客户端发送心跳时的URL如下:

PUT /eureka/apps/ORDER-SERVICE/localhost:order-service:8886?status=UP

Eureka服务器收到心跳请求后,会续订该实例的租约。如果是第一个心跳,则Eureka服务器以404响应,之后客户端必须首先发送注册请求。

此外, Eureka服务器公开以下操作以允许健康状态的修改和删除:

PUT /eureka/apps/{app id}/{instance id}/status?value={status}
DELETE /eureka/apps/{app id}/{instance id}/status

修改操作(即PUT上面的操作)是用于手动获取健康的实例OUT_OF_SERVICE时操作,或者使用Asgard等管理工具 (暂时禁止某些实例的流量)时操作。

这种修改操作对于“红/黑”部署非常有用,在这种情况下,你可以在一段时间内运行较旧版本的微服务(如果新版本不稳定,则可以轻松回滚到旧版本)。完成新版本的部署并且新版本开始为请求提供服务后,可以让旧版本OUT_OF_SERVICE(但不会让他们停止)暂停提供请求服务。即

PUT /eureka/apps/ORDER-SERVICE/localhost:order-service:8886/statusvalue=OUT_OF_SERVICE

上面修改的状态也可以被丢弃,我们可以指示让Eureka服务器开始遵守实例本身发布的状态,如下所示:

DELETE /eureka/apps/ORDER-SERVICE/localhost:order-service:8886/status

当您发现微服务的新版本不稳定并且您希望获得旧版本(即已经被打上OUT_OF_SERVICE标记的版本)以开始提供请求时,上述办法非常有用。

Eureka客户端自我诊断

Eureka客户端(或服务器)都是不调用/health端点来确定实例的健康状态,Eureka实例的运行状况由HealthCheckHandler实现确定,只要应用程序正在运行,默认情况下HealthCheckHandler始终会通知应用程序处于某种 UP 状态。

Eureka允许通过EurekaClient#registerHealthCheck()API 插入自定义的HealthCheckHandlers,如果在Spring Cloud设置了以下属性,则会注册一个新的Spring自己的处理程序EurekaHealthCheckHandler:

eureka.client.healthcheck.enabled=true

该EurekaHealthCheckHandler汇总了多个健康指标,如健康状况:

DiskSpaceHealthIndicator
RefreshScopeHealthIndicator
HystrixHealthIndicator
它将这些状态会映射到Eureka支持的状态之一,之后被映射后的状态将通过心跳传播到Eureka服务器。

Eureka客户端健康端点

Eureka客户端在向服务器注册时会在其POST的内容中加入healthCheckUrl ,这个healthCheckUrl的值是由以下实例属性计算得出:
eureka.instance.health-check-url
eureka.instance.health-check-url-path
.health-check-url-path的默认值是 /health,这是Springboot默认专门用于检查健康的actuator端点,除非.heath-check-url被专门配置了。

如果实现自定义健康状况端点或更改默认健康检查路径,则应配置这些属性:

  • 如果更改默认健康端点;
endpoints.health.path=/new-heath
#either relative path
eureka.instance.health-check-url-path=${endpoints.health.path}
#or absolute path
eureka.instance.health-check-url=http://${eureka.hostname}:${server.port}/${endpoints.health.path}
  • 如果你引入一个 management.context-path
management.context-path=/admin
#either relative path
eureka.instance.health-check-url-path=${management.context-path}/health
#or absolute path
eureka.instance.health-check-url=http://${eureka.hostname}:${server.port}/${management.context-path}/health

健康状况的试验

Eureka服务器并不关心客户端的状态 - 它只记录客户端状态,当有人查询其注册表时,它也会发布客户的健康状况。即

GET /eureka/apps/ORDER-SERVICE

<application>
   <name>DISCOVERY-EUREKA-CLIENT</name>
   <instance>
      <instanceId>localhost:discovery-eureka-client:8886</instanceId>
      <ipAddr>192.168.1.6</ipAddr>
      <port>8886</port>
      <status>UP</status>
      <overriddenstatus>UP</overriddenstatus>
      <healthCheckUrl>http://localhost:8886/health</healthCheckUrl>
      ...
      ...
   </instance>
</application>

这个响应有三个与健康有关的重要信息: status 、overridenstatus和healthCheckUrl

status 是Eureka实例本身发布的健康状况。
overriddenstatus 是手动或通过工具强制执行的健康状态。比如PUT /eureka/apps/{app id}/instance id}/status?value={status}操作用于修改发布的状态,那么status和overriddenstatus都将变更为新的状态。
healthCheckUrl 是客户端公开GET其健康状态的端点。
其他工具则可以利用这些健康信息:

客户端负载平衡器(如Ribbon)可以做出负载平衡决策 : Ribbon 读取 status 属性并仅使用具有UP 负载平衡状态的实例 。但是,Ribbon不会调用 healthCheckUrl, 而是依赖于注册表中可用的、已发布实例状态。

像Asgard这样的部署工具可以做出部署决策 - 在滚动部署期间,Asgard部署了一个新版本的微服务,在 部署其余实例之前一直等待当前实例转换到UP 状态。但是,status Asgard 不是依赖于注册表中可用的实例状态(即属性),而是 通过调用 healthCheckUrl来了解实例状态 ,这可能是因为status 属性的值可能会变得陈旧(因为它取决于下一节中描述的几个因素),但在这种情况下,实时健康状态很重要,以避免部署延迟。

健康状况的准确性

由于下面列出的原因,Eureka服务器注册表健康状况的并不总是准确的。

CAP中的AP - 由于Eureka在CAP定理方面定位于高度可用的系统,因此在网络分区期间,集群Eureka服务器之间的信息可能不一致。
服务器响应缓存 - Eureka服务器维护一个响应缓存,默认情况下每30秒更新一次。因此,实际上在 GET /eureka/apps/{app id}/ 响应中出现 UP 的实例可能已经DOWN 了 。
定期调度心跳 - 由于客户端默认情况下每30秒发送一次心跳,因此服务器注册表中实例的运行状况可能不准确。
自我保护 - 当Eureka服务器没有收到超过某个阈值的心跳时,它会停止失效注册表中的客户端,从而会使注册表不准确。
因此,客户端应遵循适当的故障转移机制来补充这种不准确性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值