从RocketMQ我们学到了什么?(NameServer篇)

微信公众号:IT一刻钟
大型现实非严肃主义现场
一刻钟与你分享优质技术架构与见闻,做一个有剧情的程序员
关注可了解更多精彩内容。问题或建议,请公众号留言。

从RocketMQ我们学到了什么?(NameServer篇)_第1张图片

文章目录

    • 消息调度中心是今天的主角
      • 路由注册
      • 路由剔除
      • 路由发现
    • CAP理论
    • 之所以替换掉Zookeeper
    • 学到了什么?
    • 说在后面的话

在很久很久以前,人们之间的通信方式就是面对面交谈,你说一句,我听一句,虽然简单可靠,但是弊端也很大。
比如,当你成为一个军队的首领,每个属下一有情况就立刻向你汇报,一个还好,但当你的属下有几十个几百个的时候,他们每天不分时间不看场合,都在叽叽喳喳和你汇报情况,那你可能什么都听不到,而且脑袋都要炸掉了。这个时候,你说停,都给我停下,要汇报情况的,去门口排队,一个一个的来,这个就叫做流量削峰,一群人不要一拥而上,都乖乖给我排队去。
然后你就一个接一个的听,听了整整24个小时,实在困的不行,寻思着这样不行呀,如此下去可能就要天妒英才了,于是你又说,来人,发笔和纸,都把要汇报的消息写在纸上,写完后告诉吕秀才,然后听吕秀才的指示,沿着屋里右面墙根,按照指示的位置叠放整齐,汇报的人就可以退下该做啥做啥去吧,等我休息一下,再来看你们的汇报内容,这就叫做异步处理,你终于可以由自己掌控消息获取的进度了,美滋滋的去睡觉了。
而汇报的人把内容写在纸上,叠放好,就可以退下自己做自己该做的事情,而不是一直在门口等待汇报,这个就叫做解耦。
削峰,异步,解耦。这就是消息队列最常用的三大场景。
故事中的下属们,就是消息生产者角色,屋子右面墙根那块地就是消息持久化,吕秀才就是消息调度中心,而你就是消息消费者角色。下属们汇报的消息,应该叠放在哪里,这个消息又应该在哪里才能找到,全靠吕秀才的惊人记忆力,才可以让消息准确的被投放以及消费。

消息调度中心是今天的主角

在RocketMQ里,就有一个角色和吕秀才的作用一样,叫做NameServer,它是整个分布式消息调度的总控制,是RocketMQ的灵魂之所在,倘如没有了它,RocketMQ会分崩离析无法工作。
那么,它是怎么工作的呢?
我们先来看一张RocketMQ屋里架构图:
从RocketMQ我们学到了什么?(NameServer篇)_第2张图片
乱如蜘蛛丝?不要害怕,换句话说,先忘掉这张图吧。

我们来类比一下现实生活,有一个人想要给另外一个人寄快件,那么就需要先由这个人在网上查询有哪些邮局,然后选择其中一个邮局,把快件投递给它,再由邮局配送到目标人。

需要完成这一整个业务流程,首先需要将邮局自身的信息注册到卫星网络上,卫星负责信息的调度,这样发件人就知道有哪些邮局可以选择,收件人通过卫星网络知道快件到了哪个邮局,可以联系邮局沟通适合的配送时间,而邮局则负责接收配送存储快件。
类比RocketMQ简线图就是如下:
从RocketMQ我们学到了什么?(NameServer篇)_第3张图片
Producer:消息⽣产者,⽤于向消息服务器发送消息,就是图中的寄件人。
NameServer:路由注册中⼼,就是图中的卫星。
Broker:消息存储服务器,就是图中的邮局。
Consumer:消息消费者,不是今天的重点,图中未标出,就是收件人。

由此可见,NameServer作为分布式消息队列的协调者,具有信息路由注册与发现的作用。

路由注册

邮局在竣工后,需要与卫星联网,将自己纳入卫星网络管理中,这样就相当于对外宣布,我这个邮局开始运营了,可以收发邮件快递了。
邮局并网之后,如何让卫星持续并及时感知这个邮局在线以及邮局自身信息的调整,使卫星可以随时协调这个邮局呢?这个时候就需要邮局定时向卫星发一条信息:
“哔哔哔————我是邮局C,编号SHC,地址XXXXX,归属中国上海集群,在线,此时此刻2019年3月15日13点21秒”
卫星接收到消息后,拿个小本本记录下来:
“邮局B,BJB,北京,2019年3月15日13点10秒,活着…”
“邮局A,BJA,北京,2019年3月15日13点15秒,活着…”
“邮局C,SHC,上海,2019年3月15日13点21秒,活着…”


上面这个故事,就讲述了NameServer路由注册的基本原理。
NameServer就相当于卫星,内部会维护一个Broker表,用来动态存储Broker的信息。
而Broker就相当于邮局,在启动的时候,会先遍历NameServer列表,依次发起注册请求,保持长连接,然后每隔30秒向NameServer发送心跳包,心跳包中包含BrokerId、Broker地址、Broker名称、Broker所属集群名称等等,然后NameServer接收到心跳包后,会更新时间戳,记录这个Broker的最新存活时间。
NameServer在处理心跳包的时候,存在多个Broker同时操作一张Broker表,为了防止并发修改Broker表导致不安全,路由注册操作引入了ReadWriteLock读写锁,这个设计亮点允许多个消息生产者并发读,保证了消息发送时的高并发,但是同一时刻NameServer只能处理一个Broker心跳包,多个心跳包串行处理。这也是读写锁的经典使用场景,即读多写少。

