Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >2021升级版微服务教程6—Ribbon使用+原理+整合Nacos权重+实战优化 一篇搞定

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

原创
作者头像
鹿老师的Java笔记
修改于 2021-01-14 10:10:15
修改于 2021-01-14 10:10:15
2K00
代码可运行
举报
运行总次数:0
代码可运行

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

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

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集合当中,具体源码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

具体源码位置是在LoadBalancerAutoConfiguration中。

RestTemplate添加一个拦截器

拦截器不是Ribbon的功能

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

定义一个拦截器

实现ClientHttpRequestInterceptor接口就具备了拦截请求的功能,该接口源码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ClientHttpRequestInterceptor {
    /**
     *实现该方法,在该方法内完成拦截请求后的逻辑内容。
     *对于ribbon而言,在该方法内完成了根据具体规则从
     *服务集群中选取一个服务,并向该服务发起请求的操作。
     */
   ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;

}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中提供了获取以及添加拦截器的方法,具体源码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)省略部分代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()方法,该方法具体代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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方法,该方法代码如下(只保留了核心代码):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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中):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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

  1. 在项目中,选择使用 NacosRule

image-20210106152259628

Ribbon实战优化

饥饿加载

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ribbon:
  eager-load:
    # 开启ribbon饥饿加载
    enabled: true
    # 配置user-center使用ribbon饥饿加载,多个使用逗号分隔
    clients: user-center

参数调优

主要调整请求的超时时间,是否重试

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 从注册中心刷新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. 觉得不错的话,也可以关注 编程鹿 的个人公众号看更多文章和讲解视频(感谢大家的鼓励与支持🌹🌹🌹)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
整理常见 DOM 操作
整理常见 DOM 操作 ⭐️ 更多前端技术和知识点,搜索订阅号 JS 菌 订阅 框架用多了,你还记得那些操作 DOM 的纯 JS 语法吗?看看这篇文章,来回顾一下~ ? 操作 className ad
JS菌
2019/05/06
1.1K0
一个非常实用的CSS小技巧,帮你应对各种场景
图中一个容器内有多个内容块,每块都有一个底部的下划线,但是一般为了美观,最后一个内容块儿的下划线是要去掉的
JowayYoung
2020/12/17
4810
一个非常实用的CSS小技巧,帮你应对各种场景
Virtual DOM到底有什么迷人之处?如何搭建一款迷你版Virtual DOM库?
我们可以仿照snabbdom库https://github.com/snabbdom/snabbdom.git自己动手实现一款迷你版Virtual DOM库。
Vam的金豆之路
2021/12/01
3470
Virtual DOM到底有什么迷人之处?如何搭建一款迷你版Virtual DOM库?
一起来做一个json格式化工具吧
说到json格式化你肯定很熟悉,毕竟压缩后的json数据基本不可读,为了方便查看,我们可以在编辑器中可以通过插件一键格式化,也可以通过一些在线工具来美化,当然,有时在开发中也会遇到json格式化的需求,有很多开源库或组件能我们解决这个问题,不过并不妨碍我们自己实现一个。
街角小林
2023/07/09
4680
一起来做一个json格式化工具吧
DOM操作笔记
DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。
bamboo
2019/01/29
1.1K0
DOM操作笔记
分享一个输入框的打字特效
最近发现友链的两位朋友的博客都有这个特效,后来对比了下这两个人的博客主题结构,就把这个特效扒了出来,鉴于今天有三个人都问了我这个效果怎么设置,故分享之。
泽泽社长
2023/04/17
5150
jQuery VS JavaScript原生API
如今技术日新月异,各类框架库也是层次不穷。即便当年漫山红遍的JQuery(让开发者write less, do more,So Perfect!!)如今也有被替代的大势。但JS原生API写法依旧;并且有时候只不过小写一个Demo,或者产品中只有少量的前端效果或DOM操作,就去花时间&空间引入jQuery,或者React?不免有取宰牛之刀以杀鸡之嫌。 在jQuery的温柔乡里,是否还能记得原生她javascript原生?如果仅为使用个选择器($)或者类似的东西,是否真的有必要加载jQuery?故此了解下JS常
晚晴幽草轩轩主
2018/03/27
1.7K0
你可能不需要 jQuery!使用原生 JavaScript 进行开发
  很多的 JavaScript 开发人员,包括我在内,都很喜欢 jQuery。因为它的简单,因为它有很多丰富的插件可供使用,和其它优秀的工具一样,jQuery 让我们开发人员能够更轻松的开发网站和 Web 应用。
