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

1. Ribbon示例

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

@Bean
@LoadBalanced
RestTemplate restTemplate() {return new RestTemplate();
}@Test
public void productInfo() {String id = UUID.randomUUID().toString();String url = "http://shop-product/product/info?id=" + id;Object result = this.restTemplate.getForObject(url, HashMap.class);System.out.println(JsonUtil.BeanToJson(result));
}

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

2. @LoadBalanced原理

在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.state(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
@Qualifier
public @interface LoadBalanced {
}

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

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

3. 获取服务实例列表

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

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

3.1 配置文件

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

spring.application.name=shop-order
shop-product.ribbon.listOfServers=http://localhost:8001,http://localhost:8002

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

3.2 利用注册中心获取

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

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

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

配置参数 意义
com.netflix.loadbalancer.PollingServerListUpdater 定时拉取
com.netflix.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 (isActive.compareAndSet(false, true)) {this.updateListener = new EurekaEventListener() {@Overridepublic void onEvent(EurekaEvent event) {if (event instanceof CacheRefreshedEvent) {if (!updateQueued.compareAndSet(false, true)) {  // if an update is already queuedlogger.info("an update action is already queued, returning as no-op");return;}try {refreshExecutor.submit(new Runnable() {@Overridepublic void run() {updateAction.doUpdate();}}); } catch (Exception e) {updateQueued.set(false);  // if submit fails, need to reset updateQueued to false}}}};//注册事件监听器,省略不重要的代码} else {logger.info("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 (isAlive) {newUpList.add(svr);}
}

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

6. 服务路由

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

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

7. 负载均衡调度器

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

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

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

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

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

开启springcloud全家桶5:探索负载均衡组件 Ribbon实现与原理相关推荐

  1. 开启springcloud全家桶:springcloud常见面试题

    微服务面试题 一.Spring Cloud有哪些特点? Spring cloud 流应用程序启动器是基于 Spring Boot 的 Spring 集成应用程序,提供与外部系统的集成.Spring c ...

  2. 开启springcloud全家桶1:springcloud套餐简介

    为什么需要学习Spring Cloud 不论是商业应用还是用户应用,在业务初期都很简单,我们通常会把它实现为单体结构的应用.但是,随着业务逐渐发展,产品思想会变得越来越复杂,单体结构的应用也会越来越复 ...

  3. SpringCloud Alibaba 实战之《负载均衡:Ribbon 如何保证微服务的高可用》

    上一讲我们对 Nacos 的集群环境与实现原理进行了讲解,我们已经可以轻松将单个微服务接入到 Nacos 进行注册,但是微服务本不是孤岛,如何实现有效的服务间稳定通信是本文即将介绍的主要内容,本次我们 ...

  4. 客户端负载均衡Ribbon之一:Spring Cloud Netflix负载均衡组件Ribbon介绍

    Netflix:['netfliːks] ribbon:英[ˈrɪbən]美[ˈrɪbən] n. 带; 绶带; (打印机的) 色带; 带状物; v. 把-撕成条带; 用缎带装饰; 形成带状;     ...

  5. 开启springcloud全家桶2:初探Hystrix原理与实践

    背景 分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务.如下图,对于同步调用,当库存服务不可用时,商品服务请求线程被阻塞,当有大批量请求调用库存服务时,最终可能导致整个商品服 ...

  6. springcloud全家桶之ribbon

    ribbon负载均衡,feign负载均衡,nginx负载均衡.springcloud对dubbo.eureka对dubbo.从阿里巴巴等级的p3到p10. 举例说明,我去麦当劳买吃的,有三个窗口,三个 ...

  7. springcloud 实现反向代理和负载均衡

    springcloud 实现反向代理和负载均衡 1. 搭建注册中心,路由服务zuul,两个服务端 参考我的上一篇博客 springcloud项目搭建 注意两个服务端服务名称要相同,端口不能相同配置如下 ...

  8. JAVA开发(分布式SpringCloud全家桶一些组件读法)

    配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话构成SpringCloud的集合. Eureka服务注册与发现(Eureka:怎么读?(拼音读法:yi rui ka,伊 ...

  9. 使用SpringCloud全家桶中的Feign踩的坑‘Error creating bean with name 'eurekaAutoServiceRegistration'

    在自学SpringCloud全家桶中的Feign的时候踩的坑,启动的时候报如下错误: org.springframework.beans.factory.BeanCreationNotAllowedE ...

最新文章

  1. java 学习 --------接口概念
  2. /proc/net/sockstat 里的信息是什么意思?
  3. 初涉网络流 POJ 1459 Power Network
  4. word-vba-microsoft(中英文)
  5. flannel无法跨主机ping通容器的解决方式
  6. Unicode – CSS中文字体转编码
  7. iPhone 13有搞头,经典功能或将回归!
  8. java8 追加文字到文件_使用Stream-Java 8替换文件中的文本
  9. 我热爱编程,但我讨厌这个行业
  10. BZOJ1007:[HNOI2008]水平可见直线(计算几何)
  11. 动态背景 图层上写文字_文字效果很难吗?教你如何打造绚丽的浮雕文字!
  12. jquery子元素过滤选择器:nth-child、:first-child、:last-child、:only-child
  13. 做开源,兴趣是最好的源动力 | 龙蜥开发者说第1期
  14. 求2020 CFA二级notes资源,谢谢!
  15. pcshare个人版
  16. 统计软件 | Jamovi简介
  17. 第一轮通知 | 5月11-13日,中国肠道大会!
  18. AEMDA: Inferring miRNA-disease associations based on deep autoencoder
  19. C#图形窗口的几种边框样式,固定大小,及可调节大小等样式
  20. 微信开放平台开发(3) 移动应用微信登录

热门文章

  1. 【一文读懂】Spring Bean生命周期详解
  2. 【spark内置函数】lit和typedLit
  3. 扩展kmp入门+比赛模板
  4. Nextcloud提供的缺省文件
  5. 前端常用正则表达式(详细版)
  6. Python模块和文件操作
  7. Command——SQL
  8. 全面!数据分析思路大梳理
  9. 化工人员定位系统提供智能安全保障
  10. 汽车之家搜索算法工程师招聘