1.如何保证消息不丢失
生产者、中间件、消费者 所以要保证消息就是保证三个环节都不能丢失数据。
解决方案
消息生产阶段:生产者会不会丢消息,取决于生产者对于异常情况的处理是否合理。从消息被生产出来,然后提交给 MQ 的过程中,只要能正常收到 ( MQ 中间件) 的 ack 确认响应,就表示发送成功,所以只要处理好返回值和异常,如果返回异常则进行消息重发,那么这个阶段是不会出现消息丢失的。
消息存储阶段:Kafka 在使用时是部署一个集群,生产者在发布消息时,队列中间件通常会写「多个节点」,也就是有多个副本,这样一来,即便其中一个节点挂了,也能保证集群的数据不丢失。开启持久化功能可以确保MQ消息不丢失(交换器,队列,消息)
消息消费阶段:消费者接收消息+消息处理之后,才回复 ack 的话,那么消息阶段的消息不会丢失。不能收到消息就回 ack,否则可能消息处理中途挂掉了,消息就丢失了
2.消息重复消费
重复消费可能是因为网络波动,消费者挂了,但是消费者有个自动确认的机制,消费者就有可能重新消费这个消息
解决方案:
每条消息设置一个唯一的标识id(支付id,订单id,文章id.......)
幂等方案:【 分布式锁、数据库锁(悲观锁、乐观锁) 】
唯一标识:客户端为每一个请求都生成一个全局唯一id,服务端校验id是否被处理(接口调用)
数据库事务/乐观锁:通过版本号或者状态字段来控制并发更新,确保多次更新等同一次操作(余额扣减)
分布式锁:通过分布式锁保证同一时刻只能有一个请求执行(秒杀业务)
消息去重:消息队列生产者为每条消息生成唯一id,消费者处理消息先校验id是否处理过。
3.如何保证幂等写?
幂等性是指 同一操作的多次执行对系统状态的影响与一次执行结果一致。例如,支付接口若因网络重试被多次调用,最终应确保仅扣款一次。实现幂等写的核心方案:
解决方案
唯一标识(幂等键):客户端为每个请求生成全局唯一ID(如 UUID、业务主键),服务端校验该ID是否已处理,适用场景接口调用、消息消费等。
数据库事务 + 乐观锁:通过版本号或状态字段控制并发更新,确保多次更新等同于单次操作,适用场景数据库记录更新(如余额扣减、订单状态变更)。
数据库唯一约束:利用数据库唯一索引防止重复数据写入,适用场景数据插入场景(如订单创建)。
分布式锁:通过锁机制保证同一时刻仅有一个请求执行关键操作,适用场景高并发下的资源抢夺(如秒杀)。
消息去重:消息队列生产者为每条消息生成唯一的消息 ID,消费者在处理消息前,先检查该消息 ID 是否已经处理过,如果已经处理过则丢弃该消息
4.延迟队列(死信交换机)
延迟队列
延迟队列:进入队列的消息会被延迟消费的队列
比如场景:超时订单,限时优惠,定时发布
死信交换机
延迟队列=死信交换机+TTL(生存时间)
当一个队列的消息满足下列情况之一,就可以成为死信(可能不会被消费):
- 消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false
- 消息是一个过期消息,超时无人消费
- 要投递的队列消息堆积满了,最早的消息可能成为死信
如果该队列配置了dead-letter-exchange属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为死信交换机(Dead Letter Exchange,简称DLX)
TTL
TTL,也就是Time-To_Live.如果一个队列中的消息TTL结束还没有被消费,就会变成死信,TTL超时分为两种情况:
- 消息所在的队列设置了存活时间
- 消息本身设置了存活时间
5.消息队列的可靠性、顺序性怎么保证?
可靠性
解决方案
消息持久化:确保消息队列能够持久化消息是非常关键的。在系统崩溃、重启或者网络故障等情况下,未处理的消息不应丢失。例如,像 RabbitMQ 可以通过配置将消息持久化到磁盘,通过将队列和消息都设置为持久化的方式(设置durable = true),这样在服务器重启后,消息依然可以被重新读取和处理。
消息确认机制:消费者在成功处理消息后,应该向消息队列发送确认(acknowledgment)。消息队列只有收到确认后,才会将消息从队列中移除。如果没有收到确认,消息队列可能会在一定时间后重新发送消息给其他消费者或者再次发送给同一个消费者。以 Kafka 为例,消费者通过commitSync或者commitAsync方法来提交偏移量(offset),从而确认消息的消费。
消息重试策略:当消费者处理消息失败时,需要有合理的重试策略。可以设置重试次数和重试间隔时间。例如,在第一次处理失败后,等待一段时间(如 5 秒)后进行第二次重试,如果重试多次(如 3 次)后仍然失败,可以将消息发送到死信队列,以便后续人工排查或者采取其他特殊处理。
顺序性
解决方案
消息队列对顺序性的支持:部分消息队列本身提供了顺序性保证的功能。比如 Kafka 可以通过将消息划分到同一个分区(Partition)来保证消息在分区内是有序的,消费者按照分区顺序读取消息就可以保证消息顺序。但这也可能会限制消息的并行处理程度,需要在顺序性和吞吐量之间进行权衡。
消费者顺序处理策略:消费者在处理顺序消息时,应该避免并发处理可能导致顺序打乱的情况。例如,可以通过单线程或者使用线程池并对顺序消息进行串行化处理等方式,确保消息按照正确的顺序被消费。
6.如何处理消息队列的消息积压问题?
消息积压是因为生产者的生产速度,大于消费者的消费速度。遇到消息积压问题时,我们需要先排查,是不是有bug产生了。如果不是bug,我们可以优化一下消费的逻辑,比如之前是一条一条消息消费处理的话,我们可以确认是不是可以优为批量处理消息。如果还是慢,我们可以考虑水平扩容,增加Topic的队列数,和消费组机器的数量,提升整体消费能力。
解决方案
- 提高消费者消费能力,如使用多线程。
- 增加消费者数量,采用工作队列模式,让多个消费者并行消费同一队列。
- 扩大队列容量,使用RabbitMQ的惰性队列,支持数百万条消息存储,直接存盘而非内存。