跟着阿笨一起玩NET
2018/09/19
1.2K0
你可能不需要 jQuery!使用原生 JavaScript 进行开发
JavaScript DOM(二)
通过上文可知获取元素可以来利用 DOM 提供的方法来获取元素,如 getElementById、querySelector 等方法,但是也可以利用节点关系来获取元素
赤蓝紫
2023/01/01
6680
JavaScript DOM(二)
掌握JavaScript DOM操作:轻松打造动态网页
在现代Web开发中,JavaScript无疑是不可或缺的一部分。而DOM(文档对象模型)操作则是JavaScript的核心功能之一。DOM允许JavaScript与HTML文档进行交互,从而实现动态网页效果。本文将带你从零开始,逐步掌握JavaScript DOM操作,让你在Web开发中游刃有余。
Front_Yue
2024/09/10
2710
掌握JavaScript DOM操作:轻松打造动态网页
前端day13-JS(WebApi)学习笔记(attribute语法、DOM节点操作)
小技巧:如果API写的是Emement复数的形式,也就是后面加了s(Emements)那么它返回的就是一个伪数组 否则就是单个对象,一般只有id才会是单个对象,其他方式获取(标签名 类名)都是伪数组.
帅的一麻皮
2020/04/19
3.2K0
前端day13-JS(WebApi)学习笔记(attribute语法、DOM节点操作)
jQuery 写法示例
选择元素 $('.box') // CSS3 选择器 $('.el', $parent) $().add('.el1').add('.el2') $('.box').filter(':visible') $('.box').filter(function(){ var $this = $(this) return $this.index() > 3 && $this.hasClass('xxx') }) $('.box').find('.box-header') $('.box').close
前端GoGoGo
2018/08/24
8930
评论框加入七彩打字动画
对于 本主题,依次进入 控制台 - 外观 - 设置外观 - 主题自定义扩展,将以下代码加入到 自定义 HTML 元素拓展 - 在 body 标签结束前。其他主题,加入到主题对应的 footer.php 中 标签结束前。 [collapse title="JavaScript Code" show="true"]
季春二九
2023/03/22
5980
评论框加入七彩打字动画
jQuery手机端上拉下拉刷新页面源码
<!DOCTYPE html> <html> <head> <title>jQuery手机端上拉下拉刷新页面代码 - 蚂蚁社区-开源源码社区</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, max
用户5997198
2019/08/09
2.4K0
jQuery手机端上拉下拉刷新页面源码
webapi(一)初识DOM&定时器
含义: 将HTML文档以树状结构直观地表现出来 作用: 直观地体现了标签与标签之间的关系
且陶陶
2023/04/12
5600
webapi(一)初识DOM&定时器
Typecho评论打字的时候出现炫酷打字跳动闪动效果
老蒋这几天在比较专注Typecho CMS的一些文档,看到有些朋友用的Typecho搭建的个人博客还是非常不错的,有些酷炫的效果感觉我也要去使用的。比如我们在他们的博客评论留言打字的时候,有跳动酷炫的打字效果,这个是如何加进去的?
老蒋
2021/12/24
9180
节点操作
网页中的所有内容都是节点(标签、属性、文本、注释等),在DOM 中,节点使用 node 来表示。
星辰_大海
2020/09/30
1.2K0
前端架构师之10_JavaScript_DOM
第1级DOM(DOM Level 1,或DOM1)。为XML和HTML文档中的元素、节点、属性等提供了必备的属性和方法。结合了Netscape及微软公司开发的DHTML(动态HTML)思想。
张哥编程
2024/12/13
2300
javascript之DOM操作
http://www.cnblogs.com/kissdodog/archive/2012/12/25/2833213.html
bear_fish
2018/09/19
5690
CSS魔法堂:那个被我们忽略的outline
 在CSS魔法堂:改变单选框颜色就这么吹毛求疵!中我们要模拟原生单选框通过Tab键获得焦点的效果,这里涉及到一个常常被忽略的属性——outline,由于之前对其印象确实有些模糊,于是本文打算对其进行稍微深入的研究^_^
^_^肥仔John
2018/10/11
7930
CSS魔法堂:那个被我们忽略的outline
推荐阅读
相关推荐
整理常见 DOM 操作
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验