程序地带

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


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



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



Ribbon使用+原理+整合Nacos权重+实战优化 一篇搞定
Ribbon基本使用
简介

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


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


Nginx独立进程做负载均衡,通过负载均衡策略,将请求转发到不同的服务上客户端负载均衡,通过在客户端保存服务列表信息,然后自己调用负载均衡策略,分摊调用不同的服务
基本使用

Ribbon的负载均衡有两种方式


和 RestTemplate 结合 Ribbon+RestTemplate和 OpenFeign 结合

Ribbon的核心子模块


ribbon-loadbalancer:可以独立使用或者和其他模块一起使用的负载均衡APIribbon-core:Ribbon的核心API
订单服务集成Ribbon

订单服务调用商品服务


配置过程 分两步


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


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

配置 RestTemplate


15940021299801594002129980

订单服务调用商品服务


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


15940023473931594002347393

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


15940023584211594002358421
15940023855091594002385509

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


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

服务列表实际上是个map


image-20210106112037719image-20210106112037719
Ribbon负载均衡原理 [了解]
获取@LoadBalanced注解标记的RestTemplate。

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


@[email protected](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);}

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


imgimg

首先获取所有标识@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-20210105193640996image-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-20210105194052894image-20210105194052894

轮询源码:


image-20210105194240761image-20210105194240761
修改默认的自定义规则

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


修改为随机算法


创建具有负载均衡功能的RestTemplate实例


@[email protected] RestTemplate restTemplate() {    return new RestTemplate();}

使用RestTemplate进行rest操作的时候,会自动使用负载均衡策略,它内部会在RestTemplate中加入LoadBalancerInterceptor这个拦截器,这个拦截器的作用就是使用负载均衡。


默认情况下会采用轮询策略,如果希望采用其它策略,则指定IRule实现,如:


@Beanpublic IRule ribbonRule() {    return new BestAvailableRule();}

这种方式对OpenFeign也有效。


修改为按照Nacos配置的权重进行负载均衡


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


image-20210106152111687image-20210106152111687
image-20210106152129819image-20210106152129819
image-20210106152146617image-20210106152146617

在项目中,选择使用 NacosRule


image-20210106152259628image-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=1和MaxAutoRetriesNextServer=1请求在1s内响应,超过1秒先同一个服务器上重试1次,如果还是超时或失败,向其他服务上请求重试1次。


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


如果你觉得这篇内容对你挺有有帮助的话:


点赞支持下吧,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)


欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。


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



版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/bingyang-py/p/14277881.html

随机推荐

webpack配置踩坑记(一),style-loader失效

配置webpack时,使用style-loader时候,发现没有将样式添加到head中。开始我的配置如下:constpath=require("...

不愿透露姓名的余菜鸟 阅读(130)

Windows 配置Python环境

Windows 配置Python环境

下载Python官网下载根据自己的需求去下载对应的版本安装Python接下来默认下一步验证win+R输入cmd打开终端,输入python,进入python命令行模式...

钱甫新 阅读(177)

如何关闭谷歌默认浏览器检查提示

如何关闭谷歌默认浏览器检查提示

打开设置,搜索“保存付款方式”,关闭"允许网站检查您是否已保存付款方式"即可,截图如下:参考:https://blog.csdn.net/txy_2018/article/detail...

知北行 阅读(211)

HBase环境搭建(伪分布式)

目录1、前置条件2、HBase上传解压3、配置环境变量3.1配置系统环境3.2配置hbase-env.sh3.3配置hbase-site.xml3.4修改regionservers文件4、启动5、查看...

小财迷,嘻嘻 阅读(964)

【面向对象3大特点】

封装性封装是面向对象的方法应该遵循的一个重要原则,它有2个含义:①把对象的属性和行为看成一个密不可分的整体,把这两者(对象的属性和行为)封装在不可分割的对象当...

吖不羡仙 阅读(485)

生成ltx文件命令_Linux lsof命令详解

生成ltx文件命令_Linux lsof命令详解

请关注本头条号,每天坚持更新原创干货技术文章如需学习视频,请查看本头条号简介,免费在线观看学习视频1.了解lsof命令作用lsof(listopenfiles...

墨门-光磊 阅读(200)