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

教程全目录「含视频」:https://gitee.com/bingqilinpeishenme/Java-Wiki

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


    1594002129980

订单服务调用商品服务

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


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


    1594002358421

    1594002385509

Ribbon负载均衡简单版实现的流程

  1. RestTemplate发送的请求是服务名称http://nacos-product/product/getProductById/1
  2. 获取@LoadBalanced注解标记的RestTemplate
  3. RestTemplate添加一个拦截器,当使用RestTemplate发起http调用时进行拦截
  4. 根据url中的服务名称 以及自身的负载均衡策略 去订单服务的服务列表中找到一个要调用的ip+端口号 localhost:8802
  5. 访问该目标服务,并获取返回结果

1594002530793

服务列表实际上是个map


image-20210106112037719

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);
}

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


img

首先获取所有标识@LoadBalanced注解的RestTemplate(可以理解成获取那些开启了Ribbon负载均衡功能的RestTemplate),然后将Ribbon默认的拦截器LoadBalancerInterceptor添加到RestTemplate中,这样当使用RestTemplate发起http请求时就会起到拦截的作用。当有请求发起时,ribbon默认的拦截器首先会创建ILoadBalancer(里面包含了选取服务的规则(IRule)、服务集群的列表(ServerList)、检验服务是否存活(IPing)等特性)。在代码层面的含义是加载RibbonClientConfiguration配置类)。然后使用ILoadBalancer从服务集群中选择一个服务,最后向这个服务发送请求。

Ribbon负载均衡规则

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

Ribbon默认负载均衡规则

根据上述Ribbon的原理,可以知道IRule接口负责负载均衡的实现,具体如下:


image-20210105193640996
规则名称 特点
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表示请求数最少策略等等

随机源码:


image-20210105194052894

轮询源码:


image-20210105194240761

修改默认的自定义规则

默认是轮询 可以修改为任意的规则

修改为随机算法

  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中对集群进行权重的配置


    image-20210106152111687

    image-20210106152129819

    image-20210106152146617
  2. 在项目中,选择使用 NacosRule


    image-20210106152259628

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. 觉得不错的话,也可以关注 编程鹿 的个人公众号看更多文章和讲解视频(感谢大家的鼓励与支持)

