Spring Cloud 说透Ribbon 实现负载均衡原理

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();
    
  • 在创建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整个源码调用过程

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值