微服务通信之ribbon实现原理
前言
上一篇我们知道了feign调用实现负载均衡是通过集成ribbon实现的。也较为详细的了解到了集成的过程。现在我们看一下ribbo是如何实现负载均衡的。写到这里我尚未去阅读源代码,我在这里盲猜一下: 他肯定是有一个从注册中心拉取配置的模块,一个选择调用服务的模块。然后我们就带着这样的指导思想去看源码。
一、ribbo是何时从eurake加载的服务列表?
从上一篇文章我们知道,feign调用实际上调用的是AbstractLoadBalancerAwareClient.executeWithLoadBalancer(...)方法,我们看一下该方法做了那些事情:
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
// 省略
}
}
可以看到上述代码主要是创建了一个负载均衡执行命令类,然后执行请求。我们看看提交请求后执行的具体逻辑:
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
// Use the load balancer
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(...);
// 省略部分代码
}
这里可以看到它使用了RXjava,rxjava主要是利用观察者思想实现的链式调用,其源码的实现稍显复杂,自己使用需要谨慎,借用Uncle Ben的一句话 ”with great power comes great responsibility “。我们关心一下上面的selectServers()方法,从方法名我们可以大致猜出,它是在选择调用的服务。我们看一下此方法的实现:
/**
* Return an Observable that either emits only the single requested server
* or queries the load balancer for the next server on each subscription
*/
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
显然核心逻辑在loadBalancerContext.getServerFromLoadBalancer(...)里面,我们继续向下看:
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
// 省略部分代码
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// Partial URI or no URI Case
// well we have to just get the right instances from lb - or we fall back
if (lb != null){
Server svc = lb.chooseServer(loadBalancerKey);
}
}
// 省略部分代码
return new Server(host, port);
}
非常戏剧性的是,我们看到他选择一个服务是通过ILoadBalancer进行的,那我们的看一下ILoadBalancer是怎么创建的。首先ILoadBalancer是属于Ribbon提供的类,其创建我们先看Ribbon自身的ILoadbalance创建过程,发现其是通过RibbonClientConfiguration的下述方法创建:
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
可以看到上述方法先判断name对应的ILoadBalancer是否存在,不存在就创建了一个。我们看一下这个name代表的是什么:
@RibbonClientName
private String name = "client";
可以看到它取得的是客户端名称,其实就是@FeignClient的name属性的值,这里可以直接告诉你怎么实现动态给这里的name赋值的:在生成feign的代理对象过程的实现,会将feign通过@FeingClient的contentId属性进行Context隔离,在加载配置的时候会将@RibbonClientName属性的@Value属性的环境变量的值设置成当前@FeignClient的name值,具体可以直看NamedContextFactory.createContext()方法。言归正转,ZoneAwareLoadBalancer的构建显然是持有了Config,Rule,Ping,ServerList,ServerListFilter,ServerListUpdater。
现在我们看一下这四个类的分工与职责。
- Config
config 主要是配置了服务名称,服务读取的一些配置比如刷新服务间隔等
- Rule
选用服务的规则,他决定了选用哪个服务。
- Ping
主要用于探测服务列表中的服务是否可用
- ServerList
它主要是提供最新服务列表:包括上线、下线的服务(默认实现DiscoveryEnabledNIWSServerList)
- ServerListFilter
用于过滤服务,do what you want to do.
- ServerListUpdater
用于更新服务列表(当前的默认实现是启动定时器,然后调用ServerList获取服务列表,将当前负载均衡可用服务列表全覆盖)。
知道了上面的职责划分,我们看到ZoneAwareLoadBalancer最终初始化服务列表的方法:
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
servers = serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
updateAllServerList(servers);
}
基于上述我们可以清晰的了解到,获取服务列表发生在ServerList的实现中(serverListImpl.getUpdatedListOfServers())。
二、ribbo配合Eurake的ServerList实现
上面serverListImpl 实现类就是DiscoveryEnabledNIWSServerList,当调用getUpdatedListOfServers()最终调用到了如下所述方法,我们看一下他干了些什么:
// 省略了部分代码
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
EurekaClient eurekaClient = eurekaClientProvider.get();
if (vipAddresses!=null){
for (String vipAddress : vipAddresses.split(",")) {
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
for (InstanceInfo ii : listOfInstanceInfo) {
if (ii.getStatus().equals(InstanceStatus.UP)) {
DiscoveryEnabledServer des = createServer(ii, isSecure, shouldUseIpAddr);
serverList.add(des);
}
}
}
}
return serverList;
}
上述代码主要是通过EurakeClient 获取对应服务实例,然后将服务实例向DiscoveryEnabledServer进行映射。至此从服务列表的获取过程就完成了。
三、 Rule 服务的选择
知道了服务列表的来源,接下来就是最关键的一步了,选择一个服务调用。我们先看一下默认情况使用的是什么规则(RibbonClientConfiguration):
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
显然默认使用的是ZoneAvoidanceRule。 这里我们看一下IRule都有哪些实现,然后阐述一下各个算法实现(这里没有再看源码的必要了,算法就那些):
- ZoneAvoidanceRule
基于可用性与区域的复合预测规则 - RoundRobinRule
轮询 - WeightedResponseTimeRule
根据响应时间选择 - RandomRule
随机规则
小结
总结起来,ribbon的核心组成就是ZoneAwareLoadBalancer的组成。其分工明确,服务列表的维护、服务的可用性、服务的选择都有专门的类去负责。下一篇将会着重讲一下Feign、Ribbon如何做到基于服务的配置隔离的,会适当配合实战,比如怎么自定义一个全局生效的路由规则,自定义一个基于服务名的生效的路由规则等等。
微服务通信之ribbon实现原理的更多相关文章
- 2021升级版微服务教程6—Ribbon使用+原理+整合Nacos权重+实战优化 一篇搞定
2021升级版SpringCloud教程从入门到实战精通「H版&alibaba&链路追踪&日志&事务&锁」 教程全目录「含视频」:https://gitee.c ...
- 如何将 Redis 用于微服务通信的事件存储
来源:Redislabs作者:Martin Forstner 翻译:Kevin (公众号:中间件小哥) 以我的经验,将某些应用拆分成更小的.松耦合的.可协同工作的独立逻辑业务服务会更易于构建和维护.这 ...
- 【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结
前言 想说的话 这里已经梳理完Eureka.Ribbon.Feign三大组件的基本原理了,今天做一个总结,里面会有一个比较详细的调用关系流程图. 说明 原创不易,如若转载 请标明来源! 博客地址:一枝 ...
- 【微服务】之四:轻松搞定SpringCloud微服务-负载均衡Ribbon
对于任何一个高可用高负载的系统来说,负载均衡是一个必不可少的名称.在大型分布式计算体系中,某个服务在单例的情况下,很难应对各种突发情况.因此,负载均衡是为了让系统在性能出现瓶颈或者其中一些出现状态下可 ...
- Spring Cloud 微服务:Eureka+Zuul+Ribbon+Hystrix+SpringConfig实现流程图
相信现在已经有很多小伙伴已经或者准备使用springcloud微服务了,接下来为大家搭建一个微服务框架,后期可以自己进行扩展.会提供一个小案例: 服务提供者和服务消费者 ,消费者会调用提供者的服务,新 ...
- java架构之路-(微服务专题)ribbon的基本使用和内部算法的自我实现
上次回归: 上次我们主要说了,我们的注册中心nacos的使用,如我们的命名空间.分组.集群.版本等是如何使用的,如果是这样呢?我们现在有三个用户服务和三个订单服务,我们应该如何分发这些请求呢?都请求到 ...
- 微服务通信之feign的注册、发现过程
前言 feign 是目前微服务间通信的主流方式,是springCloud中一个非常重要的组件.他涉及到了负载均衡.限流等组件.真正意义上掌握了feign可以说就掌握了微服务. 一.feign的使用 f ...
- 微服务负载均衡 —— ribbon
负载均衡是系统高可用.缓解网络流量和处理能力扩容的重要手段,广义的负载均衡指的是服务端负载均衡,如硬件负载均衡(F5)和软件负载均衡(Nginx).负载均衡设备会维护一份可用的服务器的信息,当客户端请 ...
- SpringCloud微服务(02):Ribbon和Feign组件,实现服务调用的负载均衡
本文源码:GitHub·点这里 || GitEE·点这里 一.Ribbon简介 1.基本概念 Ribbon是一个客户端的负载均衡(Load Balancer,简称LB)器,它提供对大量的HTTP和TC ...
随机推荐
- Sass 教程
什么是Sass 什么是css预处理语言 css预处理语言可以理解为: 开发一种特殊的编程语言, 把css文件作为编译否的结果, 我们在这个编程语言三增加了很多程序的特性, 使开发变得的更加简单 当前流 ...
- 探究"补阶乘大法的本质"——糖水不等式!
废话不多说先来康一条例题: 证明: 下面给出题目的一种解法(我称之为"补阶乘大法"): 思考:为什么补上一个阶乘(准确说不是阶乘,是两个数阶乘的之商)项,放缩后再给去掉,就能达到我 ...
- 062 01 Android 零基础入门 01 Java基础语法 07 Java二维数组 01 二维数组应用
062 01 Android 零基础入门 01 Java基础语法 07 Java二维数组 01 二维数组应用 本文知识点:二维数组应用 二维数组的声明和创建 ? 出现空指针异常 数组的名字指向数组的第 ...
- Python基础笔记2-ruamel.yaml读写yaml文件
上一篇笔记记录了Python中的pyyaml库对yaml文件进行读写,但了解到ruamel.yaml也能对yaml文件进行读写,于是想尝试一下它的用法. 一,注意 这里首先要更正一下网上大部分博客的说 ...
- 多测师讲解python _re模块_高级讲师肖sir
import re# 一.常用方法:# match():从头匹配# search():从整个文本搜索# findall():找到所有符合的# split():分割# sub():替换# group() ...
- 面经手册 · 第13篇《除了JDK、CGLIB,还有3种类代理方式?面试又卡住!》
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 编程学习,先铺宽度还是挖深度? 其实技术宽度与技术深度是相辅相成的,你能了解多少技术 ...
- element中过滤器filters的使用(开发小记)
之前在开发过程中遇到这么一个问题,一串数据需要在el-table中展示,其中含有金额字段,需要将其转换成标准数据格式,即三位一个逗号间隔. 今年刚毕业就上手项目了,第一次接触的Vue,开发经验少,也忘 ...
- MeteoInfoLab脚本示例:创建netCDF文件(合并文件)
在MeteoInfoLab中增加了创建netCDF文件并写入数据的功能,这里利用合并多个netCDF文件为一个新的netCDF文件为例.1.创建一个可写入的netCDF文件对象(下面用ncfile表示 ...
- 制作西北地区地图数据并maskout
1.从全国地图数据中选中西北5省:打开bou2_4p.shp文件添加相应的图层(中国各省的行政区域),选中工具栏中的"通过矩形选择要素"工具,用鼠标点击选择要输出的图元,按住ctr ...
- day19 Pyhton学习 递归函数
# 函数的递归 : 在一个函数的内部调用它自己 # import sys # sys.setrecursionlimit(1000000) # 设置递归的最大深度 # 总结 # 1.递归函数的定义 : ...