RocketMQ - 高可用设计

发布于: 2020 年 05 月 31 日

计算机系统的可用性用平均无故障时间来度量,系统的可用性越高,则平均无故障时间越长。高可用性也是分布式中间件的重要特性,RocketMQ的高可用设计主要有四个方面的体现:

  • 消息发送的高可用:在消息发送时可能会遇到网络问题、Broker宕机等情况,而NameServer检测Broker是有延迟的,虽然NameServer每隔10秒会扫描所有Broker信息,但要Broker的最后心跳时间超过120秒以上才认为Broker不可用,所以Producer不能及时感知Broker下线。如果在这期间消息一直发送失败,那么消息发送失败率会很高,这在业务上是无法接收的。这里大家可能会有一个疑问,为什么NameServer不及时检查Broker和通知Producer? 这是因为那样做会使网络通信和架构设计变得非常复杂,而NameServer的设计初衷就是尽可能简单,所以这块的高可用方案在Producer中来实现。RocketMQ采用了一些发送端的高可用方案,来解决发送失败的问题,其中最重要的两个设计是重试机制和故障延迟机制。

  • 消息存储的高可用:在RocketMQ中消息存储的高可用体现在发送成功的消息不能丢、Broker不能发生单点故障,出现Broker异常宕机、操作系统Crash、机房断电或者断网等情况保证数据不丢。RocketMQ主要通过消息持久化、主从复制、读写分离机制来保证。

  • 消息消费的高可用:实际业务场景中无法避免消费消息失败的情况,可能由于网络原因导致,也可能由于业务逻辑错误导致。但无论发生什么情况,即使消息一直消费失败,也不能丢失消息数据。RocketMQ主要通过消费重试机制和消息ACK机制来保证。

  • 集群管理的高可用:集群管理的高可用主要体现在NameServer的设计上,当部分NameServer节点宕机时不会有什么糟糕的影响,只剩一个NameServer节点RocketMQ集群也能正常运行,即使NameServer全部宕机,也不影响已经运行的Broker、Producer和Consumer。

消息发送重试机制

重试机制比较简单,在消息发送出现异常时会尝试再次发送,默认最多重试三次。重试机制仅支持同步发送方式,不支持异步和单向发送方式。根据发送失败的异常类型处理策略略有不同。

如果是网络异常RemotingException和客户端异常MQClientException会重试,而Broker服务端异常MQBrokerException和线程中断异常InterruptedException则不会再重试,且抛出异常。

故障规避机制

在介绍NameServer时提到,NameServer为了简化和客户端通信,发现Broker故障时并不会立即通知客户端。故障规避机制用来解决当Broker出现故障,Producer不能及时感知而导致消息发送失败的问题。默认是不开启的,如果在开启的情况下,消息发送失败的时候会将失败的Broker暂时排除在队列选择列表外。规避时间是衰减的,如果Broker一直不可用,会被NameServer检测到并在Producer更新路由信息时进行剔除。

在选择查找路由时,选择消息队列的关键步骤如下:

  • 先按轮询算法选择一个消息队列

  • 从故障列表判断该消息队列是否可用

判断消息队列是否可用有两个步骤:

  • 判断其是否在故障列表中,不在故障列表中代表可用。

  • 在故障列表faultItemTable中还需要判断当前时间是否大于等于故障规避的开始时间startTimestamp,使用这个时间判断是因为通常故障时间是有限制的,Broker宕机之后会有相关运维去恢复。

这部分重点在于故障机器FaultItem在什么场景下进入故障列表faultItemTable中,消息发送失败时就可能使机器故障了。

主从复制

RocketMQ为了提高消息消费的高可用性,避免Broker发生单点故障引起存储在Broker上的消息无法及时消费,同时避免单个机器上硬盘损坏出现消息数据丢失。RocketMQ采用Broker数据主从复制机制,当消息发送到Master服务器后会将消息同步到Slave服务器,如果Master服务器宕机,消息消费者还可以继续从Slave拉取消息。

消息从Master服务器复制到Slave服务器上,有两种复制方式:同步复制SYNC_MASTER和异步复制ASYNC_MASTER。通过配置文件${ROCKETMQ_HOME}/conf/broker.conf里面的brokerRole参数进行设置。

  • 同步复制:Master服务器和Slave服务器都写成功后才返回给客户端写成功的状态。优点是如果Master服务器出现故障,Slave服务器上有全部数据的备份,很容易恢复到Master服务器。缺点是由于多了一个同步等待的步骤,会增加数据写入延迟,并且降低系统的吞吐量。

  • 异步复制:仅Master服务器写成功即可返回给客户端写成功的状态。有点刚好是同步复制的缺点,由于没有那一次同步等待的步骤,服务器的延迟比较低且吞吐量较高。缺点显而易见,如果Master服务器出现故障,有些数据因为没有被写入Slave服务器,未同步的数据有可能丢失。

在实际应用中需要结合业务场景,合理设置刷盘方式和主从复制方式。不建议使用SYNC_FLUSH同步刷盘方式,因为它会频繁地触发写磁盘操作,性能下降明显。高性能是RocketMQ的一个明显特点,因此放弃性能是不合适的选择。通常可以把Master和Slave设置成异步刷盘、同步复制,这样即使有一台服务器出现故障,仍然可以保证数据不丢失。

消息重试机制

