Eureka 目前的状态:Eureka 目前 1.x 版本还在更新,但是应该不会更新新的功能了,只是对现有功能进行维护,升级并兼容所需的依赖。 Eureka 2.x 已经胎死腹中了。但是,这也不代表 Eureka 就是不能用了。如果你需要一个简便易于部署的注册中心,Eureka 还是一个很好的选择。云服务环境中,基本上所有实例地址和微服务名称都在不断变化,也并不太需要 Eureka 所缺少的持久化特性。当你的集群属于中小规模的时候(节点小于 1000 个), Eureka 依然是一个不错的选择。当你的集群很大的时候,Eureka 的同步机制可能就限制了他的表现。

Eureka 的设计

Eureka 的设计比较小巧,没有复杂的同步机制,也没有复杂的持久化机制,集群关系只是简单的将收到的客户端请求转发到集群内的其他 Eureka 实例。Eureka 本身也只有注册中心的功能,不像其他种类的注册中心那样,将注册中心和配置中心合在一起,例如 Consul 和 nacos。

Eureka 的交互流程如下

Eureka 的设计-LMLPHP

首先,Service A 通过 Eureka Client 发送注册请求(Register)到同一可用区的 Eureka Server 1。之后通过发送心跳请求(Renew)到这个 Eureka Server 1. Eureka Server 1 收到这些请求的时候,会处理这些请求并将这些请求转发到其他的集群内的 Eureka Server 2 和 Eureka Server 3. Eureka Server 2 和 Eureka Server 3 不会再转发收到的 Eureka Server 1 转发过来的请求。然后,Service B 还有 Service C 通过 Eureka 获取到了 Service A 的位置,最后调用了 Service A。

对于本地没有查询到的微服务,Eureka Server 还会从远程 Region 的 Eureka Server 去获取,例如这里对于 Service D,本地没有查到,Eureka Server 会返回远程 Region 的 Service D 的实例。由于本地有 Service A,所以肯定不会返回远程 Region 的 Service A 的实例。并且,本地是定时拉取的远程 Region 的 Service 列表,并不是每次查询的时候现查询的。

一般的,微服务之间的互相调用,并不经过 Eureka,也不会涉及到 Eureka 客户端了,而是通过负载均衡器调用,这个我们后面就会提到。

Eureka 相关概念

这里我们忽略所有的 AWS 相关的术语以及配置还有相关逻辑处理。

Eureka 中的术语:

  1. Eureka 实例:每个注册到 Eureka 上面的实例就是 Eureka 实例
  2. Eureka 实例状态:包括 UP(可以处理请求),DOWN(健康检查失败,不能正常处理请求),STARTING(启动中,不能处理请求),OUT_OF_SERVICE(人为下线,暂时不处理请求),UNKNOWN(未知状态)。
  3. Eureka 服务器:作为注册中心运行,主要提供实例管理功能(处理实例注册(register)请求、处理实例注销(cancel)请求、处理实例心跳(renew)请求、内部处理实例过期(evict))、实例查询功能(各种查询实例信息的接口,例如通过 AppName 获取实例列表,通过实例 id 获取实例信息等等)
  4. Eureka 服务器集群:Eureka 服务器的集群,每个 Eureka 服务器都配置了区域以及可用区,Eureka 服务器收到的客户端请求会转发到同一区域内的其他 Eureka 服务器,可以配置优先发到同一可用区的 Eureka 服务器。非同一区域内 Eureka 服务器,通过定时拉取的方式进行同步。
  5. Eureka 客户端:请求 Eureka 服务器的客户端。封装发送实例注册(register)请求、实例注销(cancel)请求和实例心跳(renew)请求。
  6. VIP(或者是 Virtual Hostname): Eureka 中可以通过两种方式获取实例,一个是通过服务名称,另一种是通过 VIP。每个实例都有服务名称,以及 VIP。Eureka 服务器中的索引方式是以服务名称为 key 的索引,我们也可以通过遍历所有实例信息的方式通过 VIP 字符串匹配获取相关的实例。在 Spring Cloud 体系中,一个实例的 VIP、SVIP(其实就是 Secure VIP,即 https 的地址)以及服务名称都是 spring.application.name 指定的服务名称。

Eureka 相关配置

  1. Eureka 实例配置:Eureka 实例,每个注册到 Eureka 上面的实例就是 Eureka 实例。Eureka 实例包含以下元素,以及相关配置:
  2. 基本信息:包括 IP,端口等访问这个 Eureka 实例所需的信息:
eureka:
  instance:
    #一般不用我们自己设置,EurekaInstanceConfigBean 的构造器会通过 InetUtils 获取 ip 地址
    #ip-address:
    #一般不用我们自己设置,EurekaInstanceConfigBean 的构造器会通过 InetUtils 获取 hostname
    #hostname:
    #注册到 eureka 上面供其他实例访问的地址使用 ip 进行注册,其他实例会通过 ip 进行访问
    prefer-ip-address: true
    #不用设置 non-secure-port,自动使用 server.port 作为 non-secure-port
    #non-secure-port:
    #如果 secure-port-enabled 是 true,则会自动使用 server.port 作为 secure-port;我们一般内部调用不用 ssl,所以不需要配置 secure-port
    #secure-port:
    #默认是启用 non-secure-port 的
    non-secure-port-enabled: true
    #默认是不启用 secure-port 的,我们一般内部调用不用 ssl
    secure-port-enabled: false
    #个性化的实例id,包括 ip:微服务名称:端口
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
    # app名称,不填写在 Spring-cloud-netflix 体系下默认就是 spring.application.name
    appname: ${spring.application.name}
    #app组名称归类用的,目前也没什么用
    app-group-name: common
    #实例命名空间,目前也没什么用
    namespace: public
  1. 基本链接信息:包括首页路径地址以及健康检查路径地址:
eureka:
  instance:
    # 健康检查地址,默认是 /actuator/health
    health-check-url-path: /actuator/health
    # 实例状态地址,默认是 /actuator/info
    status-page-url-path: /actuator/info
    # 首页地址,默认是 /
    home-page-url-path: /
  1. 实例注册行为,即实例注册后的行为,以及心跳间隔等配置:
eureka:
  instance:
	  # 服务过期时间配置,超过这个时间没有接收到心跳EurekaServer就会将这个实例剔除
    # 注意,EurekaServer一定要设置eureka.server.eviction-interval-timer-in-ms否则这个配置无效
    # 这个配置一般为服务刷新时间配置的三倍
    # 默认90s
    lease-expiration-duration-in-seconds: 15
    #服务刷新时间配置,每隔这个时间会主动心跳一次
    #默认30s
    lease-renewal-interval-in-seconds: 5
    registry:
      #请参考 wait-time-in-ms-when-sync-empty 配置说明
      default-open-for-traffic-count: 1
      #初始期望发送心跳请求的实例个数,默认为1,在有新实例注册的时候,会 +1,有注销的时候会 -1,初始默认为 1 一般因为自己也注册到 eureka 上
      expected-number-of-clients-sending-renews: 1
    #实例注册后是否立刻开始服务,默认为 false,一般注册后还需要做一些操作,所以注册实例的状态是 STARTING。后面改变状态后会更新为 UP
    instance-enabled-onit: false
  1. 实例元数据
eureka:
  instance:
	  #元数据map,我们可以自己使用,放一些个性化的元数据,目前只有 configPath 和 zone 比较有用。 configPath 是使用 spring-cloud-config 的时候会设置
    metadata-map:
      # spring cloud 体系中,可用区的配置放入元数据中,key 为 zone
      zone: zone1
  1. Eureka 客户端配置
  2. Eureka 服务器地址配置,可以直接指定链接,也可以通过 region 和 zone 进行配置,也可以通过 DNS 配置:
eureka:
  instance:
    # 可用区列表,key 为 region,value 为 zone
    availability-zones:
      region1: zone1, zone2
      region2: zone3
    # 所在区域,通过这个读取 availability-zones 获取 zone,然后通过 zone 读取 service-url 获取对应的 eureka url
    # 这里的逻辑对应的类是 ConfigClusterResolver 和 ZoneAffinityClusterResolver
    region: region1
    # key 为 zone,value 为 eureka 链接,以逗号分隔
    service-url:
      # 默认eureka集群,这里必须是defaultZone,不能用-替换大写,与其他的配置不一样,因为实在EurekaClientConfigBean里面写死的
      defaultZone: http://127.0.0.1:8211/eureka/
      zone1: http://127.0.0.1:8212/eureka/
      zone2: http://127.0.0.1:8213/eureka/
      zone3: http://127.0.0.1:8214/eureka/
    # 如果上面 eureka server 地址相关配置更新了,多久之后会重新读取感知到
    eureka-service-url-poll-interval-seconds: 300
    # 是否使用 dns 获取,如果指定了则通过下面的 dns 配置获取,而不是上面的 service-url
    use-dns-for-fetching-service-urls: false
    # dns 配置
    # eureka-server-d-n-s-name:
    # dns 配置的 eureka server 的 port
    # eureka-server-port:
    # dns 配置的 eureka server 的 port 后面的 uri 前缀 context
    # eureka-server-u-r-l-context:
    # 如果设置为 true,则同一个 zone 下的 eureka 会跑到前面优先访问。默认为 true
    prefer-same-zone-eureka: true
  1. 拉取服务实例信息相关配置
