面试题-----RabbitMQ
如何保证消息不丢失?
场景
消息发送过程
发送者
生产者确认机制
RabbitMQ 提供了 publisher confirm 机制来避免消息发送到 MQ 过程中丢失。消息发送到 MQ 以后,会返回一个结果给发送者,表示消息是否处理成功
消息持久化
MQ 默认是内存存储消息,开启持久化功能可以确保缓存在 MQ 中的消息不丢失。
消费者确认
RabbitMQ 支持消费者确认机制,即:消费者处理消息后可以向 MQ 发送 ack 回执,MQ 收到 ack 回执后才会删除该消息 。而 SpringAMQP 则允许配置三种确认模式:
- manual:手动 ack,需要在业务代码结束后,调用 api 发送 ack。
- auto:自动 ack,由 spring 监测 listener 代码是否出现异常,没有异常则返回 ack;抛出异常则返回 nack
- none:关闭 ack,MQ 假定消费者获取消息后会成功处理,因此消息投递后立即被删除
回答
嗯!我们当时 MYSQL 和 Redis 的数据双写一致性就是采用 RabbitMQ 实现同步的,这里面就要求了消息的高可用性,我们要保证消息的不丢失。主要从三个层面考虑
第一个是开启生产者确认机制,确保生产者的消息能到达队列,如果报错可以先记录到日志中,再去修复数据
第二个是开启持久化功能,确保消息未消费前在队列中不会丢失,其中的交换机、队列、和消息都要做持久化
第三个是开启消费者确认机制为 auto,由 spring 确认消息处理成功后完成 ack,当然也需要设置一定的重试次数,我们当时设置了 3 次,如果重试 3 次还没有收到消息,就将失败后的消息投递到异常交换机,交由人工处理
消息的重复消费问题如何解决的?
回答
嗯,这个我们还真遇到过,是这样的,我们当时消费者是设置了自动确认机制,当服务还没来得及给 MQ 确认的时候,服务宕机了,导致服务重启之后,又消费了一次消息。这样就重复消费了
因为我们当时处理的支付(订单 | 业务唯一标识),它有一个业务的唯一标识,我们再处理消息时,先到数据库查询一下,这个数据是否存在,如果不存在,说明没有处理过,这个时候就可以正常处理这个消息了。如果已经存在这个数据了,就说明消息重复消费了,我们就不需要再消费了
其实这个就是典型的幂等的问题,比如,redis 分布式锁、数据库的锁都是可以的
RabbitMQ 中死信交换机?(RabbitMQ 延迟队列有了解过嘛)
死信交换机
当一个队列中的消息满足下列情况之一时,可以成为死信(dead letter):
- 消费者使用 basic.reject 或 basic.nack 声明消费失败,并且消息的 requeue 参数设置为 false
- 消息是一个过期消息,超时无人消费
- 要投递的队列消息堆积满了,最早的消息可能成为死信
如果该队列配置了 dead-letter-exchange 属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为死信交换机(Dead Letter Exchange,简称 DLX)。
TTL
TTL,也就是 Time-To-Live。如果一个队列中的消息 TTL 结束仍未消费,则会变为死信,ttl 超时分为两种情况:
- 消息所在的队列设置了存活时间
- 消息本身设置了存活时间
public void sendTtlMessage() {// 创建消息,设置消息体和 TTL(单位:毫秒)Message message = MessageBuilder.withBody("hello, ttl message".getBytes(StandardCharsets.UTF_8)).setExpiration("5000") // 设置消息的存活时间为 5000 毫秒(5 秒).build();// 消息 ID,需要封装到 CorrelationData 中,用于确认机制等场景CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());// 发送消息到指定交换机和路由键rabbitTemplate.convertAndSend("ttl.direct", "ttl", message, correlationData);}
延迟消息插件
回答
我们当时的 xx 项目有一个 xx 业务,需要用到延迟队列,其中就是使用 RabbitMQ 来实现的。
延迟队列就是用到了死信交换机和 TTL(消息存活时间)实现的。
如果消息超时未消费就会变成死信,在 RabbitMQ 中如果消息成为死信,队列可以绑定一个死信交换机,在死信交换机上可以绑定其他队列,在我们发消息的时候可以按照需求指定 TTL 的时间,这样就实现了延迟队列的功能了。
我记得 RabbitMQ 还有一种方式可以实现延迟队列,在 RabbitMQ 中安装一个死信插件,这样更方便一些,我们只需要在声明交互机的时候,指定这个就是死信交换机,然后在发送消息的时候直接指定超时时间就行了,相对于死信交换机 + TTL 要省略了一些步骤
RabbitMQ如果有100万消息堆积在MQ,如何解决?
惰性队列
回答
我在实际的开发中,没遇到过这种情况,不过,如果发生了堆积的问题,解决方案也所有很多的
第一:提高消费者的消费能力,可以使用多线程消费任务
第二:增加更多消费者,提高消费速度
使用工作队列模式,设置多个消费者消费消费同一个队列中的消息
第三:扩大队列容积,提高堆积上限
可以使用 RabbitMQ 惰性队列,惰性队列的好处主要是
①接收到消息后直接存入磁盘而非内存
②消费者要消费消息时才会从磁盘中读取并加载到内存
③支持数百万条的消息存储