关于 RocketMQ 的高可用
注:4.5.0 版本
整体架构
消息中间件的主要作用是在生产者与消费者之间起到异步通信、功能解耦及削峰填谷的效果。
对于 RocketMQ,服务端的模块包括 NameServer、Brocker 及存储系统,客户端的角色包括生产者与消费者。
就消息生命周期的视角,其工作原理的要点如下:
1.生产者从 NameServer 中同步 Topic 消息,获取路由信息
2.生产者选择一个消息队列,向 Brocker 发送消息
3.Brocker 在文件系统中存储生产者的消息
4.消费者以广播或集群方式从 Brocker 消费消息,同时记录文件系统的消费进度
高可用设计
本文高可用的定义:RocketMQ 正常(合理时间内产生合适的响应)提供消息服务能力。
从模块的角度,需要考虑 NameServer、Brocker 及存储系统的高可用;从流程的视角,需要考虑消息发送及消息消费的高可用。下文从这几个方面分析。
NameServer
NameServer 的作用相当于 Kafka 中的 Zookeeper,故本小节着重描述下其解决的问题及工作原理。
解决的问题
消息生产者向 Broker 发送消息,消息消费者通过 Broker 消费消息,实现功能解耦。
为了保障服务端的高可用性,避免单点故障,通常会部署多个 Broker,此时的问题是如果其中一个 Broker 宕机,生产者如何在不重启的情况下感知到?从而保障功能可用。
NameServer 模块即是用于解决这个而设计的。
工作原理
NameServer 与 Broker 之间保持长连接,NameServer 每隔一段时间检测 Broker 的最近一次上报心跳时间是否超出预设时间以判断是否存活,如果已宕机,则将其移出注册中心表,并更新路由规则。
但此时 NameServer 并不会通知消息生产者,而是需要消息生产者定期同步。
所以为什么不是主动通知消息生产者,以达到更高的消息吞吐量?
这样做的主要是为了降低 NameServer 的设计复杂性,如需要考虑推送哪些消息客户端,推送失败如何处理及不同客户端由于时延导致的不一致性等等。
具体的设计要点包括:
1)Broker 每隔 30S 向 NameServer 集群中每一个节点上报心跳并同步 Topic 路由等信息;
2)消息客户端每隔 30S 向 NameServer 拉取路由信息;
3)NameServer 收到 Broker 上报的时间戳及路由信息后更新对应数据;
4)NameServer 定期(10S)检查 Broker 是否失效(>120S 没有收到心跳),则更新路由信息,移出失效的 Broker。
高可用机制
集群方式保障高可用性,即部署多个 NameServer 实例。
但实例之间并不通信。
==> 由此造成的问题是存在某一时刻不同实例的路由信息并不完全相同的情况。
此问题的具体影响是消息生产者获取到的路由信息中可能包含已经宕机的 Broker,从而导致消息发送失败,所以消息发送是否不是高可用的?我们在消息发送小节说明。
Brocker
Broker 是消息中间件的核心组件,承接消息生产者发送的消息,并流转至消息消费者,其高可用性至关重要,一个关键的问题是 Broker 宕机情况下如何正常提供服务?
在 RocketMQ 4.5.0 版本前,仅提供了主从同步机制,可以保障消费者在主节点宕机的情况下继续从从节点消费(读),但随之而来的问题是,可用的写节点减少后,会导致整个集群的写负载能力受到影响,进而影响到集群的高可用性。
所以一个想法是在主节点宕机情况下,能否用从节点代替主节点承载写能力,以提升集群的可用性?
基于此考虑,RocketMQ 4.5.0 版本及以后提供了主从切换机制。
RocketMQ 的主从切换是通过 Rocket DLedger 实现的,其底层算法基于 Raft 协议,主要包括 Leader 选举与日志复制两个方面。
其中 Leader 选举基于大多数原则,通过随机心跳时间的方式减小重试次数;日志复制则采用类似两阶段提交的方式,领导者在收到大多数跟随者的成功响应后,即返回复制成功。
对于 Rocket DLedger 具体如何实现及在 RocketMQ 中如何应用本文不再展开,谨以此小节抛砖引玉。
存储系统
在存储系统方面,RocketMQ 实现高可用性的关键策略包括以下几个方面:
日志复制:RocketMQ 使用日志文件来存储消息数据,并采用主从复制机制实现数据的冗余备份。每个主节点都有对应的一个或多个从节点,主节点将写入的消息数据异步复制到从节点上。这种主从复制的方式确保了即使主节点故障,数据依然可以从从节点中恢复和提供服务。
异步刷盘:RocketMQ 采用异步刷盘(Async Flush)的方式将内存中的消息数据定期刷写到磁盘上。这样做可以提高写入性能,并减少 IO 阻塞对消息发送的影响。同时,通过合理的刷盘策略和配置,系统可以在保证一定的容灾能力的前提下,提供较高的性能和吞吐量。
集群化部署:RocketMQ 支持以集群的方式部署 Broker 节点,每个 Broker 节点负责存储一部分消息数据。这样,即使某个节点发生故障,其他正常运行的节点仍然可以提供服务,确保数据的可用性和持久化。
快速故障切换:当 RocketMQ 的主节点发生故障或不可用时,系统会自动进行快速故障切换,将一个从节点晋升为新的主节点。这种自动切换机制保证了数据持久化的连续性和可用性。
磁盘容量管理:RocketMQ 提供了磁盘容量管理机制,可以根据配置设置限制每个 Broker 节点上存储消息数据的最大容量。当达到限制后,系统会根据一定的策略进行消息的删除或归档,以释放空间并保证系统的正常运行。
通过以上策略,RocketMQ 在数据持久化方面实现了高可用性。采用日志复制、异步刷盘、集群化部署等机制来保证数据冗余备份、减少 IO 阻塞和单点故障的风险。同时,快速故障切换和磁盘容量管理等措施进一步提升了系统的可持续运行能力和数据的可靠性。
消息发送
消息发送者在自动发现主题的路由信息后,RocketMQ 默认使用轮询算法进行路由的负载均衡。
RocketMQ 为了实现消息发送的高可用性,引入了两个非常重要的特性:
1)消息发送重试策略
RocketMQ 在发送消息时如果出现了失败,默认会重试两次。
2)故障规避机制
当消息第一次发送失败时,会在重试时避开刚才发送失败的 Broker,选择其他 Broker 上的队列进行重试以提高消息发送的成功率。
其核心思想在于:如果第一次发送失败是由于 Broker 宕机引起的,那么如果继续重试该 Broker,大概率还是会失败,重试就失去了意义。
消息消费
消息消费流程中涉及的高可用问题主要有两点:
1.消息的消费是否需要保障某一主题(Topic)的全局顺序消费
2.消息消费的过程中如果 Broker 宕机,如何保障消息不会丢失
针对上述两方面问题,分别在消费模式及主从同步小节说明。
消费模式
消费的消费模式分为广播模式与集群模式,其中
广播模式下某一主题下的任一条消息将被集群内的所有消费者消费一次,每个消费者根据业务不同实现其各自功能;
而在集群模式下,消息消费的通用逻辑是:消息队列与消费者是 N:1 的关系,即一个消息队列同一时间仅允许被一个消费者消费,但一个消费者可以消费多个消息队列。
广播模式下,如果允许重复消费,而且消费者实例数大于 1,则消费的高可用性可以得到很好的保障;
集群模式下,RocketMQ 仅支持某个分区下的顺序消费,如果要做到 Topic 下的顺序性,则需要将该 Topic 的队列数置为 1,高可用性无法保障。
主从同步
消费者从 Broker 获取消息,如果 Broker 宕机,消费者如何消费消息?
RocketMQ 提供主从同步机制解决这一问题。
具体而言,消息消费进度到达主节点后,会将消息同步到消息从节点,如果主节点宕机,消息消费者可以从从节点继续消费。
引申问题:主节点恢复后,消费进度在主从节点如何同步?
RocketMQ 设计的机制中,元数据只能由从节点主动拉取,主节点即使宕机后重启也不会从从节点同步数据;
但 RocketMQ 额外的消费进度同步机制:Broker 在收到客户端后,如果拉取的请求中包含消费端的消费进度,则使用该进度覆盖服务端的消费进度。
以上。
如果本文对您有帮助,欢迎关注并转发~
声明:本文言论仅代表个人观点。
版权声明: 本文为 InfoQ 作者【K】的原创文章。
原文链接:【http://xie.infoq.cn/article/20fd9465afce711175b643289】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论