eureka:
  instance:
    # 是否从 eureka 上面拉取实例
    fetch-registry: true
	  # 如果只想获取一个特定 virtual host name 的实例列表,就配置 registry-refresh-single-vip-address
    #registry-refresh-single-vip-address:
    # 客户端请求头指定服务端返回的实例信息是压缩的信息还是完整信息,默认是完整信息
    # full, compact
    client-data-accept: full
    # eureka client 刷新本地缓存时间
    # 默认30s
    registry-fetch-interval-seconds: 5
    # eureka client 刷新本地缓存(定时拉取 eureka 实例列表)线程池大小,默认为 2
    cache-refresh-executor-thread-pool-size: 2
    # eureka client 刷新本地缓存(定时拉取 eureka 实例列表)线程池任务最大延迟时间,这个配置是定时拉取任务延迟(registry-fetch-interval-seconds)的倍数,默认 10 倍
    cache-refresh-executor-exponential-back-off-bound: 10
    # 是否禁用增量拉取,如果网络条件不好,可以禁用,每次都会拉取全量
    disable-delta: false
    # 只保留状态为 UP 的实例,默认为 true
    filter-only-up-instances: true
    #可以指定也从某些 region 拉取服务实例
    #fetch-remote-regions-registry:
	  # 是否打日志记录每次拉取实例信息与当前缓存内的实例信息变化
    log-delta-diff: true

	  #在spring cloud 环境中,DiscoveryClient 用的其实都是 CompositeDiscoveryClient,这个 CompositeDiscoveryClient 逻辑其实就是多个 DiscoveryClient 共存,先访问一个,没找到就通过下一个寻找
    #这个order决定了顺序,默认为 0
    order: 0
  1. 当前实例注册相关配置
eureka:
  instance:
    # 是否将自己注册到 eureka 上面
    register-with-eureka: true
	  # 是否在初始化的时候就注册到 eureka,一般设置为 false,因为实例还不能正常提供服务
    should-enforce-registration-at-init: false
    # 是否在关闭的时候注销实例,默认为 true
    should-unregister-on-shutdown: true
    # 是否对于实例状态改变更新进行限流,默认为 true
    on-demand-update-status-change: true
	  # 实例信息同定时同步到 Eureka Server 的间隔时间。每隔这么长时间,检查实例信息(即eureka.instance配置信息)是否发生变化,如果发生变化,则同步到 Eureka Server,默认 30s
    # 主要检查两类信息,分别是服务地址相关信息,以及服务过期时间与刷新时间配置信息
    instance-info-replication-interval-seconds: 30
    # 实例信息同定时同步到 Eureka Server 的初始延迟时间,默认 40s
    initial-instance-info-replication-interval-seconds: 40
  1. http连接相关配置
eureka:
  instance:
    # 代理相关配置
    # proxy-host:
    # proxy-port:
    # proxy-user-name:
    # proxy-password:
    # 是否对于发往 Eureka Server 的 http 请求启用 gzip,目前已经过期了,只要 Eureka Server 启用了 gzip,请求就是 gzip 压缩的
    g-zip-content: true
    # httpclient 的链接超时,默认 5s
    eureka-server-connect-timeout-seconds: 5
    # httpclient 的读取超时,默认 5s
    eureka-server-read-timeout-seconds: 8
    # httpclient 的空闲连接超时,默认 30s
    eureka-connection-idle-timeout-seconds: 30
    # httpclient 的总连接数量,默认 200
    eureka-server-total-connections: 200
    # httpclient 的每个 host 的连接数量
    eureka-server-total-connections-per-host: 50
	  # tls 相关配置,默认没有启用
#      tls:
#        enabled: false
#        key-password:
#        key-store:
#        key-store-password:
#        key-store-type:
#        trust-store:
#        trust-store-password:
#        trust-store-type:
  1. Eureka 服务器配置
  2. 定时检查实例过期相关配置:实例注册后需要发送心跳证明这个实例是活着的, Eureka 服务器中也有定时任务检查实例是否已经过期:
