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()办法,该办法具体代码如下:

@Overridepublic <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办法,该办法代码如下(只保留了外围代码):

@Overridepublic <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中):

@Overridepublic 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@LoadBalancedpublic RestTemplate restTemplate() {    return new RestTemplate();}

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

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

    @Beanpublic 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秒,单位msribbon.ServerListRefreshInterval=15000# 申请连贯的超时工夫 默认1秒,单位msribbon.ConnectTimeout=30000# 申请解决的超时工夫 默认1秒,单位msribbon.ReadTimeout=30000# 对所有操作申请都进行重试,不配置这个MaxAutoRetries不起作用 默认false#ribbon.OkToRetryOnAllOperations=true# 对以后实例的重试次数 默认0# ribbon.MaxAutoRetries=1# 切换实例的重试次数 默认1ribbon.MaxAutoRetriesNextServer=0
如果MaxAutoRetries=1MaxAutoRetriesNextServer=1申请在1s内响应,超过1秒先同一个服务器上重试1次,如果还是超时或失败,向其余服务上申请重试1次。

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

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

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