rocketMQ

如何保证消息不丢失?

哪些环节会有丢失的可能

其中,1,2,4三个场景都是跨网络的,而跨网络就肯定会有丢消息的可能。 然后关于3这个环节,通常MQ存盘时都会先写入操作系统的缓存page cache中,然后再由操作系统异步的将消息写入硬盘。这个中间有个时间差,就可能会造成消息丢失。如果服务挂了,缓存中还没有来得及写入硬盘的消息就会丢失。 这个是MQ场景都会面对的通用的丢消息问题。那我们看看用Rocket时要如何解决这个问题

生产者丢失

  1. Rocketmq有一个非常强悍的功能,就是事务消息的功能,凭借这个事务级的消息机制,就可以让门确保订单系统推送出去的消息一定会成功的写入mq里,绝对不会半路丢掉;
  2. 发送half消息到mq中,确定mq是否正常:

在基于Rocketmq的事务消息机制中,我们首选让订单系统发一条half消息到mq去,这个half消息本质就是一个订单支付成功的消息,不过可以理解为消息是half状态,这个时候消费系统是看不到这个half消息的,然后我们就等待接收half消息写入 成功或者失败的通知。这一步是要确定mq还正常工作,而订单系统操作数据要在half发送之后进行处理。这样不会存在订单成功,红包发放还没有成功;

  • half消息写入失败

订单系统写入half失败,可能mq挂了,网关故障,broker主备切换等导致half发送失败,总之是没有跟mq通信了。这个时候订单的做法是,交易关闭,然后进行退款。这才是正确的做法。(还有一部分人会选择补偿消息)。不过订单支付成功,然后派送红包,积分,优惠券之类的如果后续操作不了或者失败按说应该是交易失败了,退款给用户。

  • half消息成功

说明mq还活着并且还可以正常沟通。下一步修改订单状态改为订单成功;

  • 如果订单本地事物执行失败了怎么办

比如订单系统数据库当时也有网络一次或者数据库挂了,总而言之,就是订单更新成功的状态是失败的;这个时候直接让订单系统发送一个rollback请求给mq就可以了,也就是把之前half消息给删掉,不然流程继续下去;half消息删除之后,订单系统走退款流程。(这里面还有服务降级等,毕竟如果数据库都挂了退款都有可能退不了,可以降级进行数据记录到redis等,数据库恢复之后在订单关闭和退款等)

  • 如果订单系统完成本地事务

订单系统把支付订单修改为已完成,此时你就可以发送commit请求给mq,要求让mq对之前的half消息进行commit操作,然消费者可以看到这个消息进行消费;也就是half状态的消息消费者是看不到的必须将half消息commit之后消费者才可以看到并且进行消费。

  • 如果half消息请求,返回消息失败

这个时候订单系统会任务half消息失败了,订单修改为失败并且退款。但是Rocketmq有一个自己的机制会定时扫描half状态的消息,如果一直没有执行commit或者rollback操作就会回调 指定的订单系统接口;告诉订单系统这个half消息是commit还是rollback。订单系统接受到回调的请求,查询订单已经失败并且退款,必然发送rollback请求给mq删除这个half消息。如果在发送消息执行rollback或者commit给mq的时候失败了,不管是mq挂了还是网络问题等等,mq还是会走之前的流程,扫码half消息回调等执行命令消息。

  • half消息请求能否保证发送消息不丢失

如果mq有问题或者网络有问题,half发送消息肯定失败,那么订单不会执行后续流程;

如果half消息发送出去,但是没有收到响应消息,那么肯定执行退款流程,订单失败。mq回调补偿是commit还是rollback,此时肯定是执行rollback,不执行后续流程

如果half发送成功接收到成功响应,这个时候订单执行更新数据库失败,那么必然执行rollback,后续流程不执行。

如果half发送成功收到成功响应,订单执行数据库成功,那么发送commit消息,那么消费者必然能收到这个消息。即使commit发送失败,这个时候Rocketmq会定时查询half状态的消息,回调订单系统,订单系统看到订单已经成功,会重新发送commit请求。

总之只要订单成功,那么mq的消息是commit,哪消费者一定能看到这个消息。

消费端消息丢失

  1. 消费端拿到消息,消息就一定不会丢失了吗?不,还是会有丢失的可能;如果消费端拿到消息,但是消息还内存里,还没有执行派发红包的逻辑,此时他就直接提交了这条消息的offset到broker说自己已经处理过了。接着红包系统在执行消费消息的逻辑的时候直接崩溃了,内存里的消息没了,红包的逻辑也没有执行,接口broker已经收到提交的消息offset了,还以为他已经处理完这条消息了,等红包系统重新启动的时候,这条消息就不会再次消费了;
  2. 解决方式:当消息处理完毕的时候手动返回CUNSUME_SUCCESS做为消费成功的相应,告诉Rocketmq,这条消息我已经处理完毕了;对于rocketmq来说,只要你返回了成功消费的状态,他才会去提交这批消息的offset到broker中,所以如果你处理一批消息,然后提交消息的offset到borker,消费红包系统崩溃了,此时是不会丢失消息的。
  3. 注意的是不能异步消费消息;所以在默认的Consumer的消费模式之下,必须是你处理完一批消息了,才会返回 ConsumeConcurrentlyStatus.CONSUME_SUCCESS这个状态标识消息都处理结束了,去提交offset到broker去。那可能就会出现你开启的子线程还没处理完消息呢,直接返回CONSUME_SUCCESS状态了,就可能提交这批消息的offset给broker了,认为已经处理结束了。这样消费一定一旦崩溃,必然导致你的消息丢失。

MQ丢失

