Skip to main content

Eureka & Ribbon & Feign

Eureka

客户端实例化流程

  1. 客户端实例化,创建 3 个线程池(scheduler, heartbeatExecutor, cacheRefreshExecutor
  2. 如果配置了需要从服务端拉取服务列表,则执行服务拉取操作(全量保存或拉取更新,首次是全量),拉取到的服务列表放入本地缓存
  3. 如果配置了预注册处理器,执行预注册处理器
  4. 如果配置了需要向服务端注册服务且需要在初始化时注册,则执行注册操作(注册成功响应码 204,注册失败会抛出异常终止实例创建)
  5. 开启调度任务
    • 如果配置了需要从服务端拉取服务列表,周期性从服务端拉取注册的服务列表(refreshRegistry
    • 如果配置了需要向服务端注册服务,开启心跳任务(如果响应码 404,则发起注册)
    • 如果配置了按需更新,注册服务状态更新监听器,更新时上报到服务端
  6. 将客户端实例注册到监视器

客户端停止流程

  1. 如果注册了服务状态更新监听器,反注册服务状态更新监听器
  2. 关闭调度任务
  3. 如果配置了需要向服务端注册服务且需要在停止时反注册,将服务状态标记为 DOWN,反注册
  4. 关闭与服务端的传输服务
  5. 关闭心跳过时监视器
  6. 关闭服务注册信息过时监视器
  7. 从监视器反注册客户端实例

心跳与服务剔除

心跳机制

  • 客户端启动后,就会启动一个定时任务,定时向服务端发送心跳数据,告知服务端自己还活着,默认的心跳时间间隔是 30
  • 需要注册而未注册的客户端在首次心跳后会触发注册操作

服务剔除

  • 如果开启了自我保护机制,那么所有的服务,包括长时间没有收到心跳的服务(即已过期的服务)都不会被剔除
  • 如果未开启自我保护机制,那么将判断最后一分钟收到的心跳数与一分钟收到心跳数临界值比较,如果前者大于后者,且后者大于 0 的话,则启用服务剔除机制
  • 一旦服务剔除机制开启,服务端并不会直接剔除所有已过期的服务,而是通过随机数的方式进行剔除,避免自我保护开启之前将所有的服务(包括正常的服务)给剔除

自我保护机制

  • 如果 15 分钟内超过 85% 的客户端节点都没有正常的心跳,那么就会认为客户端与注册中心发生了网络故障,进入自我保护机制。
  • Eureka 采用的 AP,也就是保证了服务的可用性,舍弃了数据的一致性。当网络发生分区时,客户端和服务端的通讯将会终止,那么服务端在一定的时间内将收不到大部分的客户端的一个心跳,如果这个时候将这些收不到心跳的服务剔除,那可能会将可用的客户端剔除了,无法保证可用性。

多级缓存机制

  • 为了避免同时读写内存数据造成的并发冲突问题,Eureka 采用了多级缓存机制提升服务请求的响应速度,通过一个只读,一个读写缓存来实现
  • Eureka Server 存在三个变量(registryreadWriteCacheMapreadOnlyCacheMap)保存服务注册信息,默认情况下定时任务每 30sreadWriteCacheMap 同步至 readOnlyCacheMap,每 60s 清理超过 90s 未续约的节点,客户端每 30sreadOnlyCacheMap 更新服务注册信息,而 UI 则从 registry 更新服务注册信息

registry

  • ConcurrentHashMap - 无过期时间,实时更新,UI 端请求的是这里的服务注册信息

readWriteCacheMap

  • LoadingCache - Guava 缓存,包含失效机制,实时更新,缓存时间 180

readOnlyCacheMap

  • ConcurrentHashMap - 无过期时间,周期更新,默认每 30sreadWriteCacheMap 更新,客户端默认从这里更新服务注册信息,可配置直接从 readWriteCacheMap 更新

当服务节点发生注册,下线,过期,状态变更等变化时

Ribbon

  • 主要功能是解析配置中或注册中心的服务列表,通过客户端的软件负均衡算法来实现服务请求的分发。
  • LoadBalancerClient 在初始化的时候,会向 Eureka Server 获取服务注册列表,并且每 10sEureka Client 发送 ping,来判断服务的可用性,如果服务的可用性发生了改变或者服务数量和之前的不一致,则从注册中心更新或者重新拉取。LoadBalancerClient 有了这些服务注册列表,就可以根据具体的 IRule 来进行负载均衡。

负载均衡策略

  • 轮询策略(RoundRobinRule,默认)
    • 轮询策略表示每次都顺序取下一个 provider,比如一共有 5provider,第 1 次取第 1 个,第 2 次取第 2 个,第 3 次取第 3 个,依此类推
  • 权重轮询策略(WeightedResponseTimeRule
    • 根据每个 provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低
    • 一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,当信息足够时,给每个 provider 附上一个权重,并按权重随机选择 provider,高权越重的 provider 会被高概率选中
  • 随机策略(RandomRule
    • provider 列表中随机选择一个 provider
  • 最少并发数策略 (BestAvailableRule)
    • 选择正在请求中的并发数最小的 provider,除非这个 provider 在熔断中
  • 基于轮询策略的重试策略(RetryRule
    • 先设定一个阈值时间段,如果在这个阈值时间段内当选择 provider 不成功,则一直尝试采用轮询策略最后选择一个可用的 provider
  • 可用性敏感策略(AvailabilityFilteringRule
    • 过滤性能差的 provider,一是过滤掉一直连接失败的 provider,二是过滤掉高并发的 provider
  • 区域敏感性策略(ZoneAvoidanceRule
    • 以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的 provider
    • 如果这个 ip 区域内有一个或多个实例不可达或响应变慢,都会降低该 ip 区域内其他 ip 被选中的权重

饥饿加载

  • 造成第一次服务调用出现失败的原因主要是 Ribbon 进行客户端负载均衡的 Ribbon Client 并不是在服务启动的时候就初始化好的,而是在调用的时候才会去创建相应的 Client,所以第一次调用的耗时不仅仅包含发送 HTTP 请求的时间,还包含了创建 Ribbon Client 的时间,这样一来如果创建时间速度较慢,同时设置的超时时间又比较短的话,很容易就会出现调用失败
  • 解决方法
    • ribbon.eager-load.enabled:开启 Ribbon 的饥饿加载模式
    • ribbon.eager-load.clients:指定需要饥饿加载的客户端名称
ribbon.eager-load.enabled = true
ribbon.eager-load.clients = FooService, BarService

Feign

  • 将接口以 java interface 类抽象方法搭配相关注解的方式来声明,通过动态代理(JDK 动态代理,FeignInvocationHandler)生成代理类,将抽象方法实现成 HTTP 的请求形式调用 provider,并将响应结果转换成 java bean

@EnableFeignClients

  • 通过 basePackagesbasePackageClassesclients 声明 Feign Client
  • 通过 defaultConfiguration 声明配置

@FeignClient

  • 通过 name 指定服务名称
  • 通过 path 指定 uri 前缀,作用类似 servletcontext-path