首页 > 生活

深入理解Ribbon原理

更新时间:2025-05-13 13:02:45 阅读: 评论:0

前段时间,笔者为了解决微服务多版本共存调用的问题,笔者深入调研了Ribbon,并将调研月阳炎的内容记录了下来,以供后续查阅。Ribbon是Spring Cloud核心组件之一,它提供的最重要的功能就是负载均衡,和硬件负载均衡F5不同,它的负载均衡是基于客户端的,Zuul网关和Feign可以通过Ribbon轻松的实现服务的负载均衡,同时避免了与业务无关的冗余代码。在这篇文章中,笔者会讲解负载均衡请求调用的流程以及过程中涉及的一些重要接口。

1. Ribbon示例

下面的代码是利用ribbon实现负载均衡。

@Bean@LoadBalancedRestTemplate restTemplate() { ret六和寺urn new RestTemplate();}@Testpublic void productInfo() { String id = UUID.randomUUID().toString(); String url = "shop-product/product/info?id=" + id; Object result = this.restTemplate.getForObject(url, HashMap.class); System.out.println(JsonUtil.BeanToJson(result));}

一个请求如果被Ribbon代理之后会,请求的执行流程如下图所示,接下来笔者会详细讲解泳道图中一些重要的过程

2. @LoadBal嘿店anced原理

在Ribbon示例中可以看到,Ribbon通过一个@LoadBalanced注解就实现了RestTemplate请求的负载均衡,那么他的原理是什么呢?

RestTemplate在发送请求的时候会被ClientHttpRequestInterceptor拦截,LoadBalancerInterceptor是ClientHttpRequestInterceptor的实现类,它的作用就是用于RestTemplate的负载均衡,LoadBalancerInterceptor将负载均衡的核心逻辑交给了loadBalancer,核心代码如下所示

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,final ClientHttpRequestExecution execution) throws IOException {final URI originalUri = request.getURI();String serviceName = 小孩流鼻血originalUri.getHost();Assert.statepahs测试(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));}

@LoadBalanced注解是属于Spring,而不是Ribbon的,Spring在初始化容器的时候,如果检测到Bean被@LoadBalanced注解,Spring会为其设置LoadBalancerInterceptor的拦截器。

@LoadBalanced注解定义如下所示

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic @interface LoadBalanced {}

可以看到这个注解可以注释在类变量(field),方法参数(parameter)上,这个注解也被@Qualifier修饰,目的uwb就是为了Spring容器注入参数的时候,只选择注入被LoadBalanced注解修饰的bean对象,比如LoadBalancerAutoConfiguration如下代码

@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList();3. 获取服务实例列表

Ribbon使用ServerList接口抽象服务实例列表,Ribbon获取服务实例有如下两种方法,可以使用参数{service-name}.ribbon.NIWSServerListClassName进行选择。它的配置可选项目如下

配置参数意义comflix.loadbalancer.ConfigurationBasedServerList使用配置文件comflix.loadbalancer.DiscoveryEnabledNIWSServerList使用注册中心3.1 配置文件

配置参数为comflix.loadbalancer.ConfigurationBasedServerList。在没有使用注册中心的情况下,Ribbon可以通过配置文件手动列举服务实例的地址,它的命令规范是{{服务名}}.ribbon.listOfServers,Ribbon通过ConfigurationBasedServerList类实现配置服务列表,多个服务实例用逗号隔开

spring.application.name=shop-ordershop-product.ribbon.listOfServers=localhost:8001,localhost:8002

使用配置文件是不是意味着服务实例列表就不会不变了呢?不是的,其实还会定时更新

3.2 利用注册中心获取

利用配置文件获取服务实例列表扩展性很差,因为在服务实例上线或者下线的情况下,需要手动修改配置文件,扩展性很低,一易卡个健壮的微服务系统会采用注册中心的方式维护服务的上下线。Ribbon可以使用DiscoveryEnabledNIWSServerList维护和Eureka之间的服务上下线

4. 动态更新服务实例列表

服务实例上下线在微服务系统中是一个非常常见的场景,Ribbon也实现了该功能。Ribbon定时更新的接口抽象为ServerListUpdater。当Ribbon从注册中心获取了服务实例列表之后,Ribbon需要动态更新服务实例列表,抽象接口为ServerListUpdater,更新的方式有两种,一种是通有的人过定时任务定时拉取服务实例列表,另一种是通过Eureka服茉莉精油务事件通知的方式。Ribbon可以通过配置项{service-name}.ribbon.ServerListUpdaterClassName进行选择更新方式,配置可选项目如下所示

配置参数意义comflix.loadbalancer.PollingServerListUpdater定时拉取comflix.niws.loadbalancer.EurekaNotificationServerListUpdater事件通知4.1 定时拉取

Ribbon会使用一个定时任务线程池定时拉取更新数据。

