Spring Cloud — 深入了解Eureka、Ribbon及Feign
Eureka 负责服务注册与发现;Ribbon负责负载均衡;Feign简化了Web服务客户端调用方式。这三个组件可以协同工作,共同构建稳定、高效的微服务架构。
1 Eureka
分布式系统的CAP定理:
一致性(Consistency):同一个数据在集群中的所有节点,同一时刻都是同样的值。
可用性(Availability):集群中一部分节点故障后,集群整体还能处理客户端的请求。即每个请求都能收到一个(无论成功或失败)响应,且不会出现超时等情况。
分区容忍性(Partition tolerance):系统可用容忍消息的丢失或节点间通信的延迟。即分布式系统出现网络分区(由于网络故障导致系统中一部分节点无法与其他节点通信,从而形成孤立的分区)时,系统能否继续提供服务并保持数据一致性的能力。
在任何分布式系统中,不可能同时满足这三项,最多同时满足两项。
微服务的治理,核心是服务的注册和发现,针对同一服务,即使注册中心的不同节点保存的服务提供者信息不尽相同,也不会造成灾难性后果。对于消费者来说,能消费服务才是最重要的。即对于微服务的治理,遵循的是AP。
1.1 实现原理
图 服务提供者的服务生命周期
1.1.1 服务注册 Register
在服务提供者配置文件中将eureka.client.register-with-eureka的属性配置为true时,当服务提供者启动时,会调用Eureka所提供的服务注册相关方法,向Eureka服务器注册自己的信息。Eureka服务器会维护一个已注册的服务列表。
Eureka的AbstractInstanceRegistry类的ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry 字段存储了已注册的服务列表,
key 是服务的应用名称。value包含了服务实例的详细信息。
当服务实例状态发生变化时(比如不可用),服务提供者会向Eureka服务器更新自己的服务状态,同时会向其他Eureka服务器节点做状态同步。
1.1.2 服务续约 Renew
当将服务成功注册到Eureka服务器后,客户端会默认以30s(客户端配置属性eureka.instance.lease-renewal-interval-in-seconds)的频率向Eureka服务器发生一次心跳(即执行服务续约操作),来避免自己的注册信息被Eureka服务器剔除。
Eureka如果在90s内(客户端配置属性eureka.instance.lease-expiration-duration-in-seconds)没有收到客户端的心跳,则会将该服务实例从所维护的服务注册表中剔除,以禁止流向该实例的流量。
不过,当Eureka服务器处于自护保护模式时,不会清除服务实例信息。
1.1.3 获取服务
消费者端(fetch-registry属性配置为true)启动时会从Eureka服务器中获取注册表信息,并将其缓存在本地。并定期(默认30s)从Eureka服务器进行同步。
1.1.4 服务下线与剔除
当服务实例关闭时,会先向Eureka服务器发送服务下线请求,Eureka收到请求后,会将该服务实例信息从实例注册表中删除。
1.2 Eureka自我保护模式
图 处于自我保护模式下的Eureka服务
当Eureka处于自我保护模式时,会出现上面的红字提示,大概意思是:Eureka可能错误地报告服务实例为在线状态,而实际上它们可能并不在线。
这个问题是由于续租(Renewals)的数量低于阈值,导致服务实例没有被正确地标记为过期,从而可能影响到系统的稳定性和可用性。当Eureka每分钟收到心跳续租的数量恢复到阈值以上时,就会退出自我保护模式。
1.3 Eureka 高可用集群
Eureka服务器采用的是P2P(Peer to Peer)对等通信,是一种去中心化的结构。每个节点都是对等的。为了让Eureka服务高可用,必须让Eureka服务器之间能互相复制、同步所注册服务的实例信息。
图 Eureka集群注册模式
Eureka服务器配置文件中,register-with-eureka及fetch-registry属性都需要配置为true。eureka.client.service-url.defaultZone属性需要配置其他两个Eureka的服务器地址。
2 Bibbon
图 负载均衡方案核心部分
2.1 Ribbon 组件
Ribbon在实现负载均衡主要使用了6个组件。
2.1.1 服务器列表 ServerList
客户端负载均衡所使用的各服务实例列表,Ribbon支持下面3种方式:
- 静态服务器列表:通过BaseLoadBalancer的setServersList()方法进行设置。
- 基于配置的服务器列表:配置文件中通过<服务名称>.ribbon.listOfServers 属性进行配置。
- 基于服务发现的服务器列表:当在应用中同时使用Ribbon和Eureka时,默认会使用这种方式。当客户端启动时Ribbon会从Eureka服务器中获取所有注册服务的列表数据,并保持同步。
2.1.2 服务器列表过滤器 ServerListFilter
使用动态服务器列表时,过滤器会对原始服务列表使用一定的策略进行过滤。
ZoneAffinityServerList Filter | 仅返回与本身所处区域一致的服务提供者实例列表。 |
ServerListSubset Filter | 继承了ZoneAffinityServerListFilter,经过区域过滤后,仅返回一个固定大小的服务列表。 |
ZonePreferenceServerList Filter | 通过配置或Eureka所属区域来过滤出同区域的服务实例列表。使用Spring Cloud整合Eureka和Ribbon时会默认使用该过滤器。 |
图 具体的服务器列表过滤器
2.1.3 服务实例存活探测 IPing
就像ping指令一样,用来监测一个微服务实例是否有响应。如果监测到某服务实例不再可用则会从列表中及时剔除。
2.1.4 负载均衡策略 IRule
负责选择一个最终服务实例地址作为负载均衡处理结果。Ribbon提供的选择策略有轮询、根据响应时间加权、断路由等。
2.1.5 负载均衡器 ILoadBalancer
负载均衡的具体实现。
DynamicServerList LoadBalancer | 运行期间对服务实例动态更新和过滤。 |
ZoneAware LoadBalancer | 基础上面的类。并增加防止跨区域访问的功能。 |
NoOp LoadBalancer | 不执行实际的负载均衡操作。不会将请求分发到不同的服务实例上。 |
表 ILoadBalancer接口的具体实现类
2.1.6 服务调用器 RestClient
负载均衡后,Ribbon向服务提供者发起REST请求的工具。
2.2 负载均衡策略
Ribbon提供了以下几种负载均衡策略:
1. 轮询策略,RoundRobinRule。轮询方式选择服务实例。即每次调度执行i=(count + 1) mod n,来选出第i台服务器实例。count为执行请求的计数次数。
2. 随机选择,RandomRule。随机从可用的服务实例列表中选择一个。
3. 带有加权的轮询策略,WeightedResponseTimeRule。对各个服务实例响应时间进行加权处理,然后再采用轮询的方式。
Ribbon会对每个服务实例的响应时间进行统计,根据记录的时间计算每个服务实例的权重,最后根据每个实例的权重进行概率选择。
4 .可用过滤策略,AvailabilityFilteringRule。先过滤出有故障或并发请求大于阈值的部分服务实例,然后再以线性轮询的方式。
5 .区域感知策略,ZoneAvoidanceRule。先使用主过滤条件(区域负载器,选择最优区域)来进行过滤,然后使用次过滤条件进行过滤。最后对满足条件的服务实例使用轮询策略。
3 Feigh
Feign 提供了一个简洁的接口来定义和调用远程服务。
3.1 参数绑定
在Spring MVC 中注解会以参数每次作为默认值,但Feign不会,必须声明。
@RequestMapping(value = "/user/info",method = RequestMethod.GET)
String info(@RequestParam String name);
例如上面的代码,在Spring MVC中能获取到name这个参数值,但在Feign中不会,必须写为 @RequestParam(“name”) String name。
Feign 支持Spring MVC的注解,Spring MVC中常用的注解有以下几种:
@RequestParam: 绑定单个请求参数值。
@PathVariable:绑定URI模板变量值。
@RequestHeader:绑定请求头数据。
@RequestBody:绑定请求体数据。