eureka:
  server:
    #主动检查服务实例是否失效的任务执行间隔,默认是 60s
    eviction-interval-timer-in-ms: 3000
	  #这个配置在两个地方被使用:
    #如果启用用了自我保护,则会 renewal-threshold-update-interval-ms 指定的时间内,收到的心跳请求个数是否小于实例个数乘以这个 renewal-percent-threshold
    #定时任务检查过期实例,每次最多过期 1 - renewal-percent-threshold 这么多比例的实例
    renewal-percent-threshold: 0.85
  1. 自我保护相关配置:Eureka 服务器中有定时过期的任务,检查迟迟没有心跳的实例,并注销他们。自我保护主要针对集群中网络出现问题,导致有很多实例无法发送心跳导致很多实例状态异常,但是实际实例还在正常工作的情况,不要让这些实例不参与负载均衡:
eureka:
  server:
	  #注意,最好所有的客户端实例配置的心跳时间相关的配置,是相同的。这样使用自我保护的特性最准确。
    #关闭自我保护
    #我们这里不使用自我保护,因为:
    #自我保护主要针对集群中网络出现问题,导致有很多实例无法发送心跳导致很多实例状态异常,但是实际实例还在正常工作的情况,不要让这些实例不参与负载均衡
    #启用自我保护的情况下,就会停止对于实例的过期
    #但是,如果出现这种情况,其实也代表很多实例无法读取注册中心了。
    #并且还有一种情况就是,Eureka 重启。虽然不常见,但是对于镜像中其他的组件更新我们还是很频繁的
    #我倾向于从客户端对于实例缓存机制来解决这个问题,如果返回实例列表为空,则使用上次的实例列表进行负载均衡,这样既能解决 Eureka 重启的情况,又能处理一些 Eureka 网络隔离的情况
	  #自我保护模式基于每分钟需要收到 renew (实例心跳)请求个数,如果启用了自我保护模式,只有上一分钟接收到的 renew 个数,大于这个值,实例过期才会被注销
    enable-self-preservation: false
	  # 每分钟需要收到 renew (实例心跳)请求个数是需要动态刷新的,这个刷新间隔就是 renewal-threshold-update-interval-ms
	  #更新流程大概是:计算当前一共有多少实例,如果大于之前期望的实例量 * renewal-percent-threshold(或者没开启自我保护模式),则更新期望的实例数量为当前一共有多少实例
    #之后根据期望的实例数量,计算期望需要收到的实例心跳请求个数 = 期望的实例数量 * (60 / expected-client-renewal-interval-seconds) * renewal-percent-threshold
    #公式中 60 代表一分钟,因为公式用到了 expected-client-renewal-interval-seconds,也就是实例平均心跳间隔,为了使这个公式准确,最好每个实例配置一样的心跳时间
    #默认 900000ms = 900s = 15min
	  renewal-threshold-update-interval-ms: 900000
	  #上面提到的实例平均心跳间隔,或者说是期望的心跳间隔,为了使这个公式准确,最好每个实例配置一样的心跳时间
    #默认 30s
    expected-client-renewal-interval-seconds: 30
	  #这个配置在两个地方被使用:
    #如果启用用了自我保护,则会 renewal-threshold-update-interval-ms 指定的时间内,收到的心跳请求个数是否小于实例个数乘以这个 renewal-percent-threshold
    #定时任务检查过期实例,每次最多过期 1 - renewal-percent-threshold 这么多比例的实例
    renewal-percent-threshold: 0.85
  1. 同一区域内集群配置相关:上面我们提到了,同一区域内的 Eureka 服务器实例,收到的客户端请求,会转发到同一区域内的的其他 Eureka 服务器实例。同时,在某一 Eureka 服务器实例启动的时候,会从同一区域内其他 Eureka 服务器同步实例列表。并且,转发到其他 Eureka 服务器实例是异步转发的,这就有专门的线程池进行转发。同时,转发的也是 HTTP 请求,这就需要 HTTP 连接池:
eureka:
  server:
	  #Eureka Server 从配置中更新同一区域内的其他 Eureka Server 实例列表间隔,默认10分钟
    peer-eureka-nodes-update-interval-ms: 600000
	  #启动时从其他 Eureka Server 同步服务实例信息的最大重试次数,直到实例个数不为 0,默认为 0,这样其实就是不同步
    registry-sync-retries: 0
    #启动时从其他 Eureka Server 同步服务实例信息重试间隔
    registry-sync-retry-wait-ms: 30000
	  #集群内至少有多少个 UP 的 Eureka Server 实例数量,当前 Eureka Server 状态为 UP。默认 -1,也就是 Eureka Server 状态不考虑 UP 的集群内其他 Eureka Server 数量。
    min-available-instances-for-peer-replication: -1
	  #请求其他实例任务的最大超时时间,默认 30 秒
    max-time-for-replication: 30000
	  #用来处理同步任务的线程数量,有两个线程池,一个处理批量同步任务,默认大小为20
    max-threads-for-peer-replication: 20
    #另一个处理非批量任务(如果没用 AWS Autoscaling 对接相关特性则没有啥用),默认大小为20
    max-threads-for-status-replication: 20
    #处理批量任务的线程池队列长度,默认为 10000
    max-elements-in-peer-replication-pool: 10000
    #处理非批量任务的线程池队列长度,默认为 10000
    max-elements-in-status-replication-pool: 10000
	  #Eureka Server 通过 httpclient 访问其他 Eureka Server 同步实例,httpclient 的连接超时,默认 200ms
    peer-node-connect-timeout-ms: 200
    #httpclient 的读取超时,默认 200ms,一般不用太长
    peer-node-read-timeout-ms: 200
    #httpclient 的最大总连接数量,默认 1000
    peer-node-total-connections: 1000
    #httpclient 的对于某一 host 最大总连接数量,默认 500
    peer-node-total-connections-per-host: 500
    #httpclient 的连接空闲保持时间,默认 30s
    peer-node-connection-idle-timeout-seconds: 30
  1. 跨区域相关配置。Eureka 服务器会定时拉取其他区域的服务实例列表缓存在本地。在查询本地查询不到某个微服务的时候,就会查询这个远程区域服务实例的缓存。相关配置如下:
eureka:
  server:
    #请求其他 Region 的 httpclient 的连接超时,默认 1000ms
    remote-region-connect-timeout-ms: 1000
    #请求其他 Region 的 httpclient 的读取超时,默认 1000ms
    remote-region-read-timeout-ms: 1000
    #请求其他 Region 的 httpclient 的最大总连接数量,默认 1000
    remote-region-total-connections: 1000
    #请求其他 Region 的 httpclient 的对于某一 host 最大总连接数量,默认 500
    remote-region-total-connections-per-host: 500
    #请求其他 Region 的 httpclient 的连接空闲保持时间,默认 30s
    remote-region-connection-idle-timeout-seconds: 30
    #请求其他 Region 的 http 请求是否开启 gzip,对于其他 Region 我们认为网络连接是比较慢的,所以默认开启压缩
    g-zip-content-from-remote-region: true
    #    remote-region-urls-with-name:
    #      region2eureka1: http://127:0:0:1:8212/eureka/
    #      region2eureka2: http://127:0:0:1:8213/eureka/
    #    remote-region-app-whitelist:
    #如果需要从其他 Region 获取实例信息,这个获取间隔,默认为 30s
    remote-region-registry-fetch-interval: 30
    #如果需要从其他 Region 获取实例信息,这个任务的线程池,默认为 20个
    remote-region-fetch-thread-pool-size: 20

启动一个 Eureka Server

启动一个 Eureka 注册中心服务器非常简单,我们这里使用的是 Spring Cloud 封装好的启动包。Eureka 1.x 的 Eureka Server 是纯基于 servlet 的应用。为了与 Spring Cloud 结合使用,需要粘合模块,这就是 spring-cloud-netflix-eureka-server。在 spring-cloud-netflix-eureka-server 中,也有一个和 com.netflix.eureka.EurekaBootStrap 代码很类似的启动类,即 org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap。在我们启动 EurekaServer 实例的时候,只用加入对于 spring-cloud-starter-eureka-server 的依赖即可。之后通过 @EnableEurekaServer 注解即可启动一个 Eureka 服务器实例。

Eureka Server 的依赖:

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-iiford</artifactId>
        <groupId>com.github.hashjang</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-iiford-eureka-server</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.github.hashjang</groupId>
            <artifactId>spring-cloud-iiford-service-common</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>

Eureka Server 的配置: 参考我们上面的配置即可: application.yml

Eureka Server 的启动类: EurekaServerApplication.java

package com.github.hashjang.iiford.eureka.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

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

07-06 12:10