Ribbon也提供了一些参数,用于控制拉取的实现细节。

参数名称意义{service-name}.ribbon.ServerListRefreshInterval更新频率DynamicServerListLoadBalancer.ThreadPoolSize定时更新的线程数目

PollingServerListUpdater只是控制了线程池的动作,但是具体的业务逻辑则是封装在UpdateAction。

4.2 事件通知

和PollingServerListUpdater不同的是,如果注册中心是Eureka,可以采用事件通知的方式,即当Eureka注册中心发生注册信息变更的时候,那么就将消息发送到事件监听者,Ribbon使用EurekaNotificationServerListUpdater实现类进行更新,首先会创建一个Eureka监听器,当接口接受到通知事件之后,会将更新逻辑提交到线程池中执行,更详细的代码如下

public synchronized void start(final UpdateAction updateAction) { if (isActivepareAndSet(false, true)) { this.updateListener = new EurekaEventListener() { @Override public void onEvent(EurekaEvent event) { if (event instanceof CacheRefreshedEvent) { if (!updateQueuedpareAndSet(false, true)) { // if an update is already queued logger.info("an update a精斑检测ction is already queued, returning as no-op"); 小白菜怎么做好吃 return; } try { refreshExecutor.submit(new Runnable() { @Override pub法律咨询公司lic void run() { updateAction.doUpdate(); } }); } catch (Excep挖矿tion e) { 中国矿业大学徐州 updateQueued.set(false); // if submit fails, need to reset updateQu原味内衣网eued to false } 完成时 } } }; //注册事件监听器,省略不重要的代码 } else { logger.ib2ynfo("Update listener already registered, no-op"); }}5. 对服务进行心跳检测

服务列表中的服务实例未必一直都处于可用的状态,Ribbon会对服务实例进行检测,PingerStrategy接口抽象检测的策略,Ribbon默认采用了串行的方式进行检测,如果有必要,我们可以通过该接口实现并行的检测方式。Pinger会定时通过PingerStrategy获取更新的服务实例,并调用监听者。

// 避免在检测过程中服务实例列表发生变更,预先进行复制,代码省略//在线服务实例列表final List<Server> newUpList = new ArrayList<Server>();//发生状态变更的服务实例列表final List<Server> changedServers = new ArrayList<Server>();for (int i = 0; i < numCandidates; i++) { boolean isAlive = results[i]; Server svr = allServers[i]; boolean oldIsAlive = svr.isAlive(); svr.setAlive(isAlive); if (oldIsAlive != isAlive) { changedServers.add(svr); logger.debug("LoadBalancer [{}]: Server [{}] status changed to {}", name, svr.getId(), (isAlive ? "ALIVE" : "DEAD")); } if (isAliv孽欲追击档案之邪杀e) { newUpList.ad北海旅游d(svr); }}

除此之外,还有一个IPing接口,它的目的是检测单个服务的可用性,对于Eureka来说使用的是NIWSDiscoveryPing策略

6. 服务路由

ServerListFilter接口的作用就是从一批接口中选择一些符合条件的接口并返回。接口定义如下所示

它有什么作用呢?比如说上文中笔者谈到的,如果笔者希望得到某个版本的微服务实例,那么这个接口就能派上用场了,但是Ribbon没有这样的实现,如果笔者需要解决该需求就要自己开发接口了。在默认情况下,Ribbon采取了区域优先的过滤策略(ZoneAffinityServerListFilter),也就是说,优先使用和当前调用者一样的区域微服务实例。

7. 负载均衡调度器

从ServerListFilter获取到一个微服务实例集合后,ILoadBalanvr特警4cer需要使用某个策略从集合中选择一个服务实例, 而策略的抽象接口为IRule,如下所示

public interface IRule{ //省略一些不重要的方法 public Server choose(Object key);}

选择服务实例之后,ILoadBalancer在调用过程中,会记录请求的执行结果,比如请求的失败成功情况,调用耗时等,IRule接口也可以根据这些信息决定是否使用某个Server。

Ribbon提供了七种负载均衡策略,默认的负载均衡策略是轮训策略。

名称解释RoundRobinRule轮训策略RandomRule随机策略BestAvailableRule过滤出故障服务器后,选择一个并发量最小的WeightedResponseTimeRule针对响应时间加权轮询AvailabilityFilteringRule可用过滤策略,先过滤出故障的或并发请求大于阈值的一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选出一个;ZoneAvoidanceRule从最佳区域实例集合中选择一个最优性能的服务实例RetryRule选择一个Server,如果失败,重新选择一个Server重试

本文发布于:2023-05-31 02:56:34,感谢您对本站的认可!

本文链接:http://www.ranqi119.com/ge/85/156010.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:原理   Ribbon
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 站长QQ:55-9-10-26|友情:优美诗词|电脑我帮您|扬州装修|369文学|学编程|软件玩家|水木编程|编程频道