Ribbon如何使用

60次阅读
没有评论

共计 9268 个字符,预计需要花费 24 分钟才能阅读完成。

这篇“Ribbon 如何使用”文章的知识点大部分人都不太理解,所以丸趣 TV 小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Ribbon 如何使用”文章吧。

Ribbon 基本使用简介

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

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

Nginx 独立进程做负载均衡,通过负载均衡策略,将请求转发到不同的服务上

客户端负载均衡,通过在客户端保存服务列表信息,然后自己调用负载均衡策略,分摊调用不同的服务

基本使用

Ribbon 的负载均衡有两种方式

和 RestTemplate 结合 Ribbon+RestTemplate

和 OpenFeign 结合

Ribbon 的核心子模块

ribbon-loadbalancer:可以独立使用或者和其他模块一起使用的负载均衡 API

ribbon-core:Ribbon 的核心 API

订单服务集成 Ribbon

订单服务调用商品服务

配置过程 分两步

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

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

配置 RestTemplate

订单服务调用商品服务

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

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

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

RestTemplate 发送的请求是服务名称 http://nacos-product/product/getProductById/1

获取 @LoadBalanced 注解标记的 RestTemplate

RestTemplate 添加一个拦截器,当使用 RestTemplate 发起 http 调用时进行拦截

根据 url 中的服务名称 以及自身的负载均衡策略 去订单服务的服务列表中找到一个要调用的 ip+ 端口号 localhost:8802

访问该目标服务,并获取返回结果

服务列表实际上是个 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()方法,该方法具体代码如下:

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

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

首先获取所有标识 @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 接口负责负载均衡的实现,具体如下:

规则名称特点 AvailabilityFilteringRule 过滤掉一直连接失败的被标记为 circuit tripped 的后端 Server,并 过滤掉那些高并发的后端 Server 或者使用一个 AvailabilityPredicate 来包含过滤 server 的逻辑,其实就是检查 status 里记录的各个 server 的运行状态 BestAvailableRule 选择一个最小的并发请求的 server,逐个考察 server,如果 Server 被 tripped 了,则跳过 RandomRule 随机选择一个 ServerResponseTimeWeightedRule 已废弃,作用同 WeightedResponseTimeRuleWeightedResponseTimeRule 权重根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低 RetryRule 对选定的负载均衡策略加上重试机制,在一个配置时间段内当 选择 Server 不成功,则一直尝试使用 subRule 的方式选择一个 可用的 ServerRoundRobinRule 轮询选择,轮询 index,选择 index 对应位置的 ServerZoneAvoidanceRule 默认的负载均衡策略,即复合判断 Server 所在区域的性能和 Server 的可用性 选择 Server,在没有区域的环境下,类似于轮询(RandomRule)

其中 RandomRule 表示随机策略、RoundRobinRule 表示轮询策略、WeightedResponseTimeRule 表示加权策略、BestAvailableRule 表示请求数最少策略等等

随机源码:

轮询源码:

修改默认的自定义规则

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

修改为随机算法

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

[@Bean](https://my.oschina.net/bean)
@LoadBalanced
public RestTemplate restTemplate() { return new RestTemplate();
}

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

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

[@Bean](https://my.oschina.net/bean)
public IRule ribbonRule() { return new BestAvailableRule();
}

这种方式对 OpenFeign 也有效。

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

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

Ribbon 如何使用

Ribbon 如何使用

Ribbon 如何使用

在项目中,选择使用 NacosRule

Ribbon 如何使用

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)

以上就是关于“Ribbon 如何使用”这篇文章的内容,相信大家都有了一定的了解,希望丸趣 TV 小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注丸趣 TV 行业资讯频道。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-04发表,共计9268字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)