2021升级版微服务教程6—Ribbon使用+原理+整合Nacos权重+实战优化 一篇搞定的更多相关文章

  1. 【微服务】之四:轻松搞定SpringCloud微服务-负载均衡Ribbon

    对于任何一个高可用高负载的系统来说,负载均衡是一个必不可少的名称.在大型分布式计算体系中,某个服务在单例的情况下,很难应对各种突发情况.因此,负载均衡是为了让系统在性能出现瓶颈或者其中一些出现状态下可 ...

  2. dotnet core 微服务教程

    这个教程主要是对于第一次使用dotnet core开发的同学. 运行环境是在centos 7 , 使用了docker容器. 即这是一篇运行在linux的docker容器上的微服务的简单应用. 一. 安 ...

  3. Docker 微服务教程(搭建真正的网站)

    Docker 是一个容器工具,提供虚拟环境.很多人认为,它改变了我们对软件的认识. 站在 Docker 的角度,软件就是容器的组合:业务逻辑容器.数据库容器.储存容器.队列容器......Docker ...

  4. Docker 微服务教程

    Docker 是一个容器工具,提供虚拟环境.很多人认为,它改变了我们对软件的认识. 站在 Docker 的角度,软件就是容器的组合:业务逻辑容器.数据库容器.储存容器.队列容器......Docker ...

  5. Docker(二):微服务教程

    Docker 是一个容器工具,提供虚拟环境.很多人认为,它改变了我们对软件的认识. 站在 Docker 的角度,软件就是容器的组合:业务逻辑容器.数据库容器.储存容器.队列容器......,Docke ...

  6. Spring Cloud 微服务:Eureka+Zuul+Ribbon+Hystrix+SpringConfig实现流程图

    相信现在已经有很多小伙伴已经或者准备使用springcloud微服务了,接下来为大家搭建一个微服务框架,后期可以自己进行扩展.会提供一个小案例: 服务提供者和服务消费者 ,消费者会调用提供者的服务,新 ...

  7. 【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结

    前言 想说的话 这里已经梳理完Eureka.Ribbon.Feign三大组件的基本原理了,今天做一个总结,里面会有一个比较详细的调用关系流程图. 说明 原创不易,如若转载 请标明来源! 博客地址:一枝 ...

  8. java架构之路-(微服务专题)ribbon的基本使用和内部算法的自我实现

    上次回归: 上次我们主要说了,我们的注册中心nacos的使用,如我们的命名空间.分组.集群.版本等是如何使用的,如果是这样呢?我们现在有三个用户服务和三个订单服务,我们应该如何分发这些请求呢?都请求到 ...

  9. 14 微服务电商【黑马乐优商城】:day02-springcloud(理论篇二:知道什么是SpringCloud)

    本项目的笔记和资料的Download,请点击这一句话自行获取. day01-springboot(理论篇) :day01-springboot(实践篇) day02-springcloud(理论篇一: ...

  10. 14 微服务电商【黑马乐优商城】:day02-springcloud(理论篇四:配置Robbin负载均衡)

    本项目的笔记和资料的Download,请点击这一句话自行获取. day01-springboot(理论篇) :day01-springboot(实践篇) day02-springcloud(理论篇一) ...

随机推荐

  1. sqlserver2000 在查询时产生序号列的办法

    用的是数据库sqlserver2000,唉,有点老了,好处是到处都有,安装方便. select ( select count(*) from temp_gzsphzb as t1 where spid ...

  2. 【转】浅析Sql Server参数化查询

    转载至: http://www.cnblogs.com/lzrabbit/archive/2012/04/21/2460978.html 错误认识1.不需要防止sql注入的地方无需参数化 参数化查询就 ...

  3. 6/17 Sprint3

    首页改进:

  4. ORACLE查询当前资产状态,和另一个数据库联查,(查询重复数据中第一条),子查询作为字段查询

    背景:ORACLE查询当前资产状态,包含资产信息(表1),资产维修状态(表2),资产报废状态(表3) 如下: 资产信息:

  5. 全文检索引擎Solr系列—–全文检索基本原理

    场景:小时候我们都使用过新华字典,妈妈叫你翻开第38页,找到“坑爹”所在的位置,此时你会怎么查呢?毫无疑问,你的眼睛会从38页的第一个字开始从头至尾地扫描,直到找到“坑爹”二字为止.这种搜索方法叫做顺 ...

  6. Linux软链接和硬链接

    Linux中的链接有两种方式,软链接和硬链接.本文试图清晰彻底的解释Linux中软链接和硬链接文件的区别. 1.Linux链接文件 1)软链接文件  软链接又叫符号链接,这个文件包含了另一个文件的路径 ...

  7. “发送至Onenote”惹来的小麻烦(转)

    原文来自 :  http://blog.csdn.net/yiyu_0417/article/details/7864172 [看到这篇文章,我觉得以后我也会遇到这个问题,很有价值我就先拷贝了,留着以 ...

  8. 测者的性能测试手册:快速安装LoadRunner Linux上的Generator

    安装和初始化 安装包 上传Linux.zip(LoadRunner Generator for Linux.zip,后台回复loadrunner获取下载地址),然后通过如下命令: unzip Linu ...

  9. Qt creator使用笔记

    设置头文件的搜索路径编辑项目文件 xxx.pro INCLUDEPATH = /src/doip \ /src/doip/utils \ /src/doip/pduR \ /src/doip/uds1 ...

  10. mac上配置java开发环境

    项目在mac上跑起来的步骤: 1. 访问,https://brew.sh/  装上这个然后  brew install git  brew install maven, settings.xml需要放 ...