路由剔除


忽然有一天,邮局C的机房进老鼠了,咬断电源线宕机了,而卫星不知道邮局C业务故障了,依旧将带有邮局C的邮局表信息传给寄件人(生产者),寄件人联系邮局C发送快件,但是邮局C机房宕机,业务暂停,处于瘫痪状态,自然也就无法接收快件了。
另一方面,因为快件未能被邮局C收入,也就无法将快件转交给收件人,顾客们久久等不到自己的快件,纷纷投诉,为此邮局C的管理层备受责难。
于是邮政总局技术部开始研究讨论,怎么让卫星可以感知到邮局“失联了”,从而自动排除故障邮局,将其负责的业务交付给其他正常的邮局处理,这样就不会因为某一个邮局出现问题,而导致这个邮局所管辖的部分业务无法处理。
大家众说纷纭,最后敲定了一个方案,让卫星每隔一段时间扫描邮局信息表,如果发现某个邮局上报信息时间与当时扫描时间之间的差值超过了某个预设的阈值,就判定这个邮局“失联了”,将此邮局信息从邮局表中剔除。这样寄件人查询到的邮局表里都是正常营业的邮局信息。
新功能上线运营后,效果不错,大家再也不用担心因为某个邮局故障而导致业务停滞,又过上了泡茶报纸的生活。

这个故事同样在RocketMQ中上演。
正常情况下,如果Broker关闭,则会与NameServer断开长连接,Netty的通道关闭监听器会监听到连接断开事件,然后会将这个Broker信息剔除掉。
异常情况下,NameServer中有一个定时任务,每隔10秒扫描一下Broker表,如果某个Broker的心跳包最新时间戳距离当前时间超多120秒,也会判定Broker失效并将其移除。

细心的人会发现一个问题,NameServer在清除失活Broker之后,并没有主动通知生产者,生产者每隔30秒会请求NameServer并获取最新的路由表,那么就意味着,消息生产者总会有30秒的延时,无法实时感知Broker服务器的宕机。所以在这个30秒里,生产者依旧会向失活Broker发送消息,那么消息发送的高可用性如何保证呢?
要解决这个问题得首先谈一谈Broker的负载策略,消息发送队列默认采用轮询机制,消息发送时默认选择异常重试机制来保证消息发送的高可用。当Broker宕机后,虽然消息发送者无法第一时间感知Broker 宕机,但是当消息生产者向Broker发送消息返回异常后,消息生产者会选择另外一个Broker上的消息队列,这样就规避了发生故障的Broker,结合重试机制,巧妙实现消息发送的高可用,同时由于不需要NameServer通知众多不固定的生产者,也降低了NameServer实现的复杂性。

在降低NameServer实现复杂性方面,还有一个设计亮点就是NameServer之间是彼此独立无交流的,也就是说NameServer服务器之间在某个时刻的数据并不会完全相同,但是异常重试机制使得这种差异不会造成任何影响。
从RocketMQ我们学到了什么?(NameServer篇)_第4张图片

路由发现

天上的卫星是有限的,不易变的,而地上的寄件人是繁多的,易变的。所以寄件人想要知道有哪些邮局,很明显最适合的方式是向卫星发请求,拉取邮局表信息,而不是等卫星给每个人推送。
所以在RocketMQ中,NameServer是不主动推送会客户端的,而是由客户端拉取主题的最新路由信息。

CAP理论

NameServer作为注册和发现中心,是整个分布式消息队列调度的灵魂,谈及到分布式,就逃不开CAP理论,C是Consistency,A是Availability,P是Partiton Tolerance,对于分布式架构,网络条件不可控,出现网络分区是不可避免的,因此必须具备分区容错性,那么NameServer就是在AP还是CP中选择了,由于NameServer之间相互独立,很明显,是一个AP设计。

之所以替换掉Zookeeper

ZooKeeper为分布式应用程序提供协调服务。那为什么RocketMQ要自己造轮子,开发集群的管理程序呢?因为ZooKeeper的功能很强大,包括自动Master选举等,RocketMQ的架构设计决定了它不需要进行Master选举,用不到这些复杂的功能,只需要一个轻量级的元数据服务器就足够了。
中间件对稳定性要求很高,RocketMQ的NameServer只有很少的代码,容易维护,所以不需要再依赖另一个中间件,从而减少整体维护成本。

学到了什么?

1.长连接编程模型⾥⼼跳的实现原理
2.多线程编程中读写锁的经典使⽤⽅式
3.追求简单⾼效⼜可靠的实现⽅式

说在后面的话

想要研究NameServer源代码的,请点击链接:https://github.com/MrChiu/RocketMQ-Study/tree/release-4.3.2/namesrv
里面附有我标注的注释,易于通读代码


从RocketMQ我们学到了什么?(NameServer篇)_第5张图片

你可能感兴趣的:(RocketMQ,IT一刻钟)