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 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
微服务组件【负载均衡】Netflix Ribbon
Netflix Ribbon是一个客户端负载均衡组件,用于将用户请求根据负载均衡算法负载到后端不同的服务集群节点上,从而降低单点服务器压力。
默 语
2024/11/22
940
【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试
前面文章已经梳理清楚了Eureka相关的概念及源码,接下来开始研究下Ribbon的实现原理。
一枝花算不算浪漫
2020/01/14
7460
【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试
客户端负载均衡Ribbon之源码解析
假设有一个分布式系统,该系统由在不同计算机上运行的许多服务组成。但是,当用户数量很大时,通常会为服务创建多个副本。每个副本都在另一台计算机上运行。此时,出现 “Load Balancer(负载均衡器)”。它有助于在服务器之间平均分配传入流量。
程序员果果
2019/05/28
1.3K0
ribbon源码
这里通过@LoadBalance注解, 引入了ribbon, 自动实现ribbon的负载均衡策略
用户7798898
2020/09/27
6820
ribbon源码
为何一个@LoadBalanced注解就能让RestTemplate拥有负载均衡的能力?【享学Spring Cloud】
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
YourBatman
2019/09/18
1.7K0
Ribbon分析
调用时,直接使用在eureka中注册的服务名进行调用,就可以由ribbon来进行负载均衡了
zhaozhen
2021/07/13
3810
通过 Ribbon 查询 Nacos 服务实例
Nacos 提供了开放 API 可通过 /nacos/v1/ns/instance/list 获取服务列表。如果我们采用 spring-cloud 方式去获取服务,最终会通过 Nacos Client + loadbalancer 的方式进行客户端负载均衡。
没有故事的陈师傅
2021/08/13
1.9K0
RestTemplate的逆袭之路,从发送请求到负载均衡
上篇文章我们详细的介绍了RestTemplate发送请求的问题,熟悉Spring的小伙伴可能会发现:RestTemplate不就是Spring提供的一个发送请求的工具吗?它什么时候具有了实现客户端负载
江南一点雨
2018/04/02
3.3K0
RestTemplate的逆袭之路,从发送请求到负载均衡
微服务架构-实现技术之具体实现工具与框架5:Spring Cloud Feign与Ribbon原理与注意事项
二、FeignClent注解剖析+Spring Cloud Feign基本功能配置解读
全栈程序员站长
2022/08/11
4270
微服务架构-实现技术之具体实现工具与框架5:Spring Cloud Feign与Ribbon原理与注意事项
Spring Cloud系列二:Ribbon
在微服务环境下每个服务实例少则几个,多则上百个,如何让请求均匀分布到各服务实例上是微服务架构下必须解决的一个问题,这方面有2种解决方案:
心平气和
2021/02/26
3830
深入理解Ribbon之源码解析
方志朋
2017/12/29
1.3K0
深入理解Ribbon之源码解析
面试官:说说Ribbon是如何实现负载均衡的?
Ribbon的作用是负载均衡,但是根据我面试他人的情况来看,很多人只忙于业务,而不清楚具体的底层原理,在面试中是很容易吃亏的。基于此,本文就来分析一下这里面的请求流程,里面贴的源码会比较多,如果看不惯的话,可以直接看最后的总结。
业余草
2020/09/18
1.8K0
SpringCloudRibbon负载均衡实现原理
在SpringCloud中,我们最常使用到的负载均衡库就是ribbon。使用方式,一般是通过自动配置类注入,然后在类中定义负载均衡实例bean
tunsuy
2022/10/27
3420
Spring Cloud微服务技术栈(五):客户端负载均衡Spring Cloud Ribbon部分源码分析
为了使客户端具备负载均衡的能力,我们在代码中将RestTemplate交给Spring管理的时候,会加上@LoadBalanced注解,如下代码所示:
itlemon
2020/04/03
7020
Spring Cloud微服务技术栈(五):客户端负载均衡Spring Cloud Ribbon部分源码分析
还没有秃头吗?你真的需要大牛来教你如何深入解析Ribbon源码了
Ribbon 的 源 码 解 析 我 们 从 @LoadBalanced 开 始 讲 起 , 添 加@LoadBalanced注解后AsyncRestTemplate就具备了负载均衡的能力,代码如下:
愿天堂没有BUG
2022/10/28
4160
还没有秃头吗?你真的需要大牛来教你如何深入解析Ribbon源码了
SpringCloud Nacos + Ribbon 调用服务的 2 种方法!
在 Nacos 中,服务调用主要是通过 RestTemplate + Ribbon 实现的,RestTemplate 是 Spring 提供的 Restful 请求实现类,而 Ribbon 是客户端负载均衡器,通过 Ribbon 可以获取服务实例的具体信息(IP 和端口号),之后再通过 RestTemplate 加服务实例的具体信息就可以完成一次服务调用了。 ​
磊哥
2022/05/10
2.4K0
SpringCloud Nacos + Ribbon 调用服务的 2 种方法!
Eurkea,Ribbon和RestTemplate是如何结合到一起完成服务注册与发现功能的? --上
ResrTemplate组件是用来完成远程调用功能的,而Ribbon组件负责完成客户端负载均衡功能的,Eurkea服务端负责保存服务名和真实服务器地址的映射关系的,如果我们想要这三者结合起来完成服务发现与注册功能,有一个很简单的思路如下:
大忽悠爱学习
2023/02/13
6220
Eurkea,Ribbon和RestTemplate是如何结合到一起完成服务注册与发现功能的? --上
【一起学源码-微服务】Ribbon 源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析
上一讲讲了Ribbon的基础知识,通过一个简单的demo看了下Ribbon的负载均衡,我们在RestTemplate上加了@LoadBalanced注解后,就能够自动的负载均衡了。
一枝花算不算浪漫
2020/01/14
1.1K0
【一起学源码-微服务】Ribbon 源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析
重学SpringCloud系列二之服务注册与发现---上
为了保障微服务的高可用,提供更高的并发能力,通常需要部署多份实现集群部署。为了快速的让大家入手Spring Cloud 微服务架构,此文只做简化版的服务注册中心的构建。后续我们会讲集群部署。
大忽悠爱学习
2022/05/07
9910
重学SpringCloud系列二之服务注册与发现---上
springcloud系列之-ribbon使用及原理讲解
本章节将要学习springcloud的组件ribbon的使用,ribbon是一个实现了客户端负载均衡的工具,透明的实现了负载均衡策略,我们只需要在resttemplate加上loadbalenced注解就可以开启负载均衡,非常方便,学完这节后,你将会知道如何在你的项目上去使用这个小工具
AI码师
2020/11/19
8480
springcloud系列之-ribbon使用及原理讲解
推荐阅读
相关推荐
微服务组件【负载均衡】Netflix Ribbon
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验