Eureka & Ribbon & Feign
Eureka
客户端实例化流程
- 客户端实例化,创建
3个线程池(scheduler,heartbeatExecutor,cacheRefreshExecutor) - 如果配置了需要从服务端拉取服务列表,则执行服务拉取操作(全量保存或拉取更新,首次是全量),拉取到的服务列表放入本地缓存
- 如果配置了预注册处理器,执行预注册处理器
- 如果配置了需要向服务端注册服务且需要在初始化时注册,则执行注册操作(注册成功响应码
204,注册失败会抛出异常终止实 例创建) - 开启调度任务
- 如果配置了需要从服务端拉取服务列表,周期性从服务端拉取注册的服务列表(
refreshRegistry) - 如果配置了需要向服务端注册服务,开启心跳任务(如果响应码
404,则发起注册) - 如果配置了按需更新,注册服务状态更新监听器,更新时上报到服务端
- 如果配置了需要从服务端拉取服务列表,周期性从服务端拉取注册的服务列表(
- 将客户端实例注册到监视器
客户端停止流程
- 如果注册了服务状态更新监听器,反注册服务状态更新监听器
- 关闭调度任务
- 如果配置了需要向服务端注册服务且需要在停止时反注册,将服务状态标记为
DOWN,反注册 - 关闭与服务端的传输服务
- 关闭心跳过时监视器
- 关闭服务注册信息过时监视器
- 从监视器反注册客户端实例
心跳与服务剔除
心跳机制
- 客户端启动后,就会启动一个定时任务,定时向服务端发送心跳数据,告知服务端自己还活着,默认的心跳时间间隔是
30秒 - 需要注册而未注册的客户端在首次心跳后会触发注册操作
服务剔除
- 如果开启了自我保护机制,那么所有的服务,包括长时间没有收到心跳的服务(即已过期的服务)都不会被剔除
- 如果未开启自我保护机制,那么将判断最后一分钟收到的心跳数与一分钟收到心跳数临界值比较,如果前者大于后者,且后者大于
0的话,则启用服务剔除机制 - 一旦服务剔除机制开启,服务端并不会直接剔除所有已过期的服务,而是通过随机数的方式进行剔除,避免自我保护开启之前将所有的服务(包括正常的服务)给剔除
自我保护机制
- 如果
15分钟内超过85%的客户端节点都没有正常的心跳,那么就会认为客户端与注册中心发生了网络故障,进入自我保护机制。 Eureka采用的AP,也就是保证了服务的可用性,舍弃了数据的一致性。当网络发生分区时,客户端和服务端的通讯将会终止,那么服务端在一定的时间内将收不到大部分的客户端的一个心跳,如果这个时候将这些收不到心跳的服务剔除,那可能会将可用的客户端剔除了,无法保证可用性。
多级缓存机制
- 为了避免同时读写内存数据造成的并发冲突问题,
Eureka采用了多级缓存机制提升服务请求的响应速度,通过一 个只读,一个读写缓存来实现 Eureka Server存在三个变量(registry、readWriteCacheMap、readOnlyCacheMap)保存服务注册信息,默认情况下定时任务每30s将readWriteCacheMap同步至readOnlyCacheMap,每60s清理超过90s未续约的节点,客户端每30s从readOnlyCacheMap更新服务注册信息,而UI则从registry更新服务注册信息
registry
ConcurrentHashMap- 无过期时间,实时更新,UI端请求的是这里的服务注册信息
readWriteCacheMap
LoadingCache-Guava缓存,包含失效机制,实时更新,缓存时间180秒
readOnlyCacheMap
ConcurrentHashMap- 无过期时间,周期更新,默认每30s从readWriteCacheMap更新,客户端默认从这里更新服务注册信息,可配置直接从readWriteCacheMap更新
当服务节点发生注册,下线,过期,状态变更等变化时
Ribbon
- 主要功能是解析配置中或注册中心的服务列表,通过客户端的软件负均衡算法来实现服务请求的分发。
LoadBalancerClient在初始化的时候,会向Eureka Server获取服务注册列表,并且每10s向Eureka Client发送ping,来判断服务的可用性,如果服务的可用性发生了改变或者服务数量和之前的不一致,则从注册中心更新或者重新拉取。LoadBalancerClient有了这些服务注册列表,就可以根据具体的IRule来进行负载均衡。
负载均衡策略
- 轮询策略(
RoundRobinRule,默认)- 轮询策略表示每次都顺序取下一个
provider,比如一共有5个provider,第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:开启
ribbon.eager-load.enabled = true
ribbon.eager-load.clients = FooService, BarService
Feign
- 将接口以
java interface类抽象方法搭配相关注解的方式来声明,通过动态代理(JDK动态代理,FeignInvocationHandler)生成代理类,将抽象方法实现成HTTP的请求形式调用provider,并将响应结果转换成java bean。
@EnableFeignClients
- 通过
basePackages或basePackageClasses或clients声明Feign Client - 通过
defaultConfiguration声明配置
@FeignClient
- 通过
name指定服务名称 - 通过
path指定uri前缀,作用类似servlet中context-path