写点什么

解决 rabbitmq 消息队列的顺序及重复消费问题

作者:编程江湖
  • 2021 年 12 月 17 日
  • 本文字数:1767 字

    阅读完需:约 6 分钟

想想为什么要使用 MQ?

1.解耦,系统 A 在代码中直接调用系统 B 和系统 C 的代码,如果将来 D 系统接入,系统 A 还需要修改代码,过于麻烦!

2.异步,将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

3.削峰,并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常

使用了消息队列会有什么缺点?

1.系统可用性降低:你想啊,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性降低

2.系统复杂性增加:要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。因此,需要考虑的东西更多,系统复杂性增大。

如何保证消息队列是高可用的?

使用集群的方式维持 MQ 的可高用性。

如何保证消息不被重复消费?

保证消息不被重复消费的关键是保证消息队列的幂等性,这个问题针对业务场景来答分以下几点:

1.比如,你拿到这个消息做数据库的 insert 操作。那就容易了,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。

2.再比如,你拿到这个消息做 redis 的 set 的操作,那就容易了,不用解决,因为你无论 set 几次结果都是一样的,set 操作本来就算幂等操作。

3.如果上面两种情况还不行,上大招。准备一个第三方介质,来做消费记录。以 redis 为例,给消息分配一个全局 id,只要消费过该消息,将<id,message>以 K-V 形式写入 redis。那消费者开始消费前,先去 redis 中查询有没消费记录即可。

如何解决丢数据的问题?

1.生产者丢数据

生产者的消息没有投递到 MQ 中怎么办?从生产者弄丢数据这个角度来看,RabbitMQ 提供 transaction 和 confirm 模式来确保生产者不丢消息。

transaction 机制就是说,发送消息前,开启事物(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txRollback()),如果发送成功则提交事物(channel.txCommit())。

然而缺点就是吞吐量下降了。大数据培训因此,按照博主的经验,生产上用 confirm 模式的居多。一旦 channel 进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,rabbitMQ 就会发送一个 Ack 给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了.如果 rabiitMQ 没能处理该消息,则会发送一个 Nack 消息给你,你可以进行重试操作。

2.消息队列丢数据

处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和 confirm 机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个 Ack 信号。这样,如果消息持久化磁盘之前,rabbitMQ 阵亡了,那么生产者收不到 Ack 信号,生产者会自动重发。

那么如何持久化呢,这里顺便说一下吧,其实也很容易,就下面两步

①、将 queue 的持久化标识 durable 设置为 true,则代表是一个持久的队列

②、发送消息的时候将 deliveryMode=2

这样设置以后,rabbitMQ 就算挂了,重启后也能恢复数据。在消息还没有持久化到硬盘时,可能服务已经死掉,这种情况可以通过引入 mirrored-queue 即镜像队列,但也不能保证消息百分百不丢失(整个集群都挂掉)

3.消费者丢数据

启用手动确认模式可以解决这个问题

①自动确认模式,消费者挂掉,待 ack 的消息回归到队列中。消费者抛出异常,消息会不断的被重发,直到处理成功。不会丢失消息,即便服务挂掉,没有处理完成的消息会重回队列,但是异常会让消息不断重试。

②手动确认模式,如果消费者来不及处理就死掉时,没有响应 ack 时会重复发送一条信息给其他消费者;如果监听程序处理异常了,且未对异常进行捕获,会一直重复接收消息,然后一直抛异常;如果对异常进行了捕获,但是没有在 finally 里 ack,也会一直重复发送消息(重试机制)。

③不确认模式,acknowledge="none" 不使用确认机制,只要消息发送完成会立即在队列移除,无论客户端异常还是断开,只要发送完就移除,不会重发。

如何保证消息的顺序性?

针对这个问题,通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中。然后只用一个消费者去消费该队列。同一个 queue 里的消息一定是顺序消息的。我的观点是保证入队有序就行,出队以后的顺序交给消费者自己去保证,没有固定套路。例如 B 消息的业务应该保证在 A 消息后业务后执行,那么我们保证 A 消息先进 queueA,B 消息后进 queueB 就可以了。


用户头像

编程江湖

关注

IT技术分享 2021.11.23 加入

还未添加个人简介

评论

发布
暂无评论
解决rabbitmq消息队列的顺序及重复消费问题