消息可靠性
- RabbitMQ如何保证消息可靠性?
- RabbitMQ如何保证消息不丢失?
RabbitMQ通过以下方式来保证消息的可靠性和防止消息丢失:
- 持久化:RabbitMQ允许将消息标记为持久化,这意味着消息将会被写入磁盘而不是仅保存在内存中。这样,在消息发送到队列之后,即使RabbitMQ服务器发生故障或重启,消息也能够存储在磁盘上,并在恢复后仍然可用。
- 发布确认机制(Publisher Confirms):生产者可以使用发布确认机制来确保消息已经成功地发送到RabbitMQ。通过对每条消息进行确认,生产者可以得知消息是否已经安全地传递到RabbitMQ服务器。如果未收到确认或收到失败的确认,生产者可以选择重发消息,以确保消息的可靠传递。
而对于防止消息丢失的机制,RabbitMQ提供了以下方法:
- 消费者确认机制(Consumer Acknowledgements):消费者在正确处理消息后,可以向RabbitMQ发送确认。这样,在消息被消费并确认之前,RabbitMQ不会将其从队列中删除。如果消费者在处理消息期间发生错误,消息将会被重新投递给其他消费者,从而避免了消息的丢失。
- 消息预取(Message Prefetch):RabbitMQ允许消费者一次从队列中获取多个消息,并将它们存储在本地缓冲区中。这样可以提高消费者的效率,并减少消费者与RabbitMQ服务器之间的通信次数。如果消费者崩溃或断开连接,尚未确认的消息将会被重新投递给其他消费者,从而防止消息的丢失。
使用这些机制,可以最大程度地确保消息的可靠性和防止消息丢失,但仍需根据实际需求和场景进行配置和调优。
- RabbitMQ发送与消费消息的模型
diff
-
消息丢失的几种情况?
- 1、生产者发送消息未到达交换机
- 2、消息到达交换机,没有正确路由到队列
- 3、MQ宕机,队列中的消息不见了
- 4、消费者收到消息,还没消费完,消费者宕机
如何保证消息不丢失?
- 消息持久化:在消息发布时,将消息标记为持久化。这样,即使在RabbitMQ服务器崩溃或重启时,消息也会被写入磁盘而不会丢失。
- 发布确认机制:生产者可以使用发布确认机制来确认消息是否成功发送到RabbitMQ。在发送消息后,等待来自RabbitMQ的确认回复。如果未收到确认或收到了发送失败的确认,可以选择重发消息以确保消息的可靠传递。
- 消费者确认机制:消费者在正确处理消息后,向RabbitMQ发送确认。这样,在消息被消费并确认之前,RabbitMQ不会将其从队列中删除。如果消费者在处理期间发生错误,消息会被重新投递给其他消费者。
- 发布与确认的事务模式:将发布和确认操作包装在事务中。这样,只有在确认事务提交后,消息才会被RabbitMQ真正接收和保存。如果事务回滚或失败,消息发送将被撤销,避免消息的丢失。
- 适当的消息确认模式:根据应用程序的要求,选择适当的消息确认模式。例如,在处理消息之前先接收到后续消息,然后再确认前面的消息,以确保消息的顺序性和完整性。
- 高可用性和冗余设置:配置多个RabbitMQ节点,使用镜像队列或集群来提高可用性和冗余性。这样,即使一个节点发生故障,消息仍可被其他节点接收和处理。
- 监控和报警:设置监控和报警机制来及时检测和处理消息丢失或其他异常情况,以减少潜在的风险。
如何解决呢
- 消息能不能丢?
markdown
- 得看
- 不太重要的消息,丢了也没事
- 重要的消息,跟事务数据一致性相关的消息,不能丢的
- 1、生产者确认机制
markdown
- 1、publisher-confirm
- 消息成功投递到交换机,返回ack
- 消息未成功投递到交换机,返回nack
- 如何失败,就要重新发送
- 2、publisher-return
- 未正确到达队列,返回ack及失败原因
- 3、图示
-
- 4、实现
ruby
- 1、配置文件
- spring: rabbitmq: publisher-confirm-type: correlated publisher-returns: true template: mandatory: true
- publish-confirm-type:开启publisher-confirm
- simple:同步等待confirm结果,直到超时
- correlated:异步回调,定义ConfirmCallback,MQ返回结果时会回调这个ConfirmCallback
- publish-returns:开启publish-return,同样是基于callback机制,不过是定义ReturnCallback
- template.mandatory:定义消息路由失败时的策略。
- true,则调用ReturnCallback
- false:则直接丢弃消息
- 2、定义ConfirmCallback
- ConfirmCallback可以在发送消息时指定,因为每个业务处理confirm成功或失败的逻辑不一定相同。
- public void testSendMessage2SimpleQueue() throws InterruptedException { // 1.消息体 String message = "hello, spring amqp!"; // 2.全局唯一的消息ID,需要封装到CorrelationData中 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); // 3.添加callback correlationData.getFuture().addCallback( result -> { if(result.isAck()){ // 3.1.ack,消息成功 log.debug("消息发送成功, ID:{}", correlationData.getId()); }else{ // 3.2.nack,消息失败 log.error("消息发送失败, ID:{}, 原因{}",correlationData.getId(), result.getReason()); } }, ex -> log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),ex.getMessage()) ); // 4.发送消息 rabbitTemplate.convertAndSend("", "simple.queue", message, correlationData); // 休眠一会儿,等待ack回执 Thread.sleep(2000); }
-
- 3、定义Return回调
- 每个RabbitTemplate只能配置一个ReturnCallback,因此需要在项目加载时配置。
- @Slf4j @Configuration public class CommonConfig implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 获取RabbitTemplate RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class); // 设置ReturnCallback rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { // 投递失败,记录日志 log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}", replyCode, replyText, exchange, routingKey, message.toString()); // 如果有业务需要,可以重发消息 }); } }
-
- 持久化机制
markdown
- 1、交换机持久化
- 默认就是持久化
- 2、队列持久化
- 默认就是持久化
- 3、消息持久化
- 默认就是持久化
- 在发送消息时,使用Message对象,并设置delivery-mode为持久化
-
- 消费者ack机制
markdown
-
- ack取值
- none:只要消息到达消费者,Spring直接返回ack到MQ
- MQ收到ack,会把队列中的消息删除
- 消息会丢失
- 演示
- 消费者配置
- spring: rabbitmq: listener: simple: acknowledge-mode: none # 关闭ack
- manual:手动ack
- 消费成功,调用API给MQ返回ack
- 消费失败,调用API给MQ返回nack,并且让消息重回队列
- 演示
- 消费者配置
- spring: rabbitmq: listener: simple: acknowledge-mode: manual #手动ack
- 消费者代码
-
- auto:自动ack【默认值】。消费消息不出异常,返回ack给MQ。消费消息出异常了,返回nack,把消息重回队列
- 1、本地重试
- 消费者在消费消息时,如果失败了,则在本地重试,重新消费,如果达到重试次数,还是失败,则返回ack,不重回队列,MQ会删除队列中的消息
- spring: rabbitmq: listener: simple: retry: enabled: true #开启消费者失败重试 initial-interval: 1000 #初始的失败等待时长为1秒 multiplier: 2 #失败的等待时长倍数,下次等待时长 = multiplier * last-interval max-attempts: 3 #最大重试次数 stateless: true #true无状态;false有状态。如果业务中包含事务,这里改为false
- 2、失败策略
- 1、RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
- 2、ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
- 3、RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机
-
- 3、使用 RepublishMessageRecoverer
- 需求:把消息投递到失败的交换机,路由队列。记录日志,将来人工干预
- 实现
- 1、定义错误交换机、队列、绑定关系。定义RepublishMessageRecoverer
-
- 3、监听错误队列
-
--
diff
- 1、生产者开启confirm机制,保证消息正确到达交换机
- 2、生产者开启return机制,保证消息正确到达队列
- 3、交换机、队列、消息进行持久化
- 4、消费者开启手动ack,或者自动ack + 重试耗尽的失败策略,定义错误交换机队列,后期通过人工进行干预
- 消息重复消费问题
要解决消息重复消费的问题,可以考虑以下几种方法:
-
唯一标识符和去重:在生产者端或消息内容中添加唯一标识符,用于标识每条消息的唯一性。消费者在处理消息前,先检查该标识符是否已经处理过相同的消息,如有则进行去重操作。
-
消费者端幂等性:设计消费者端的处理逻辑具有幂等性。即无论消息被处理多次,最终结果都保持一致。这样,即使消息被重复消费,也不会对最终结果产生影响。
-
消费者确认模式(手动应答):在消费者处理消息后,通过显式发送确认(acknowledgement)给RabbitMQ,确认消息已经被成功处理。RabbitMQ在收到确认后,才会将消息从队列中删除。如果消费者在处理消息过程中发生错误,可以选择不发送确认,使消息重新投递给其他消费者。
-
幂等性队列:某些消息队列系统或中间件提供了幂等性队列的支持。这样的队列会自动处理重复消息,并确保同一条消息不会被重复消费。
-
消息超时设置:在消息中设置一个合理的超时时间。如果消费者未能及时处理消息,超过超时时间后消息会被重新投递给其他消费者,避免了消息长时间占据队列而无法被处理的问题。
-
监控和日志记录:建立完善的监控系统,及时检测和记录消息消费的状态和异常情况。通过监控和日志记录,可以对消息的消费情况进行追踪和分析,及时发现重复消费问题并进行处理。
-
1、问题
- 同一个消息如何保证重复消费问题?
- 消息如何保证幂等性?
-
2、答:消费者的操作是否是一个幂等性操作?
-
什么是幂等性?
-
多次执行同一个操作,最终的结果是一样的
-
如
-
幂等
-
查询
-
根据条件删除
- 第一次,删除成功
- 第二次及以后,虽然数据已经不存在了,但是删除操作也是成功的,只是没有删除数据。所以也不影响
-
更新
- update 表名 set 字段=值 where id = ?
-
-
非幂等性
-
增加
- 多次执行,会插入多少数据
-
更新
- update 表名 set 字段=字段 - 值 where id = ?
-
保存订单、扣减库存等操作
-
-
-
-
对于幂等性操作,多次消费消息,除开性能的影响 ,其他没有什么大问题,可以不管它
-
对于非幂等性操作,多次消费消息,会造成数据一致性的问题,所以要保证重复消费消息的问题
-
-
-
- 消息积压问题
要解决消息积压问题,可以考虑以下几个方面:
- 消费者数量和消费速度:增加消费者的数量,以提高消息的处理速度。通过水平扩展消费者,可以分摊每个消费者的负载,从而加快消息的处理速度。
- 提高消费者处理能力:优化消费者的处理逻辑和代码,提高处理消息的效率和速度。可以考虑并发处理消息、批量处理消息等方式来提高消费者的处理能力。
- 动态调整消费者数量:根据消息队列中消息的数量和消费者的处理速度,动态调整消费者的数量。通过监控消息队列积压情况,自动增加或减少消费者的数量,以适应消息的负载。
- 增加队列容量:增加消息队列的容量,以容纳更多的消息。增加队列容量可以减少消息的排队等待时间,从而降低消息积压的风险。
- 定时任务和重试机制:定期检查消息队列中的积压情况,并尝试重新投递或延迟处理积压的消息。通过设置合适的重试机制和延迟时间,可以有效地处理消息积压的情况。
- 监控和报警:设置监控系统来实时监测消息队列的积压情况。当积压量超过一定阈值时,及时触发报警机制,通知相关人员进行处理和调整。
- 异步处理和削峰填谷:将消息的处理过程异步化,通过引入消息中间件和异步处理机制,将消息的产生和消费解耦。这样可以使消息的处理能力更加灵活,同时也能够满足消息的高峰期和低谷期之间的处理需求。
-
1、产生的原因?
- 生产者生产消息的速度 远高于 消费者消费消息的速度?于是就会造成消息积压在MQ中
-
2、分析为什么会有消息积压?
-
1、设计是否有问题?
-
如果是,重新设置生产者与消费者数量匹配
-
如
- 一个生产者
- 多个消费者
-
-
优化架构
-
-
2、消费者出问题?
-
1、消费者出异常了
- 修改消费者代码,让消费者正常工作
-
2、消费者宕机了
- 第一步:修复宕机的情况
- 第二步:临时开启多个消费者,来以多倍速消费积压的消息。当积压的消息消费的差不多的情况,关闭临时消费者
-
-
-
3、惰性队列
- 可以放很多消息,还可以把多的消息持久化到本地
-