RabbitMQ高级(面试题全)
一、消息可靠性
定义:消息可靠性就是指,消息由生产者发送到MQ,最终被消费者正常消费完成。这就叫消息可靠。
分析:生产者到消费者中每一步都有可能导致消息丢失,所以核心就是要保证信息安全的从生产者传到消费者。
1.1、消息丢失的所有情况
①:生产者连不上MQ:因为网络不可靠,导致生产者暂时连不上MQ
----------生产者开启充实策略
②:生产者发送消息未到达交换机
----------开启 confirm 回调机制。每个生产者独立配置
③:消息到达交换机,没有正确路由到队列
----------开启 return 回调机制,RabbitMQ只有一个(没到队列失败就会被回调)
④:MQ宕机:导致队列中的消息不见了
⑤:4、消费者收到消息,还没消费,消费者宕机
----------开启自动ack,并且设置本地重试策略,当本地重试次数耗尽后,失败消息路由到错误队列。
1.2、那我们怎么保证消息能不丢失
注:不重要的消息丢了也没事,但是重要的要保护好!(常识)
1.2.1:生产者确认机制
①生产者确认机制:1.Publisher Return:
消息投递成功但路由失败会调用Publisher Return回调方法返回异常信息。
2.Publisher Confirm:
消息投递成功返回ack,投递失败返回nack。
注:消息投注成功但是路由失败,此时会通过Publisher Confirm返回ack,通过Publisher Return回调方法返回异常信息。
②publisher-return未正确到达队列,返回akc以及失败原因。
③实现:配置文件之后
定义ConfirmCallback----定义Return回调
1.2.2:持久化机制
交换机队列、消息持久化都是默认的
还有:在发送消息时,使用Message对象,并设置delivery-mode为持久化
1.2.3:消费者ack机制
对消费者进行入如下处理
①none只要消息到达消费者,消费者直接返回ack给MQ:MQ收到ack,会把队列中的消息删除
(注:消息可能会丢失)
②manual手动ack:消费成功,调用API给MQ返回ack。消费失败,调用API给MQ返回nack,并且让消息重回队列。
③auto:自动ack。消费消息不出异常,返回ack给MQ。消费消息出异常了,返回nack,把消息重回队列。
④:失败重试机制:
本地重试:-开启本地重试时,消息处理过程中抛出异常,不会请求到队列,而是在消
费者本地重试。
-重试达到最大次数后,Spring会返回reject,消息会被丢弃。
失败策略:当消费者出现异常后,消息会不断requeue(重入队)到队列,再重新发送给消费者。如果消费者再次执行依然出错,消息会再次返回到队列,再次投递,直到消息处理成功为止。(这样子会造成MQ的不必要负担!)
失败消息入队:本地测试达到最大重试次数后,消息会被丢弃。这在某些对于消息可靠性要求较高的业务场景下,显然不太合适了。
因此Spring允许我们自定义重试次数耗尽后的消息处理策略,这个策略是由MessageRecovery接口来定义的。
有三个不同的实现:
-RejectAndDontRequeueRecoverer
:重试耗尽后,直接reject
,丢弃消息。默认就是这种方式。
-ImmediateRequeueMessageRecoverer
:重试耗尽后,返回nack
,消息重新入队
-RepublishMessageRecoverer
:重试耗尽后,将失败消息投递到指定的交换机
其中:推荐使用epublishMessageRecoverer,将失败消息投递到固定的交换机,通过交 换机将消息转发到失败消息队列,程序监听失败消息队列,接收到失败消息,将失败消息存入 失败消息表,通过定时任务进行处理。
1.2.4:幂等性相关
在程序开发中,是指同一个业务,执行一次或多次对业务状态的影响是一致的。例如:
-
根据id删除数据
-
查询数据
但是数据的更新往往是非幂等的,例如-----取消订单,恢复库存的业务。如果多次恢复就会出现库存重复增加的情况。
例如------ 退款业务。重复退款对商家而言会有经济损失。
所以,我们要尽可能避免业务被重复执行,然而在实际业务场景中,由于意外经常会出现业务被重复执行的情况。例如: -
页面卡顿时频繁刷新导致表单重复提交
-
服务间调用的重试
-
MQ消息的重复投递
基于以上情况我们给出两种解决方案: -
唯一消息ID:1. 思路非常简单,每一条消息都生成一个唯一的id,与消息一起投递给消费者
2. 消费者接收到消息后处理自己的业务,业务处理成功后将消息ID保存到数据库或Redis
3. 如果下次又收到相同消息,去数据库或Redis查询判断是否存在,存在则为重复消息放弃处理。 -
业务状态判断:业务判断就是基于业务本身的逻辑或状态来判断是否是重复的请求,不同的业务场景判断的思路也不一样。
例如:在支付通知案例中,处理消息的业务逻辑是把订单状态从未支付修改为已支付。因此我们就可以在执
行更新时判断订单状态是否是未支付,如果不是则证明订单已经被处理过,无需重复处理。
具体实现:我们在where条件中除了判断id以外,还加上了status必须为1的条件。
如果条件不符(说明订单已支付),则SQL匹配不到数据,根本不会执行。
UPDATEorder
SET status = ? , pay_time = ? WHERE id = ? AND status = 1(加上最后的status=1),只有状态为未被处理的才能被传递。
相较更推荐业务判断的方法。