实际业务场景中无法避免消费消息失败的情况,消费失败可能是因为业务处理中调用远程服务网络问题失败,不代表消息一定不能被消费,通过重试可以解决。介绍RocketMQ的消费重试机制之前,需要先了解一下重试队列和死信队列。

  • 重试队列:在Consumer由于业务异常导致消费消息失败时,将消费失败的消息重新发送给Broker保存在重试队列,这样设计的原因是不能影响整体消费进度又必须防止消费失败的消息丢失。重试队列的消息存在一个单独的Topic中,不在原消息的Topic中,Consumer自动订阅该Topic。重试队列的Topic名称格式为"%RETRY%+consumerGroup",每个业务Topic都会有多个ConsumerGroup,每个ConsumerGroup消费失败的情况都不一样,因此各对应一个重试队列的Topic。

  • 死信队列:由于业务逻辑Bug等原因,导致Consumer对部分消息长时间消费重试一直失败,为了保证这部分消息不丢失,同时不能阻塞其它能重试消费成功的消息,超过最大重试消费次数之后的消息会进入私信队列。消息进入死信队列之后就不再自动消费,需要人工干预处理。死信队列也存在一个单独的Topic中,名称格式为"%DLQ%+consumerGroup", 原理和重试队列一致。

通常故障恢复需要一定的时间,如果不间断地重试,重试又失败的情况会占用并浪费资源,所以RocketMQ的消费重试机制采用时间衰减的方式,使用了自身定时消费的能力。首次在10秒后重试消费,如果消费成功则不再重试,如果消费失败则继续重试消费,第二次在30秒后重试消费。以此类推,每次重试的时间间隔都会加长,直到超出最大重试次数(默认为16次),则进入死信队列不再重试。

重试消费过程中的间隔时间使用了定时消息,重试的消息数据并非直接写入重试队列,而是先写入定时消息队列,再通过定时消息的功能转发到重试队列。

RocketMQ支持定时消息(也称延迟消息),延迟消息是指消息发送之后,等待指定的延迟时间后再进行消费。除了支持消息重试机制,延迟消息也适用于一些处理异步任务的场景,例如调用某个服务,调用结果需要异步在一分钟内返回,此时就可以发送一个延迟消息,延迟时间为1分钟,等1分钟后收到该消息去查询上次的调用结果是否返回。

RocketMQ不支持任意时间精确的延迟消息,仅支持1s、5s、10s、30s、1min、2min、3min、4min、5min、6min、7min、8min、9min、10min、20min、30min、1h、2h。

ACK机制

在实际业务场景中,业务应用在消费消息的过程中偶尔会出现一些异常的情况,例如程序发布导致的重启,或者网络突然出现问题,此时正在进行业务处理的消息可能消费完了,也可能业务逻辑执行到一半没有消费完,那么如何去识别这些情况呢?这就需要消息的ACK机制。

广播模式的消费进度保存在客户端本地,集群模式的消费进度保存在Broker上。集群模式中RocketMQ采用ACK机制确保消息一定被消费。在消息投递过程中,不是消息从Broker发送到Consumer就算消费成功了,需要Consumer明确给Broker返回消费成功状态才算。如果从Broker发送到Consumer后,已经完成了业务处理,但在给Broker返回消费成功状态之前,Consumer发生宕机或者断电、断网等情况,Broker未收到返回,则不会保存消费进度。 Consumer重启之后,消息会重新投递,此时也会出现重复消费的场景,这时消息的幂等性需要业务自行保证。

Broker集群部署

Broker集群部署时消息存储高可用的基本保障,最直接的表现是Broker出现单机故障或重启时,不会影响RocketMQ整体的服务能力。RocketMQ中Broker有四种不同的集群搭建方式:

单Master模式:单Master模式仅部署一台Broker机器,属于非集群模式,这种方式存在单点故障的风险,一旦Broker重启或者宕机,会导致整个服务不可用。不建议生产环境使用,仅可用于本地测试。

多Master模式:一个集群全部都是Master机器,没有Slave机器,属于不配置主从复制的场景,例如2个Master或者3个Master。也不建议生产环境使用,这种模式的优缺点如下:

优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置为RAID10时,即使机器宕机不可用,由于RAID10磁盘非常可靠,消息也不会丢失,性能最高。

缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息的实时性会受到影响。整个缺点是致命的,消息实时性收到影响意味着一段时间内部分消费不可用,违背系统可用性原则。

异步复制的多Master多Slave模式:每个Master配置一个Slave,有多对Master-Slave,主从复制采用异步复制方式,主备有短暂的消息延迟(毫秒级),这种模式的优缺点如下:

优点:即使磁盘损坏,消息丢失非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样。

缺点:在Master宕机且磁盘损坏的情况下可能会丢失少量消息。不过出现这种场景的概率很小,有风险但是很低。

同步复制的多Master多Slave模式:每个Master配置一个Slave,有多对Master-Slave,主从复制采用同步复制方式,即只有主备都写成功,才向应用返回成功。线上推荐使用异步刷盘+同步复制的多Master多Slave模式,这种模式的优缺点如下:

优点:数据与服务都无单点故障,在Master宕机的情况下,消息无延迟,服务可用性与数据可用性都非常高。

缺点:性能比异步复制模式略低(约10%),发送单个消息的RT会略高。

发布于: 2020 年 05 月 31 日 阅读数: 4
用户头像

Java收录阁

关注

士不可以不弘毅,任重而道远 2020.04.30 加入

喜欢收集整理Java相关技术文档的程序员,欢迎关注同名微信公众号 Java收录 阁获取更多文章

评论

发布
暂无评论
RocketMQ - 高可用设计