在我们大多数场景中,MQ消息都要保证可靠性,消息可靠性应该是我们最关心的一个细节,没有之一;而各个MQ实现的可靠性保证都不同,同时实现机制也不同,只有知道各个MQ实现是如何保证消息可靠性的,才能在使用的过程中不丢消息;
rabbitMQ
对于rabbitMQ,消息可靠性是从以下几点来保证的:
消息持久化;
发布者确认;
消费者确认;
消息持久化
对于rabbitMQ,默认情况下消息是不持久化的,这是为了性能考虑,但是这样会导致消息在服务器重启后丢失,这在我们大多数场景下都是不可接受的,所以,我们
要开启消息持久化,而rabbitMQ的消息持久化分为两种:
同步持久化,也就是rabbitMQ broker(服务端)每接收到一个消息都会立即持久化到磁盘上,显而易见的,这种持久化方式性能很差;
异步持久化,也就是rabbitMQ broker(服务端)接收到消息后不会立即持久化到磁盘上,而是会按照时间段批量将内存中的数据刷新到磁盘,这种方式的性能较
好,但是也存在一个问题,那就是如果服务在刷盘前重启了会导致内存中部分数据丢失;
对于这两种方式,我们优先选择使用异步持久化,因为同步持久化的性能太差(官方测试数据同步持久化相较于异步持久化性能查了200倍左右),那我们怎么防止刷盘前服务重启导致内存中的短时间内的数据丢失呢?rabbitMQ给出了发布者确认的发布模式来解决该问题;
发布者确认
对于rabbitMQ,发布者确认需要手动开启,默认是没有开启的,也就是说默认情况下我们消息发出去就完事儿了,但是服务端有没有成功处理并不一定,此时虽然消息
发布成功但是broker可能并没有正确处理该消息导致消息丢失,所以为了消息的可靠性,我们需要开启发布确认;开启了发布确认后,在我们发布完消息后服务端会在
接收到消息并成功处理后返回给我们一个ack,表示消息接收处理完毕(注意,只是broker端处理完毕,不是消费者消费完成),而broker的ack时机对于我们很关
键,broker会在以下时机给我们返回ack:
如果未开启消息持久化,则消息在被路由到各个队列(如果是镜像队列,这意味着所有镜像队列都成功接收到消息)后broker返回ack;
如果开启消息持久化,则消息在被路由到各个队列并且所有需要持久化的队列持久化完成后返回ack;而如果其中某部分队列持久化未完成则broker会返回nack,表
示服务端接收失败,需要生产者重新发布消息;注意:此时虽然部分队列持久化失败,但是持久化成功的那部分是不会回滚的,也就是说对于持久化成功的队列上绑定
的消费者可能会重复收到消息,此时消费者应该去重;
可以看出,发布者确认可以解决消息异步持久化中的问题,这两项措施保证了我们的消息肯定会发布成功并且不会丢失,后续就是消费者的事情了;
注意:其实以上措施只能保证在硬件正常的情况下消息不会丢失,而如果broker是单点部署的,则这个broker的磁盘损坏仍然会导致数据丢失,而如果broker是
集群部署的,如果集群中所有broker的磁盘都损坏,此时消息也会丢失,由于硬件故障是无法避免的,只能根据消息的重要性做集群,集群规模越大、磁盘可靠性越
高,消息丢失的概率越小;
消费者确认
通过上述的消息持久化和发布者确认,我们已经可以保证消息在发布后不会丢失了,但是消息仍然可能会在消费的过程中丢失,这与rabbitMQ的消费确认模型有关系,
rabbitMQ的消费确认模型有两种:
自动确认
手动确认
如果是手动确认,只要代码没问题,一般不会导致消息丢失,导致消息丢失的是消费者使用自动确认模型时的场景;消费者使用自动确认模型后,当消费者拉取到(接收
到)消息后会自动确认消息已经消费,消息被消费者确认后就会从指定的队列中删除,但是此时消费者并没有成功处理该消息,如果此时消费者重启或者处理失败,因为
队列中的消息已经删除不会重新投递,这就意味着消息丢失了;
所以, 不要轻易使用自动确认模型 ,除非是消息无关紧要,例如日志消息,丢了影响也不大,或者即使消息丢了,也只是会导致数据暂时不一致,系统可以通过
其他补偿措施自动到达最终一致性;
rocketMQ
消息持久化
对于rocketMQ来说,正常情况下所有消息都肯定会持久化,也就是正常情况下我们不用开启任何选项rocketMQ就会把我们的消息持久化,重启rocketMQ后消息不会丢失;
此时我们只需要考虑异常情况下的处理即可;经典异常场景有如下几种:
Broker非正常关闭
Broker异常Crash
OS Crash
机器掉电,但是能立即恢复供电情况
机器无法开机(可能是cpu、主板、内存等关键设备损坏)
磁盘设备损坏
前四种情况属于硬件资源可立即恢复的, rocketMQ此时根据刷盘方式不会丢失数据或者丢失少部分数据(如果是异步刷盘则内存中的数据会丢失,如果同步刷盘就不会有这种情况,但是性能势必会降低);
而后两种则属于单点问题,即机器故障无法恢复,如果想要避免这种情况可以使用同步双写,这样可以避免单点故障,但是性能会降低;(rocketMQ从3.0开始支持同步双写);
发布者确认
在rocketMQ中,通信分为三种情况:
同步通信;
异步通信;
单向(oneway)
其中同步通信和异步通信都是有ACK的,而oneway则是发出消息后就不管broker有没有收到了,也不会有ACK,基本用于心跳,而我们需要关注的就是同步/异步通信,因为同步通信或者异步通信仅仅只是IO模型上的区别,并不会影响消息的可靠性,导致消息丢失,
所以我们不关注通信模型,我们只关注ACK的时机,即broker什么时候会ACK,这个是真正影响消息可靠性的因素;
rocketMQ中数据写盘也是分为两种,即同步写盘和异步写盘,同步写盘效率低,异步写盘效率高;
如果broker选择了同步写盘,则消息会在被真正写入磁盘后进行ACK,此时对于生产者来说只要接到ACK基本上消息不会丢了(为什么是基本上?请参考消息持久化部分);
如果broker选择了异步写盘,则消息会在被写入page cache之后进行ACK,此时依赖OS的刷盘机制,如果在OS刷盘前OS崩溃、机器掉电等,短时间内的消息就会因为来不及写入磁盘而丢失;
所以,对于一定不能丢失的数据,我们可以牺牲性能来选择使用同步刷盘模型,而对于没有那么重要或者有补偿机制的消息,我们就可以使用更高效的异步刷盘模型;
消费者确认
对于rocketMQ,消费者确认并不重要(对于消息可靠性来说),这与rocketMQ的数据存储结构有关,rocketMQ的broker对于消息的存储可以认为是构建了一个数据库,保存的有消息、消费队列、索引,rocketMQ并不会在ACK之后立即把消息删除(具体什么时候删除取决于你的磁盘大小,如果磁盘足够大,rocketMQ可以一直不删除消息),消费者仍然有途径可以重复消费该消息;
对比
在可靠性这块儿,rabbitMQ和rocketMQ底层其实大同小异,不同的是rocketMQ默认就是会把消息落盘的,同时消费者消费完毕后不会立即删除消息(rabbitMQ则会删除),正因为rocketMQ的这一系列策略,即使是一个对该MQ没有任何了解的小白用户,直接拿来用可靠性就要高出rabbitMQ很多(当然真正理解两个MQ的底层可靠性保障机制后其实是差不多的),所以rocketMQ才会声称自己是金融级、高可靠的MQ中间件;