上一篇文章Eureka(五)——高可用中,我们介绍了如何构建高可用的服务注册中心和服务提供。
本文我们来看看如何去消费服务提供者的接口。
使用LoadBalancerClient
在Spring Cloud Commons中提供了大量的与服务治理相关的抽象接口,包括DiscoveryClient
、这里我们即将介绍的LoadBalancerClient
等。Spring Cloud做这一层抽象,很好的解耦了服务治理体系,使得我们可以轻易的替换不同的服务治理设施。
从LoadBanacerClient
接口的命名中,我们就知道这是一个负载均衡客户端的抽象定义,下面我们就看看如何使用Spring Cloud提供的负载均衡器客户端接口来实现服务的消费。
首先创建一个服务消费者工程,命名为:eureka-consumer
。并在pom.xml
中引入依赖
1 | <dependencies> |
配置application.yml
,指定eureka注册中心的地址:
1 | spring: |
创建应用主类。初始化RestTemplate
,用来真正发起REST请求。
1 |
|
创建一个接口用来消费eureka-client-multi提供的接口:
1 |
|
我们注入了LoadBalancerClient
和RestTemplate
,并在/consumer
接口的实现中,先通过loadBalancerClient
的choose
函数来负载均衡地选出一个eureka-client
的服务实例,这个服务实例的基本信息存储在ServiceInstance
中,然后通过这些对象中的信息拼接出访问/dc
接口的详细地址,最后再利用RestTemplate
对象实现对服务提供者接口的调用。
启动eureka-consumer,这时Eureka Server的dashboard如下所示,我们看到eureka-consumer也被注册到Server中。
访问eureka-consumer的/consumer
接口:http://127.0.0.1:8763/consumer
,返回eureka-client-multi
服务的数据:
查看eureka-consumer输出的日志我们会发现,loadBalancerClient
在提供服务的三个地址之间切换:
1 | http://wangqideimac.lan:8741/dc |
LoadBalancerClient的原理
上文我们看到eureka-consumer能够成功以负载均衡的方式访问eureka-client-multi服务,下面我们来看看LoadBalancerClient
的原理。
RibbonLoadBalancerClient
首先我们来看看这里LoadBalancerClient
的实现类是什么,通过加断点的方式发现它的实现类是RibbonLoadBalancerClient
。为什么?我们明明没有引入ribbon相关的依赖。
仔细看spring-cloud-starter-netflix-eureka-client
,发现它引入了ribbon的依赖:
进入spring-cloud-starter-netflix-ribbon
包,其中spring.factories
文件中定义了EnableAutoConfiguration
:
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
RibbonAutoConfiguration
是ribbon的配置类,其中定义了LoadBalancerClient
的实例为RibbonLoadBalancerClient
:
1 |
|
下图是RibbonLoadBalancerClient
类的继承关系:
RibbonLoadBalancerClient
实现了两个接口LoadBalancerClient
和ServiceInstanceChooser
。
LoadBalancerClient
接口有三个方法,其中execute()
为执行请求,reconstructURI()
用来重构url:
1 | public interface LoadBalancerClient extends ServiceInstanceChooser { |
ServiceInstanceChooser
接口,主要有一个方法,用来根据serviceId来获取ServiceInstance
,代码如下:
1 | public interface ServiceInstanceChooser { |
RibbonClientConfiguration
RibbonClientConfiguration
是一个重要的配置类,它配置了以下接口的实例对象:
IClientConfig
:DefaultClientConfigImpl
IRule
:ZoneAvoidanceRule
IPing
:DummyPing
ServerList
:ConfigurationBasedServerList
ServerListUpdater
:PollingServerListUpdater
ILoadBalancer
:ZoneAwareLoadBalancer
ServerListFilter
:ZonePreferenceServerListFilter
RetryHandler
:DefaultLoadBalancerRetryHandler
RibbonLoadBalancerClient
上文我们知道了LoadBalancerClient
的实现类是RibbonLoadBalancerClient
,这个类是非常重要的一个类,最终的负载均衡由它来执行。
choose
现在我们来看看它是如何选择具体服务实例并获取到服务信息的。
1 | public ServiceInstance choose(String serviceId) { |
choose
方法调用getServer
去获取实例:
1 | protected Server getServer(String serviceId) { |
- 调用
getLoadBalancer()
方法从Spring Context中获取ILoadBalancer
的实例 - 然后在
getServer()
方法中调用这个ILoadBalancer
实例的chooseServer
方法选择服务实例。传入的key固定为”default”。 - 新建一个
RibbonServer
实例返回。
ILoadBalancer
ILoadBalancer
在ribbon-loadbalancer包下,它是定义了实现软件负载均衡的一个接口,它需要一组可供选择的服务注册列表信息,以及根据特定方法区选择服务:
1 | public interface ILoadBalancer { |
ZoneAwareLoadBalancer
RibbonClientConfiguration
配置类中新建了ILoadBalancer
的实例ZoneAwareLoadBalancer
:
1 |
|
这是一个比较核心的类。可以看到,ZoneAwareLoadBalancer
新建的时候传入了RibbonClientConfiguration
配置类中新建的各类参数:IClientConfig
、IRule
、IPing
、ServerList
、ServerListFilter
、ServerListUpdater
。
ZoneAwareLoadBalancer
的继承关系如下:
跟踪ZoneAwareLoadBalancer
的构造函数,由于ZoneAwareLoadBalancer
继承了DynamicServerListLoadBalancer
和BaseLoadBalancer
,因此它会首先构造BaseLoadBalancer
和DynamicServerListLoadBalancer
。
DynamicServerListLoadBalancer
的构造函数执行流程如下:
- 经过一系列的初始化配置
- 执行
restOfInit()
方法 restOfInit()
方法中,有一个updateListOfServers()
方法,该方法用来获取所有的ServerListupdateListOfServers()
方法首先调用serverListImpl.getUpdatedListOfServers()
获取所有的服务列表。serverListImpl
是ServerList接口的具体实现DomainExtractingServerList
。DomainExtractingServerList.getUpdatedListOfServers
方法调用DiscoveryEnableNIWSServerList.getUpdatedListOfServers
方法DiscoveryEnableNIWSServerList.getUpdatedListOfServers
方法最终调用obtainServersViaDiscovery()
方法返回相应服务所有的服务实例信息。
updateListOfServers()
方法最后调用updateAllServerList
方法更新服务实例
DiscoveryEnabledNIWSServerList.obtainServersViaDiscovery
obtainServersViaDiscovery
方法中首先通过eurekaClientProvider
获取EurekaClient
。eurekaClientProvider
的实现类是DefaultListableBeanFactory#Jsr330DependencyProvider
,它返回Spring容器类中相应类的实例。
EurekaClient
的实现类为CloudEurekaClient
,它具有服务注册、获取服务注册列表的等全部功能。调用其getInstancesByVipAddress
方法返回相应服务所有的服务实例信息。
更新注册信息
前文我们看到,LoadBalancer从EurekaClient中获取服务信息,这里我们看看LoadBalancer如何判断服务的可用性,如何更新服务注册信息。
前面我们说到,ZoneAwareLoadBalancer
继承了BaseLoadBalancer
,它在实例化中也会执行BaseLoadBalancer
的构造函数。
在BaseLoadBalancer
的构造函数中调用setupPingTask()
开启一个PingTask任务,代码如下:
1 | void setupPingTask() { |
默认情况下pingIntervalSeconds
为10,即每10秒执行一次PingTask
。
PingTask
任务主体中新建一个Pinger
对象,调用其runPinger
方法,流程如下:
执行
results = pingerStrategy.pingServers(ping, allServers)
,获取服务的可用性。其中pingerStrategy
的实际类型为BaseLoadBalancer#SerialPingStrategy
。ping
的类型为IPing
,它有一个isAlive
方法判断server是否可用,这里的实际类型为NIWSDiscoveryPing
。SerialPingStrategy
的pingServers
方法中遍历所有的服务实例,调用NIWSDiscoveryPing.isAlive
方法获取服务实例的可用性。NIWSDiscoveryPing.isAlive
方法获取服务实例的状态,如果状态为UP
,说明服务可用;否则该服务不可用。将可用的服务实例保存在
List<Server> upServerList
中。- 如果服务可用性发生了改变,则调用
ServerStatusChangeListener.serverStatusChanged
进行相应的通知。
PollingServerListUpdater
Server List的更新在PollingServerListUpdater定时任务中执行。它在RibbonClientConfiguration
中新建:
1 |
|
它在定时任务中调用DynamicServerListLoadBalancer.updateListOfServers
方法,从本地的缓存(virtualHostNameAppMap
)中获取服务列表,最后调用updateAllServerList
方法更新服务实例,保存在BaseLoadBalancer
实例的allServerList
变量中。
chooseServer
ZoneAwareLoadBalancer
的chooseServer
方法代码如下:
1 | public Server chooseServer(Object key) { |
调用getLoadBalancerStats()
方法获取LoadBalancerStats
,LoadBalancerStats
类保存了LoadBalancer每个节点的操作特征和统计信息。然后调用getAvailableZones
方法获取可用的Zone,
根据可用Zone的不同,chooseServer
方法选择不同的执行流程。本地中我们配置了defaultZone: http://peer1:8751/eureka/,http://peer2:8752/eureka/,http://peer3:8753/eureka/
,因此可用的Zone只有一个。我们先看看可用Zone只有一个的情况。
可用Zone只有一个
如果可用Zone只有一个,调用父类BaseLoadBalancer
的chooseServer
方法。
chooseServer
方法中调用IRule
的choose
方法选择一个Server。
IRule
IRule用于复杂均衡的策略,它有三个方法:
1 | public interface IRule{ |
其中choose()
是根据key来获取server,setLoadBalancer()
和getLoadBalancer()
是用来设置和获取ILoadBalancer的。
IRule有很多默认的实现类,这些实现类根据不同的算法和逻辑来处理负载均衡。Ribbon实现的IRule如下图所示:
- BeastAvailableRule
- ClientConfigEnableRoundRobinRule
- AvailabilityFilteringRule
- ZoneAvoidanceRule
- RoundRobinRule
- WeightedResponseTimeRule
- RetryRule
- RandomRule
ZoneAvoidanceRule
默认选择的ZoneAvoidanceRule
,根据server的zone区域和可用性来轮询选择。其choose
方法如下:
1 | public Server choose(Object key) { |
- 首先调用
getLoadBalancer()
方法获取ZoneAwareLoadBalancer
- 调用
getPredicate()
方法获取CompositePredicate
实例 - 调用
CompositePredicate
的父类AbstractServerPredicate
的chooseRoundRobinAfterFiltering
方法。传入调用ZoneAwareLoadBalancer.getAllServers()
方法获得的服务列表,该列表从BaseLoadBalancer
实例的allServerList
属性中获取。
chooseRoundRobinAfterFiltering
方法如下:
1 | public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) { |
- 调用
getEligibleServers
方法获取适合的Server列表,判断是否合适的方法为ZoneAvoidanceRule.apply
方法 - 然后调用
incrementAndGetModulo
方法获取Server的index,该方法就是在modulo范围内轮询获取下一个index - 最后通过index获取Server
可用Zone大于一个
修改eureka-consumer的application.yml
:
1 | spring: |
如果可用Zone大于一个,流程如下:
- 调用
getLoadBalancerStats()
方法获取LoadBalancerStats
- 调用
ZoneAvoidanceRule.createSnapsot
保存关于zone的快照 - 调用
ZoneAvoidance.getAvailableZones
返回可用的Zones 如果可用的Zones数量小于刚才zone快照里保存的数量
- 调用
ZoneAvoidanceRule.randomChooseZone
从可用的Zones中随机选择一个zone - 根据选择的zone,调用
getLoadBalancer
方法获取BaseLoadBalancer
- 调用
BaseLoadBalancer.chooseServer
方法选择服务实例
- 调用
否则,和可用Zone只有一个的情况一样,调用父类
BaseLoadBalancer
的chooseServer
方法。
@LoadBalanced
LoadBalancerClient有一种更简单的使用方法:
1 |
|
在创建RestTemplate
bean的时候加上@LoadBalanced
注解,我们就可以直接使用restTemplate
:
1 | "/consumer") ( |
原理
Spring为加@LoadBalanced
注解的方法加入了拦截器,其配置在LoadBalancerAutoConfiguration
类中。
1 |
|
该类首先维护了一个被@LoadBalanced
修饰的RestTemplate
对象的List,在初始化过程中,通过调用customizer.customize(restTemplate)
方法来给RestTemplate
增加拦截器LoadBalancerInterceptor
。
拦截器LoadBalancerInterceptor
的拦截方法如下:
1 | public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, |
首先根据url获取服务名称,然后调用RibbonLoadBalancerClient.execute
方法。
RibbonLoadBalancerClient.execute
方法和choose
方法非常类似:
1 | public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { |
- 调用
getLoadBalancer()
方法从Spring Context中获取ILoadBalancer
的实例 - 然后在
getServer()
方法中调用这个ILoadBalancer
实例的chooseServer
方法选择服务实例。传入的key固定为”default”。 - 新建一个
RibbonServer
实例。 - 最后调用获得的服务并返回结果
http://blog.didispace.com/spring-cloud-starter-dalston-2-1/
https://blog.csdn.net/forezp/article/details/74820899