继 SpringCloud Feign 之后的第二篇分布式框架文章,同样秉承单周末一个 SpringCloud 组件的大目标为原则
在平常使用 SpringCloud 中,一般会使用 Feign,因为 Feign 内部集成了 Ribbon
但是 Ribbon 又是一个不可忽视的知识点,并且比 Feign 要难很多。列举文章大纲主题
- 如何获取注册中心服务实例
- 非健康服务实例如何下线
- Ribbon 底层原理实现
- 自定义 Ribbon 负载均衡策略
文章使用 SpringCloud Ribbon 源代码 Hoxton.SR9 版本:2.2.6.RELEASE
另外在文章结尾,说了一些看源码过程中的感想,以及 Ribbon 中笔者实现不合理的流程说明
负载均衡是指通过负载均衡策略分配到多个执行单元上,常见的负载均衡方式有两种
Ribbon 是 Netflix 公司开源的一款负载均衡组件,负载均衡的行为在客户端发生,所以属于上述第二种
一般而言,SpringCloud 构建以及使用时,会使用 Ribbon 作为客户端负载均衡工具。但是不会独立使用,而是结合 RestTemplate 以及 Feign 使用,Feign 底层集成了 Ribbon,不用额外的配置,开箱即用
文章为了更贴切 Ribbon 主题,所以使用 RestTemplate 充当网络调用工具
RestTemplate 是 Spring Web 下提供访问第三方 RESTFul Http 接口的网络框架
注册中心选用阿里 Nacos,创建两个服务,生产者集群启动,消费者使用 RestTemplate + Ribbon 调用,调用总体结构如下
生产者代码如下,将服务注册 Nacos,并对外暴露 Http Get 服务
消费者代码如下,将服务注册 Nacos,通过 RestTemplate + Ribbon 发起远程负载均衡调用
RestTemplate 默认是没有负载均衡的,所以需要添加 @LoadBalanced
启动三个生产者实例注册 Nacos,启动并且注册成功如下所示
想要按严格的先后顺序介绍框架原理,而不超前引用尚未介绍过的术语,这几乎是不可能的,笔者尽可能介绍明白
先来看一下 Ribbon 是如何在客户端获取到注册中心运行实例的,这个点在之前是我比较疑惑的内容
服务注册相关的知识点,会放到 Nacos 源码解析说明
先来举个例子,当我们执行一个请求时,肯定要进行负载均衡对吧,这个时候代码跟到负载均衡获取服务列表源码的地方
解释一下上面标黄色框框的地方:
现在想要知道 Ribbon 是如何获取服务实例的就需要跟进 getLoadBalancer()
首先声明一点,getLoadBalancer() 方法的语意是从 Ribbon 父子上下文容器中获取名称为 ribbon-produce,类型为 ILoadBalancer.class 的 Spring Bean
之前在讲 Feign 的时候说过,Ribbon 会为每一个服务提供者创建一个 Spring 父子上下文,这里会从子上下文中获取 Bean
看到这里并没有解决我们的疑惑,以为方法里会有拉取服务列表的代码,然鹅只是返回一个包含了服务实例的 Bean,所以我们只能去跟下这个 Bean 的上下文
我们需要从负载均衡客户端着手,因为默认是 ZoneAwareLoadBalancer,那我们需要跟进它何时被创建,初始化都做了什么事情
ZoneAwareLoadBalancer 是一个根据区域(Zone)来进行负载均衡器,因为如果不同机房跨区域部署服务列表,跨区域的方式访问会产生更高的延迟,ZoneAwareLoadBalancer 就是为了解决此类问题,不过默认都是同一区域
ZoneAwareLoadBalancer 很重要,或者说它代表的 负载均衡路由角色 很重要。进行服务调用前,会使用该类根据负载均衡算法获取可用 Server 进行远程调用,所以我们要掌握创建这个负载均衡客户端时都做了哪些
ZoneAwareLoadBalancer 是在服务第一次被调用时通过子容器创建
@Bean @ConditionalOnMissingBean // RibbonClientConfiguration 被加载,从 IOC 容器中获取对应实例填充到 ZoneAwareLoadBalancer
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
...
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
// 调用父类构造方法
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
在 DynamicServerListLoadBalancer 中调用了父类 BaseLoadBalancer 初始化了一部分配置以及方法,另外自己也初始化了 Server 服务列表等元数据
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
// 调用父类 BaseLoadBalancer 初始化一些配置,包括 Ping(检查服务是否可用)Rule(负载均衡规则)
super(clientConfig, rule, ping);
// 较重要,获取注册中心服务的接口
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
// 初始化步骤分了两步走,第一步在上面,这一步就是其余的初始化
restOfInit(clientConfig);
}
先来说一下 BaseLoadBalancer 中初始化的方法,这里主要对一些重要参数以及 Ping、Rule 赋值,另外根据 IPing 实现类执行定时器,下面介绍 Ping 和 Rule 是什么
方法大致做了以下几件事情:
这里会介绍被填入的 IPing 和 IRule 是什么东东,并且都有哪些实现
IPing 接口负责向 Server 实例发送 ping 请求,判断 Server 是否有响应,以此来判断 Server 是否可用
接口只有一个方法 isAlive,通过实现类完成探测 ping 功能
public interface IPing {
public boolean isAlive(Server server);
}
IPing 实现类如下:
IRule 接口负责根据不用的算法和逻辑处理负载均衡的策略,自带的策略有 7 种,默认 ZoneAvoidanceRule
上面说过,会有两个初始化步骤,刚才只说了一个,接下来说一下 这个其余初始化方法 restOfInit
,虽然取名叫其余初始化,但是就重要性而言,那是相当重要
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
// 初始化服务列表,并启用定时器,对服务列表作出更新
enableAndInitLearnNewServersFeature();
// 更新服务列表,enableAndInitLearnNewServersFeature 中定时器的执行的就是此方法
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
获取服务列表以及定时更新服务列表的代码都在此处,值得仔细看着源码。关注其中更新服务列表方法就阔以了
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);
}
第一个问题兜兜转转,终于要找到如何获取的服务列表了,serverListImpl 实现自 ServerList,因为我们使用的 Nacos 注册中心,所以 ServerList 的具体实现就是 NacosServerList
public interface ServerList<T extends Server> {
public List<T> getInitialListOfServers();
public List<T> getUpdatedListOfServers();
}
ServerList 中只有两个接口方法,分别是 获取初始化服务列表集合、获取更新的服务列表集合,Nacos 实现中两个调用都是一个实现方法,可能设计如此
相当于 Ribbon 提供出接口 ServerList,注册中心开发者们谁想和 Ribbon 集成,那你就实现这个接口吧,到时候 Ribbon 负责调用 ServerList 实现类中的方法实现
Ribbon 和各服务注册中心之间,这种实现方式和 JDBC 与各数据库之间很像
兜兜转转中问题已经明朗,一起总结下注册中心获取服务实例这块内容
首先笔者做了两个 "大胆" 的实验,第一次是对生产者 SpringBoot 项目执行关闭流程,这时候 Nacos 注册中心是 实时感知到并将此服务下该实例删除
证明 Nacos 客户端是有 类似于钩子函数的存在,感知项目停止就会向 Nacos 服务端注销实例。但是这个时候要考虑一件事情,那就是在 暴力 Kill 或者执行关闭操作 的情况下,存在于 Ribbon 客户端服务列表缓存能不能感知
第二次我这边测试流程是这样的,可以极大程度还原生产上使用 Ribbon 可能会遇到的问题
针对于服务列表的维护,在 Ribbon 中有两种方式,都是通过定时任务的形式维护客户端列表缓存
这一块源码都不贴了,放两个源代码位置,感兴趣自己看看就行了
+ DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature
+ BaseLoadBalancer#setupPingTask
如果你面试的时候,面试官问了本小节相关内容,把这两个点都能答出来,基本上 SpringCloud 源码就差不多了
底层原理实现这一块内容,会先说明使用 Ribbon 负载均衡调用远端请求的全过程,然后着重看一下 RandomRule 负载策略底层是如何实现
上面已经说过,ILoadBalancer 是负责负载均衡路由的,内部会使用 IRule 实现类进行负载调用
public interface ILoadBalancer {
public Server chooseServer(Object key);
...
}
chooseServer 流程中调用的就是 IRule 负载策略中的 choose 方法,在方法内部获取一个健康 Server
public Server choose(ILoadBalancer lb, Object key) {
...
Server server = null;
while (server == null) {
...
List<Server> upList = lb.getReachableServers(); // 获取服务列表健康实例
List<Server> allList = lb.getAllServers(); // 获取服务列表全部实例
int serverCount = allList.size(); // 全部实例数量
if (serverCount == 0) { // 全部实例数量为空,返回 null,相当于错误返回
return null;
}
int index = chooseRandomInt(serverCount); // 考虑到效率问题,使用多线程 ThreadLocalRandom 获取随机数
server = upList.get(index); // 获取健康实例
if (server == null) {
// 作者认为出现获取 server 为空,证明服务列表正在调整,但是!这只是暂时的,所以当前释放出了 CPU
Thread.yield();
continue;
}
if (server.isAlive()) { // 服务为健康,返回
return (server);
}
...
}
return server;
}
简单说一下随机策略 choose 中流程
比较简单,有小伙伴可能就问了,如果健康实例小于全部实例怎么办?这种情况下存在两种可能
留下一个思考题:
为什么不直接从健康实例中选择实例呢
如果直接从健康实例列表选择,就能规避下标越界异常,为什么作者要先从全部实例中获取 Server 下标?
这种自定义策略,在框架中都支持的比较友好,根据上面提的问题,我们自定义一款策略
@Slf4j
public class MyRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object key) {
ILoadBalancer loadBalancer = getLoadBalancer();
while (true && ) {
Server server = null;
// 获取已启动并且可访问的服务列表
List<Server> reachableServers = loadBalancer.getReachableServers();
if (CollectionUtils.isEmpty(reachableServers)) return null;
int idx = ThreadLocalRandom.current().nextInt(reachableServers.size());
server = reachableServers.get(idx);
if (server == null || server.isAlive()) {
log.warn("Ribbon 服务实例异常, 获取为空 || 状态不健康");
Thread.yield();
continue;
}
return server;
}
}
... initWithNiwsConfig 不用实现
}
说一下我们自己实现的 MyRule 负载的逻辑:
然后把 MyRule 注册到 SPring IOC 容器中就好了,在初始化时就会代替默认的 Rule 负载规则
在阅读 Ribbon Ping 这一块源代码时,发现了两处个人认为不太合理的地方
setPingInterval 和 setPing 两个方法发生在 BaseLoadBalancer 初始化时,相当于接着上面逻辑继续。先说明执行逻辑,再看下不合理的地方
setupPingTask 用于定期执行对 Server 的 ping 任务,也就是检测 Server 是否可用
个人觉得在 setPingInterval 中没有必要执行 setupPingTask 方法
作出上述结论有以下依据:
另外还有一点,作者感觉代码中调用的方法没有实际意义。和上述类似,都是在 ping 为空时执行了 setPingInterval 方法
以上这两点是笔者跟源码时,发现不妥当的地方,所以在这里占了一些篇幅说明,主要是想表达两点自己的想法给读者朋友
整体来看,文章更 注重表达设计思想以及源码分析,所以阅读文章需要一定的源码功底。同时文章是针对问题而展开叙述,哪怕源码不理解也能有所收获
Ribbon 这块内容从初始化负载均衡客户端 ILoadBalancer 说起,讲述了初始化过程中具体的内容,包括如何开启 IPing 定时器以及服务列表更新定时器
另外通过源码查看到 Ribbon 的服务列表其实是向 Nacos 提供的接口发起服务调用 获取并保存到本地缓存,继而牵引出如何保证不健康实例下线:IPing 定时器和服务更新定时器
文末章节说了下请求 Ribbon 负载均衡的全链路以及如何自己定义一个负载均衡算法。最最后面也说了下自己看源码过程中对 SpringCloud IPing 感觉无意义的代码,当然,不排除是为了别的包集成而留下的
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/LRmyqpknH3K9yU4ew3rcUQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。