关于微服务:2021升级版微服务教程6Ribbon使用原理整合Nacos权重实战优化-一篇搞定

51次阅读

共计 8907 个字符,预计需要花费 23 分钟才能阅读完成。

2021 升级版 SpringCloud 教程从入门到实战精通「H 版 &alibaba& 链路追踪 & 日志 & 事务 & 锁」

教程全目录「含视频」:https://gitee.com/bingqilinpe…

Ribbon 应用 + 原理 + 整合 Nacos 权重 + 实战优化 一篇搞定

Ribbon 根本应用

简介

Ribbon 是一个客户端负载平衡工具,封装 Netflix Ribbon 组件,可能提供客户端负载平衡能力。

了解 Ribbon 最重要的就是了解客户端这个概念,所谓客户端负载平衡工具不同于 Nginx(服务端负载平衡),Ribbon 和应用程序绑定,自身不是独立的服务,也不存储服务列表,须要负载平衡的时候,会通过应用程序获取注册服务列表,而后通过列表进行负载平衡和调用。

  • Nginx 独立过程做负载平衡,通过负载平衡策略,将申请转发到不同的服务上
  • 客户端负载平衡,通过在客户端保留服务列表信息,而后本人调用负载平衡策略,摊派调用不同的服务

根本应用

Ribbon 的负载平衡有两种形式

  1. 和 RestTemplate 联合 Ribbon+RestTemplate
  2. 和 OpenFeign 联合

Ribbon 的外围子模块

  1. ribbon-loadbalancer:能够独立应用或者和其余模块一起应用的负载平衡 API
  2. ribbon-core:Ribbon 的外围 API

订单服务集成 Ribbon

订单服务调用商品服务

配置过程 分两步

  1. 在订单服务中导入 ribbon 的依赖

    <!--ribbon-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
  2. 配置 RestTemplate

订单服务调用商品服务

  1. 订单服务调用商品服务的链接 不能写成 ip+ 端口号,须要写成商品服务的服务名称

  2. 重启 订单服务 测试负载平衡

Ribbon 负载平衡简略版实现的流程

  1. RestTemplate 发送的申请是服务名称 http://nacos-product/product/…
  2. 获取 @LoadBalanced 注解标记的RestTemplate
  3. RestTemplate增加一个拦截器,当应用 RestTemplate 发动 http 调用时进行拦挡
  4. 依据 url 中的服务名称 以及本身的负载平衡策略 去订单服务的服务列表中找到一个要调用的 ip+ 端口号 localhost:8802
  5. 拜访该指标服务,并获取返回后果

服务列表实际上是个 map

Ribbon 负载平衡原理 [理解]

获取 @LoadBalanced 注解标记的 RestTemplate。

Ribbon 将所有标记 @LoadBalanced 注解的 RestTemplate 保留到一个 List 汇合当中,具体源码如下:

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

具体源码地位是在 LoadBalancerAutoConfiguration 中。

RestTemplate 增加一个拦截器

拦截器不是 Ribbon 的性能

RestTemplate 增加拦截器须要有两个步骤,首先是定义一个拦截器,其次是将定义的拦截器增加到 RestTemplate 中。

定义一个拦截器

实现 ClientHttpRequestInterceptor 接口就具备了拦挡申请的性能,该接口源码如下:

public interface ClientHttpRequestInterceptor {
    /**
     * 实现该办法,在该办法内实现拦挡申请后的逻辑内容。* 对于 ribbon 而言,在该办法内实现了依据具体规定从
     * 服务集群中选取一个服务,并向该服务发动申请的操作。*/
   ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;

}

ribbon 中对应的实现类是 LoadBalancerInterceptor 具体源码如下:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

   private LoadBalancerClient loadBalancer;
   private LoadBalancerRequestFactory requestFactory;

    // 省略结构器代码...

   @Override
   public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
         final ClientHttpRequestExecution execution) throws IOException {final URI originalUri = request.getURI();
      String serviceName = originalUri.getHost();
      /**
       * 拦挡申请,并调用 loadBalancer.execute()办法
       * 在该办法外部实现 server 的选取。向选取的 server
       * 发动申请,并取得返回后果。*/
      return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
   }
}

将拦截器增加到 RestTemplate 中

RestTemplate继承了 InterceptingHttpAccessor,在InterceptingHttpAccessor 中提供了获取以及增加拦截器的办法,具体源码如下:

public abstract class InterceptingHttpAccessor extends HttpAccessor {

    /**
     * 所有的拦截器是以一个 List 汇合模式进行保留。*/
   private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();

   /**
    * 设置拦截器。*/
   public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {this.interceptors = interceptors;}

   /**
    * 获取以后的拦截器。*/
   public List<ClientHttpRequestInterceptor> getInterceptors() {return interceptors;}

   // 省略局部代码...
}

通过这两个办法咱们就能够将方才定义的 LoadBalancerInterceptor 增加到有 @LoadBalanced 注解标识的 RestTemplate 中。具体的源码如下 (LoadBalancerAutoConfiguration) 省略局部代码:

