MQ 消息丢失、重复、积压问题,如何解决?

一、什么是消息队列

消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ

二、应用场景

异步处理,应用解耦,流量削锋和消息通讯四个场景

 三、消息丢失、重复、积压问题,如何解决?

  1.消息丢失:

案例解答

我们首先来看消息丢失的环节,一条消息从生产到消费完成这个过程,可以划分三个阶段,分别为消息生产阶段,消息存储阶段和消息消费阶段。

消息生产阶段:

从消息被生产出来,然后提交给 MQ 的过程中,只要能正常收到 MQ Broker 的 ack
确认响应,就表示发送成功,所以只要处理好返回值和异常,这个阶段是不会出现消息丢失的。

消息存储阶段:

这个阶段一般会直接交给 MQ 消息中间件来保证,但是你要了解它的原理,比如 Broker 会做副本,保证一条消息至少同步两个节点再返回ack。

消息消费阶段:

消费端从 Broker 上拉取消息,只要消费端在收到消息后,不立即发送消费确认给
Broker,而是等到执行完业务逻辑后,再发送消费确认,也能保证消息的不丢失。

 但是我们不知道服务器什么时候会宕机,消息服务什么时候会挂掉,所以任何时刻都有可能我们不能保证 MQ 是不是弄丢了你的消息,消费者是否消费了你的消息。

扫描二维码关注公众号,回复: 15392369 查看本文章

所以我们需要做消息检测

消息检测:

总体方案解决思路为:在消息生产端,给每个发出的消息都指定一个全局唯一 ID,或者附加一个连续递增的版本号,然后在消费端做对应的版本校验。

 方案落地:可以利用拦截器机制。在生产端发送消息之前,通过拦截器将消息版本号注入消息中(版本号可以采用连续递增的 ID 生成,也可以通过分布式全局唯一 ID生成)。然后在消费端收到消息后,再通过拦截器检测版本号的连续性或消费状态,这样实现的好处是消息检测的代码不会侵入到业务代码中,可以通过单独的任务来定位丢失的消息,做进一步的排查。

如果同时存在多个消息生产端和消息消费端,通过版本号递增的方式就很难实现了,因为不能保证版本号的唯一性,此时只能通过全局唯一 ID 的方案来进行消息检测,具体的实现原理和版本号递增的方式一致。

现在,你已经知道了哪些环节(消息存储阶段、消息消费阶段)可能会出问题,并有了如何检测消息丢失的方案,然后就要给出解决防止消息丢失的设计方案。

2.防止重复消费

  重复消费的场景:在消息消费的过程中,如果出现失败的情况,通过补偿的机制发送方会执行重试,重试的过程就有可能产生重复的消息

就是如何解决消费端幂等性问题(幂等性,就是一条命令,任意多次执行所产生的影响均与一次执行的影响相同),只要消费端具备了幂等性,那么重复消费消息的问题也就解决了。

接下来看某个入库数据服务的例子,将入库服务处理消费消息,在这个例子中,我们可以通过改造业务逻辑,让它具备幂等性。

 这个表有两个字段:消息 ID 和消息执行状态。这样,我们消费消息的逻辑可以变为:在消息日志表中增加一条消息记录,然后再根据消息记录,异步操作入库数据,然后入库成功返回标识已消费该消息id的消息,修改该条消息id消息为已完成状态。

为我们每次都会在插入之前检查是否消息已存在,所以就不会出现一条消息被执行多次的情况,这样就实现了一个幂等的操作。当然,基于这个思路,不仅可以使用关系型数据库,也可以通过 Redis 来代替数据库实现唯一约束的方案。

在这里我多说一句,想要解决“消息丢失”和“消息重复消费”的问题,有一个前提条件就是要实现一个全局唯一 ID 生成的技术方案。

3.防止消息堆积

原因:消费性能不够!

因为消息发送之后才会出现积压的问题,所以和消息生产端没有关系,又因为绝大部分的消息队列单节点都能达到每秒钟几万的处理能力,相对于业务逻辑来说,性能不会出现在中间件的消息存储上面。毫无疑问,出问题的肯定是消息消费阶段。

解决:1.增加消费端的数量  2.消费业务逻辑优化

扩容消费者的实例数的同时,必须同步扩容主题 Topic
的分区数量,确保消费者的实例数和分区数相等。如果消费者的实例数超过了分区数,由于分区是单线程消费,所以这样的扩容就没有效果。

总结:这篇文章有点长,但是都是消息队列组件容易出现的问题,大家读到这里相信大家心里也有初步的解决方案和问题认知

猜你喜欢

转载自blog.csdn.net/qq_38623939/article/details/126172807