15、RocketMQ 实战 - RocketMQ如何保证消息的可靠性?

一、概述

我们知道,网络传输是不可靠的,在分布式系统中,经常存在网络闪断的情况,所以消息中间件都存在消息丢失的风险,各种消息中间件也提供了重试机制,保证消息至少传输成功一次,当然RocketMQ也不例外。今天我们就来看看RocketMQ是如何最大限度的保证消息不丢失的呢?

先来看看影响RocketMQ消息可靠性的几种情况:

1、 Broker非正常关闭;
2、 Broker异常Crash(宕机);
3、 OSCrash(宕机);
4、 机器掉电,但是能立即恢复供电情况;
5、 机器无法开机(可能是cpu、主板、内存等关键设备损坏);
6、 磁盘设备损坏;

1)、2)、3)、4) 四种情况都属于硬件资源可立即恢复情况,RocketMQ在这四种情况下能保证消息不丢,或者丢失少量数据(依赖刷盘方式是同步还是异步)。

5)、6)属于单点故障,且无法恢复,一旦发生,在此单点上的消息全部丢失。RocketMQ在这两种情况下,通过异步复制,可保证99%的消息不丢,但是仍然会有极少量的消息可能丢失。通过同步双写技术可以完全避免单点,同步双写势必会影响性能,适合对消息可靠性要求极高的场合,例如与Money相关的应用。

注:RocketMQ从3.0版本开始支持同步双写。

RocketMQ消息丢失可能发生在以下三个阶段:

  • 1、生产者发送消息到Broker时;
  • 2、Broker内部存储消息到磁盘以及主从复制同步时;
  • 3、Broker把消息推送给消费者或者消费者主动拉取消息时;

下面我们就从这三个方面分析 RocketMQ 是如何保证消息的可靠性的。

二、发送端消息可靠性

生产者发送消息主要有三种方式:同步发送、异步发送、单向发送。下面具体介绍不同的发送方式实现的消息可靠性保证。

  • 1、同步发送

同步发送是指发送端在发送消息时,阻塞线程进行等待,直到服务器返回发送的结果。发送端如果需要保证消息的可靠性,防止消息发送失败,可以采用同步阻塞式的发送,然后同步检查Brocker返回的状态来判断消息是否持久化成功。如果发送超时或者失败,则会默认重试2次,RocketMQ选择至少传输成功一次的消息模型,但是有可能发生重复投递,所以消费端需要做好幂等。

  • 2、异步发送

异步发送是指发送端在发送消息时,传入回调接口实现类,调用该发送接口后不会阻塞,发送方法会立即返回,回调任务会在另一个线程中执行,消息发送结果会回传给相应的回调函数。具体的业务实现可以根据发送的结果信息来判断是否需要重试来保证消息的可靠性。

  • 3、单向发送

单向发送是指发送端发送完成之后,调用该发送接口后立刻返回,并不返回发送的结果,业务方无法根据发送的状态来判断消息是否发送成功,单向发送相对前两种发送方式来说是一种不可靠的消息发送方式,因此要保证消息发送的可靠性,不推荐采用这种方式来发送消息。

  • 4、发送重试策略

生产者发送消息失败后,会根据相应的策略进行重试。Producer 的 send 方法本身支持内部重试,重试逻辑如下:

a、至多重试 2 次。

b、如果同步模式发送失败,则轮转到下一个 Broker,如果异步模式发送失败,则只会在当前 Broker 进行重试。这个方法的总耗时时间不超过 sendMsgTimeout 设置的值,默认 10s。

c、如果本身向 broker 发送消息产生超时异常,就不会再重试。

以上策略也是在一定程度上保证了消息可以发送成功。如果业务对消息可靠性要求比较高,建议应用增加相应的重试逻辑:比如调用 send 同步方法发送失败时,则尝试将消息存储到 db,然后由后台线程定时重试,确保消息一定到达 Broker。

三、Broker存储端消息可靠性

生产者发送消息到RocketMQ的Broker后,一般都是先把消息写到PageCache中,然后再持久化到磁盘上,在刷盘阶段可能会出现消息丢失。除了刷盘,在进行Broker的主从复制时,需要将Master的消息同步到Slave,这个主从复制的过程也会出现消息丢失。下面我们就分析下RocketMQ在这两个阶段是如何保证消息可靠性的。

  • 1、消息刷盘

数据从pagecache刷新到磁盘有两种方式,同步刷盘和异步刷盘。

a、同步刷盘

消息写入内存的 PageCache后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。这种方式可以保证数据绝对安全,但是吞吐量不大。

b、异步刷盘(默认)

消息写入到内存的 PageCache中,就立刻给客户端返回写操作成功,当 PageCache中的消息积累到一定的量时,触发一次写操作,或者定时等策略将 PageCache中的消息写入到磁盘中。这种方式吞吐量大,性能高,但是 PageCache中的数据可能丢失,不能保证数据绝对的安全。

实际应用中要结合业务场景,合理设置刷盘方式,尤其是同步刷盘的方式,由于频繁的触发磁盘写动作,会明显降低性能。

  • 2、主从复制同步

主从消息同步也有两种方式:同步复制和异步复制。

a、同步复制

同步复制方式是等Master和Slave均写成功后才反馈给客户端写成功状态。在同步复制方式下,如果Master出故障,Slave上有全部的备份数据,容易恢复,但是同步复制会增大数据写入延迟,降低系统吞吐量。

b、异步复制

异步复制方式是只要Master写成功,即可反馈给客户端写成功状态。在异步复制方式下,系统拥有较低的延迟和较高的吞吐量,但是如果Master出了故障,有些数据因为没有被写 入Slave,有可能会丢失;

实际应用中要结合业务场景,合理设置主从同步的方式,推荐使用同步复制,虽然会降低吞吐量,但是能最大保证消息不丢失。

四、消费端消息可靠性

客户端发送消息的时候,无论是Pull模式还是Push模式,都需要经过网络传输,所以可能发生消息丢失的情况。RocketMQ主要提供了以下一些机制保证消息的可靠性:

  • 1、消费重试机制

消费者从RocketMQ拉取到消息之后,需要返回消费成功来表示业务方正常消费完成。因此只有返回CONSUME_SUCCESS才算消费完成,如果返回CONSUME_LATER则会按照不同的messageDelayLevel时间进行再次消费,时间分级从秒到小时,最长时间为2个小时后再次进行消费重试,如果消费满16次之后还是未能消费成功,则不再重试,会将消息发送到死信队列,从而保证消息存储的可靠性。

  • 2、死信队列

未能成功消费的消息,消息队列并不会立刻将消息丢弃,而是将消息发送到死信队列,其名称是在原队列名称前加%DLQ%,如果消息最终进入了死信队列,则可以通过RocketMQ提供的相关接口从死信队列获取到相应的消息,保证了消息消费的可靠性。

五、总结

本篇文章主要从三个方面分析了RocketMQ如何保证消息的可靠性。

  • 消息发送方:通过不同的重试策略保证了消息的可靠发送;
  • Broker服务端:通过不同的刷盘机制以及主从复制来保证消息的可靠存储;
  • 消息消费方:通过至少消费成功一次以及消费重试机制来保证消息的可靠消费;