Ribbon 负责客户端负载均衡
Ribbon重要类继承关系
Ribbon调用流程图
1、RestTemplate是如何和Ribbon结合的
最后,回答问题的本质,为什么在RestTemplate加一个@LoadBalance注解就可可以开启负载均衡呢?
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
-
@LoadBalanced注解在spring-cloud-commons包中,查看mate-info/spring.factories文件,这里是Spring启动时会去自动装配的Class类文件,我们全局搜索LoadBalancedc.class文件,没发现引用;再全局搜索@LoadBalancedc 发现LoadBalancerAutoConfiguration负载均衡自动装配类中使用此注解;
获取所有IOC容器的restTemplates,restTemplate.setInterceptors(list);将RetryLoadBalancerInterceptor 重试负载均衡拦截器注入到restTemplate中,用于拦截每个http请求;@LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { ... } public RestTemplateCustomizer restTemplateCustomizer( final RetryLoadBalancerInterceptor loadBalancerInterceptor) { restTemplate.setInterceptors(loadBalancerInterceptor); }
-
RetryLoadBalancerInterceptor里实现来负载均衡,拦截http请求,
在拦截器方法中 public ClientHttpResponse intercept(){ ... }
-
一次完整的Http请求流程
1、如上面的流程图片所示,ribbon通过注入httpRequest拦截器intercept来拦截所有请求;
-
获取Request请求的host得到serveiceName;
-
第一个请求会加载RibbonClientConfiguration配置文件,从而创建ZoneAwareLoadBalancer对象,在继承关系中,它是IloadBalancer的实现类,IloadBalancer在ribbon框架的作用:负责从注册中心获取服务列表List与选根据服务名称选择服务,在ZoneAwareLoadBalancer父类DynamicServerListLoadBalancer构造方法中调用restOfInit(clientConfig)方法,restOfInit方法会根据serviceName获取consul对应服务列表信息,并保存到本地;在BaseLoadBalancer构造方法中创建定时器,实时监听服务状态;只有第一次请求会加载配置文件。
-
配置文件加载完成之后,调用RibbonLoadBalancerClient负载均衡执行客户端choose方法,通过IRule(默认实现ZoneAvoidanceRule)负载策略选出可以的一个服务,根据server信息reconstructURI()重构URI,执行http请求
-
如果请求结果异常,ribbon通过RetryTemplate在while循环捕获异常,比较当前服务,继续进行可用服务负载均衡,重试接口;如果重试了所有的服务都异常,最后response返回异常;如果其中一台服务成功,调处while循环;
Ribbon实现负载均衡的整个流程就此结束。
2、Ribbon实现负载均衡几个重要Class类介绍
1、当发起第一次http请求时,会去加载RibbonClientConfiguration配置文件,创建一些参数实例
-
IClientConfig:负责客户端、负载均衡、http请求参数的配置,如连接时间,读取时间配置,配置的key在CommonClientConfigKey里;
Ribbon结合restTemplate设置超时时间就通过他的实现DefaultClientConfigImpl设置; -
IRule:负责服务路由规则配置,
-
RandomRule随机,
-
RoundRobinRule轮训,
-
WeightedResponseTimeRule权重,
-
ZoneAvoidanceRule从可用的服务中轮训一个服务,他和RoundRobinRule类似;
private int incrementAndGetModulo(int modulo) { for (;;) { //module 服务集合size,每次与size取模 int current = nextIndex.get(); int next = (current + 1) % modulo; if (nextIndex.compareAndSet(current, next) && current < modulo) return current; } }
-
-
IPing:负责向server服务发起“ping”,验证服务是否存活,不同的注册中心有不同的实现,如ConsulPing、PingUrl、PingConstant、NoOpPing、DummyPing
- PingUrl 真实的去ping 某个url,判断其是否alive
- PingConstant 固定返回某服务是否可用,默认返回true,即可用
- NoOpPing 不去ping,直接返回true,即可用。
- DummyPing 直接返回true,并实现了initWithNiwsConfig方法。
-
ServerList:获取注册中心所有的服务列表;
public interface ServerList<T extends Server> { public List<T> getInitialListOfServers(); public List<T> getUpdatedListOfServers(); }
-
ServerListFilter:根据配置信息动态过滤符合规则的服务列表
public interface ServerListFilter<T extends Server> { public List<T> getFilteredListOfServers(List<T> servers); }
-
ILoadBalancer是ribbon-loadbalancer的jar包下,它是定义了实现软件负载均衡的一个接口,它需要一组可供选择的服务注册列表信息,以及根据负载策略去选择服务
# 添加一个servers集合 public void addServers(List<Server> newServers); 根据key获取一个server public Server chooseServer(Object key); 标记某个服务下线 public void markServerDown(Server server); 获取可用的server集合 public List<Server> getReachableServers(); 获取所有的server集合 public List<Server> getAllServers();
![](https://img-blog.csdnimg.cn/20200428174353723.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoYW5nZTk4NzY1NDMyMQ==,size_16,color_FFFFFF,t_70)
- 在创建ZoneAwareLoadBalancer实例时,会执行构造方法通过ServerList实现类获取Consul中服务名称为serviceName所有服务注册列表,并保存在线程安全的List集合中;源码如下:
结构图:在创建ZoneAwareLoadBalancer对象时会执行DynamicServerListLoadBalancer构造方法,以及BaseLoadBalancer构造方法:
DynamicServerListLoadBalancer构造方法中,会去获取所有服务列表的实现方法restOfInit(clientConfig);
{
restOfInit(clientConfig); 继续调用-> updateListOfServers();
}
updateListOfServers(){
我使用的是consul注册中心,ServerList的实现类为ConsulServerList,获取服务名称为serviceName的所有服务;
servers = serverListImpl.getUpdatedListOfServers();
}
BaseLoadBalancer构造方法中,会创建一个定时任务,每隔10秒回检测我们services服务的存活性,如果服务于之前状态不一致,会通知ribbon更新服务列表,或者重新从consul拉去新服务列表,获取到的服务信息保存到allServerList中;IPing的实现类做具体ping实现;consul实现类ConsulPing,它会检查server的status状态,来判断服务是否存活;
setupPingTask(){
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
new PingTask() 调用-> runPinger();->
notifyServerStatusChangeListener(changedServers);
}
2、LoadBalancerClient 负载均衡执行客户端
-
在Ribbon中一个非常重要的组件为LoadBalancerClient,他在spring-cloud-commons包下,它是一个接口,它继承ServiceInstanceChooser接口,他的实现类是RibbonLoadBalancerClient;
-
LoadBalancerClient接口提供的方法有三个
# 执行请求 <T> T execute(String serviceId, LoadBalancerRequest<T> request) <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) # 重构url URI reconstructURI(ServiceInstance instance, URI original);
-
ServiceInstanceChooser接口
提供了根据serviceId 获取serviceInstance实例方法; ServiceInstance choose(String serviceId);
-
RibbonLoadBalancerClient 他是一个负载均衡执行客户端,负责负载均衡的请求处理,根据serviceName获取具体服务节点,实现服务负载均衡,重构URI,执行请求获取response结果;
源码中通过choose()方法,选择具体服务实例的一个方法,通过追踪源码,最终交给了ILoadBalancer类去选择服务实例;ILoadBalancer作用就是获取服务注册列表,实时监听server状态public ServiceInstance choose(String serviceId) { Server server = getServer(serviceId); } protected Server getServer(String serviceId) { return getServer(getLoadBalancer(serviceId)); }
3、ConsulRawClient 服务注册于发现的真正调用者
- ConsulRawClient具有服务注册、获取服务注册列表功能;
- ConsulRawClient中声明了调用consul所有api的方法,连接consul注册中心请求客户端
RetryTemplate 重试模板
-
如果serviceName部署了三台服务,user-1,user-2,user-3,第一次请求负载到user1上,user1服务可能抛出处理异常或者超时异常,RetryTemplate会通过while循环进行服务负载均衡重试调用,同时捕获调用异常,如果捕获异常,标记user1不可用,重新从user2,user3负载一台可用服务,进行http请求的重试retryCallback.doWithRetry(context);
doExecute(...){ ... while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) { try { // 服务负载均衡之后重试调用 return retryCallback.doWithRetry(context); } catch (Throwable e) { try { // 注册异常,进行服务负载 registerThrowable(retryPolicy, state, context, e); } catch (Exception ex) { throw new TerminatedRetryException("Could not register throwable", ex); } ... }
参考ribbon源码解析
自己查看了ribbon整个源码调用过程