消息持久化

  • 消息写入mq之后,其实mq的可能仅仅是把这个消息给写入到page cache里面,也就是一个缓冲区中相当于缓存,此时没有写入到磁盘;如果此时出现broker机器崩溃机器重启,page cache内存里的数据就没有,造成mq消息的丢失;
  • 解决方式:缓存写入磁盘有两种情况,同步刷盘和异步刷盘;同步刷盘是指,消息在写入mq的os cache的时候会同时写入磁盘文件中,然后返回相应(吞吐量严重下降);异步刷盘是指,消息在写入mq的os cache的时候,写入就返回相应,然后开启线程将os cache内存中的mq消息持久化到磁盘中;使用同步刷盘可以防止mq消息丢失;所以如果一定要确保数据零丢失的话,可以调整MQ的刷盘策略,我们需要调整broker的配置文件,将其中的flushDiskType配置设置 为:SYNC_FLUSH,默认他的值是ASYNC_FLUSH,即默认是异步刷盘的。能保证绝不丢失吗?明显不能。
  • 假如mq的消息稳稳的落在了磁盘里面了,而这个时候broker所在机器的磁盘出现了故障,消息还是会丢失的。主从架构模式避免磁盘故障导致的数据丢失,也就是必须让一个master broker有一个slave broker去同步他的数据,而且消息写入成功,必须让slave brkoer写入成功,保证数据多个副本冗余;这样一来,你一条消息但凡写入成功了,此时主从两个Broker上都有这条数据了,此时如果你的Master Broker的磁盘坏了,但是 Slave Broker上至少还是有数据的,数据是不会因为磁盘故障而丢失的。

设置集群镜像

1、单Master模式

这种部署方式的风险比较大,一旦Broker宕掉,就会导致整个服务不可用,一般只在开发环境为了节约资源的时候使用,线上环境非常不建议使用。

2、多Master模式

这种模式会在一个集群中部署多个Master,没有Slave,这种模式也是存在一定的优缺点:

优点:配置简单,单个Master宕掉其他几Master可以正常提供服务,在磁盘配置为RAID10时,消息基本上不会丢失,之所以不是百分百是因为异步刷盘可能会丢失少量消息,同步刷盘不会丢失消息。
缺点:如果其中一个Master宕掉,这台机器上还没有消费的消息在恢复之前不能被消费,可能影响消息的消费时效。
3、多Master多Salve模式(异步)

每个Master配置一个Slave,有多对Master-Slave一起提供服务,HA采用异步复制方式,主备有很短暂的消息延迟,这种模式的优缺点:

优点:即使磁盘损坏,也只是会丢失非常少的消息,消息的时效性也不会有影响,Master宕机后依然可以消费从Slave消费消息,并且这个过程不需要人工干预,性能和多Master模式接近。
缺点:Master宕机磁盘损坏的情况下会丢失少量消息。
4、多Master多Slave模式(同步)

每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主备都写成功,才向应用返回成功,这种模式的优缺点如下:

优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
缺点:性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。

RocketMq消费有序性

MQ的顺序问题分为全局有序和局部有序。

  • 全局有序:整个MQ系统的所有消息严格按照队列先入先出顺序进行消费。
  • 局部有序:只保证一部分关键消息的消费顺序。

我们需要分析下这个问题,在通常的业务场景中,全局有序和局部有序哪个更重要?其实在大部分的MQ业务场景,我们只需要能够保证局部有序就可以了。例如我们用QQ聊天,只需要保证一个聊天窗口里的消息有序就可以了。而对于电商订单场景,也只要保证一个订单的所有消息是有序的就可以了。

至于全局消息的顺序,并不会太关心。而通常意义下,全局有序都可以压缩成局部有序的问题。例如以前我们常用的聊天室,就是个典型的需要保证消息全局有序的场景。但是这种场景,通常可以压缩成只有一个聊天窗口的QQ来理解。

即整个系统只有一个聊天通道,这样就可以用QQ那种保证一个聊天窗口消息有序的方式来保证整个系统的全局消息有序。 然后 落地到RocketMQ。通常情况下,发送者发送消息时,会通过MessageQueue轮询的方式保证消息尽量均匀的分布到所有的MessageQueue上,而消费者也就同样需要从多个MessageQueue上消费消息。而MessageQueue是RocketMQ存储消息的最小单元,他们之间的消息都是互相隔离的,在这种情况下,是无法保证消息全局有序的。

而对于局部有序的要求,只需要将有序的一组消息都存入一个MessageQueue对象里,这样MessageQueue的FIFO设计天生就可以保证这一组消息的有序.RocketMQ中可以在发送者发送消息时指定一个MessageSelector对象,让这个对象来决定消息发入哪一个MessageQueue。这样就可以保证一组有序的消息能够发到同一个MessageQueue里。

另外,通常所谓的保证Topic全局消息有序的方式,就是将Topic配置成只有一个MessageQueue队列(默认是4个)。这样天生就能保证消息全局有序了。

而这种方式对整个Topic的消息吞吐影响是非常大的,如果这样用,基本上就没有用MQ的必要了。

在MQ的模型中,顺序需要由3个阶段去保障:

  1. 消息被发送时保持顺序
  2. 消息被存储时保持和发送的顺序一致
  3. 消息被消费时保持和存储的顺序一致

全局有序

局部有序

对局部有序的要求,只需要将有序的一组消息都存入同一个messageQueue里,这样MessageQueue的FIFO就可以保证这一组消息的有序。RocketMQ中可以在发送者发送消息时指定一个MessageSelector对象,让这个对象来决定消息发入哪一个MessageQueue。这样就可以保证一组有序的消息能够发送到同一个MessageQueue里

Last modification:December 28, 2022
如果觉得我的文章对你有用,请随意赞赏