public class LoadBalancerAutoConfiguration {

    /**
      * 获取所有带有 @LoadBalanced 注解的 restTemplate
     */
   @LoadBalanced
   @Autowired(required = false)
   private List<RestTemplate> restTemplates = Collections.emptyList();

    /**
     * 创立 SmartInitializingSingleton 接口的实现类。Spring 会在所有
     * 单例 Bean 初始化实现后回调该实现类的 afterSingletonsInstantiated()
     * 办法。在这个办法中会为所有被 @LoadBalanced 注解标识的
     * RestTemplate 增加 ribbon 的自定义拦截器 LoadBalancerInterceptor。*/
   @Bean
   public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List<RestTemplateCustomizer> customizers) {return new SmartInitializingSingleton() {
         @Override
         public void afterSingletonsInstantiated() {for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {for (RestTemplateCustomizer customizer : customizers) {customizer.customize(restTemplate);
               }
            }
         }
      };
   }
    /**
     * 创立 Ribbon 自定义拦截器 LoadBalancerInterceptor
     * 创立前提是以后 classpath 下不存在 spring-retry。* 所以 LoadBalancerInterceptor 是默认的 Ribbon 拦挡
     * 申请的拦截器。*/
    @Configuration
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
   static class LoadBalancerInterceptorConfig {
      @Bean
      public LoadBalancerInterceptor ribbonInterceptor(
            LoadBalancerClient loadBalancerClient,
            LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
      }

      /**
       * 增加拦截器具体方法。首先获取以后拦截器汇合(List)
       * 而后将 loadBalancerInterceptor 增加到以后汇合中
       * 最初将新的汇合放回到 restTemplate 中。*/
      @Bean
      @ConditionalOnMissingBean
      public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {return new RestTemplateCustomizer() {
            @Override
            public void customize(RestTemplate restTemplate) {
               List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
               list.add(loadBalancerInterceptor);
               restTemplate.setInterceptors(list);
            }
         };
      }
   }
}

至此晓得了 ribbon 拦挡申请的基本原理,接下来咱们看看 Ribbon 是怎么选取 server 的。

Ribbon 选取 server 原理概览

通过下面的介绍咱们晓得了当发动申请时 ribbon 会用 LoadBalancerInterceptor 这个拦截器进行拦挡。在该拦截器中会调用 LoadBalancerClient.execute() 办法,该办法具体代码如下:

@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
  /**
   * 创立 loadBalancer 的过程能够了解为组装选取服务的规定(IRule)、* 服务集群的列表 (ServerList)、测验服务是否存活(IPing) 等个性
   * 的过程(加载 RibbonClientConfiguration 这个配置类),须要留神
   * 的是这个过程并不是在启动时进行的,而是当有申请到来时才会解决。*/
   ILoadBalancer loadBalancer = getLoadBalancer(serviceId);

   /**
    * 依据 ILoadBalancer 来选取具体的一个 Server。* 选取的过程是依据 IRule、IPing、ServerList
    * 作为参照。*/
   Server server = getServer(loadBalancer);
   if (server == null) {throw new IllegalStateException("No instances available for" + serviceId);
   }
   RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
         serviceId), serverIntrospector(serviceId).getMetadata(server));

   return execute(serviceId, ribbonServer, request);
}

通过代码咱们可知,首先创立一个 ILoadBalancer,这个ILoadBalancer 是 Ribbon 的外围类。能够了解成它蕴含了选取服务的规定 (IRule)、服务集群的列表(ServerList)、测验服务是否存活(IPing) 等个性,同时它也具备了依据这些个性从服务集群中选取具体一个服务的能力。
Server server = getServer(loadBalancer);这行代码就是选取举一个具体 server。
最终调用了外部的 execute 办法,该办法代码如下(只保留了外围代码):

@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
   try {
      // 发动调用
      T returnVal = request.apply(serviceInstance);
      statsRecorder.recordStats(returnVal);
      return returnVal;
   }
   catch (IOException ex) {statsRecorder.recordStats(ex);
      throw ex;
   }
   catch (Exception ex) {statsRecorder.recordStats(ex);
      ReflectionUtils.rethrowRuntimeException(ex);
   }
   return null;
}

接下来看下 request.apply(serviceInstance) 办法的具体做了那些事件(LoadBalancerRequestFactory 中):

@Override
public ClientHttpResponse apply(final ServiceInstance instance)
      throws Exception {HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
   // 省略局部代码...
   /**
    * 发动真正申请。*/
   return execution.execute(serviceRequest, body);
}

看到这里整体流程的原理就说完了,接下来咱们联合一张图来回顾下整个过程:

首先获取所有标识 @LoadBalanced 注解的 RestTemplate(能够了解成获取那些开启了 Ribbon 负载平衡性能的RestTemplate),而后将 Ribbon 默认的拦截器LoadBalancerInterceptor 增加到 RestTemplate 中,这样当应用 RestTemplate 发动 http 申请时就会起到拦挡的作用。当有申请发动时,ribbon 默认的拦截器首先会创立 ILoadBalancer(外面蕴含了选取服务的规定(IRule)、服务集群的列表(ServerList)、测验服务是否存活(IPing) 等个性)。在代码层面的含意是加载 RibbonClientConfiguration 配置类)。而后应用 ILoadBalancer 从服务集群中抉择一个服务,最初向这个服务发送申请。

Ribbon 负载平衡规定

参考资料:https://www.jianshu.com/p/79b…

Ribbon 默认负载平衡规定

根据上述 Ribbon 的原理,能够晓得 IRule 接口负责负载平衡的实现,具体如下:

规定名称 特点
AvailabilityFilteringRule 过滤掉始终连贯失败的被标记为 circuit tripped 的后端 Server,并 过滤掉那些高并发的后端 Server 或者应用一个 AvailabilityPredicate 来蕴含过滤 server 的逻辑,其实就是查看 status 里记录的各个 server 的运行状态
BestAvailableRule 抉择一个最小的并发申请的 server,一一考查 server,如果 Server 被 tripped 了,则跳过
RandomRule 随机 抉择一个 Server
ResponseTimeWeightedRule 已废除,作用同 WeightedResponseTimeRule
WeightedResponseTimeRule 权重 依据响应工夫加权,响应工夫越长,权重越小,被选中的可能性越低
RetryRule 对选定的负载平衡策略加上重试机制,在一个配置时间段内当 抉择 Server 不胜利,则始终尝试应用 subRule 的形式抉择一个 可用的 Server
RoundRobinRule 轮询抉择,轮询 index,抉择 index 对应地位的 Server
ZoneAvoidanceRule 默认的负载平衡策略,即复合判断 Server 所在区域的性能和 Server 的可用性 抉择 Server,在没有区域的环境下,相似于轮询(RandomRule)

其中 RandomRule 示意随机策略、RoundRobinRule 示意轮询策略、WeightedResponseTimeRule 示意加权策略、BestAvailableRule 示意申请数起码策略等等

随机源码:

轮询源码:

批改默认的自定义规定

默认是轮询 能够批改为任意的规定

批改为随机算法

  1. 创立具备负载平衡性能的 RestTemplate 实例

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {return new RestTemplate();
    }

    应用 RestTemplate 进行 rest 操作的时候,会主动应用负载平衡策略,它外部会在 RestTemplate 中退出 LoadBalancerInterceptor 这个拦截器,这个拦截器的作用就是应用负载平衡。

    默认状况下会采纳轮询策略,如果心愿采纳其它策略,则指定 IRule 实现,如:

    @Bean
    public IRule ribbonRule() {return new BestAvailableRule();
    }

    这种形式对 OpenFeign 也无效。

批改为依照 Nacos 配置的权重进行负载平衡

  1. 在 nacos 中对集群进行权重的配置

  2. 在我的项目中,抉择应用 NacosRule

Ribbon 实战优化

饥饿加载

Ribbon 默认懒加载,意味着只有在发动调用的时候才会创立客户端

ribbon:
  eager-load:
    # 开启 ribbon 饥饿加载
    enabled: true
    # 配置 user-center 应用 ribbon 饥饿加载,多个应用逗号分隔
    clients: user-center

参数调优

次要调整申请的超时工夫,是否重试

如果业务没有做幂等性的话倡议把重试关掉:ribbon.MaxAutoRetriesNextServer=0

# 从注册核心刷新 servelist 的工夫 默认 30 秒,单位 ms
ribbon.ServerListRefreshInterval=15000
# 申请连贯的超时工夫 默认 1 秒,单位 ms
ribbon.ConnectTimeout=30000
# 申请解决的超时工夫 默认 1 秒,单位 ms
ribbon.ReadTimeout=30000
# 对所有操作申请都进行重试, 不配置这个 MaxAutoRetries 不起作用 默认 false
#ribbon.OkToRetryOnAllOperations=true
# 对以后实例的重试次数 默认 0
# ribbon.MaxAutoRetries=1
# 切换实例的重试次数 默认 1
ribbon.MaxAutoRetriesNextServer=0

如果 MaxAutoRetries=1MaxAutoRetriesNextServer=1申请在 1s 内响应,超过 1 秒先同一个服务器上重试 1 次,如果还是超时或失败,向其余服务上申请重试 1 次。

那么整个 ribbon 申请过程的超时工夫为:ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1)

如果你感觉这篇内容对你挺有有帮忙的话:

  1. 点赞反对下吧,让更多的人也能看到这篇内容(珍藏不点赞,都是耍流氓 -_-)
  2. 欢送在留言区与我分享你的想法,也欢送你在留言区记录你的思考过程。
  3. 感觉不错的话,也能够关注 编程鹿 的集体公众号看更多文章和解说视频(感激大家的激励与反对????????????)

正文完
 0