MQ高级应用
消息可靠性
当MQ数据出现消息丢失,怎么解决?
首先常见的出现问题的场景:
- 发送消息时丢失:
-
- 生产者发送消息时连接MQ失败
- 生产者发送消息到达MQ后未找到
Exchange - 生产者发送消息到达MQ的
Exchange后 ,未找到合适的Queue - 消息到达MQ后,处理消息的进程发生异常
- MQ导致消息丢失:
-
- 消息到达MQ,保存到队列后,尚未消费就突然宕机
- 消费者处理消息时:
-
- 消息接收后尚未处理突然宕机
- 消息接收后处理过程中抛出异常
1. 生产消息可靠性
1.1 生产者重试机制
就是生产者发送消息时,出现了网络故障,导致与MQ的连接中断的问题 。
SpringAMQP提供的消息发送时的重试机制
rabbitmq:
connection-timeout: 1s # 设置MQ的连接超时时间
template:
retry:
enabled: true # 开启超时重试机制
initial-interval: 1000ms # 失败后的初始等待时间
multiplier[倍增]: 1 # 失败后下次的等待时长倍数,下次等待时长 = 上次等待时长 *
multiplier max-attempts: 3 # 总共尝试次数
重试是阻塞式的重试, 多次重试等待的过程中,当前线程是被阻塞的,
所以需要合理配置, 性能敏感责任需要考虑禁用,可以使用异步线程发送消息
1.2 生产者确认机制
双机制: publisher Confirm(确认)和Publisher Return(回调)
publisher Confirm: 消息投递成功返回ack,投递失败返回nack
Publisher Return: 消息投递成功但路由失败会调用Publisher Return回调方法返回异常信息。
针对复杂情况,可以结合使用两种完成确认
情景其一:
消息投递成功但可能路由失败了,此时会通过Publisher Confirm返回ack,通过Publisher Return回调方法返回异常信息。(查看具体原因)
配置参数:
publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型
publisher-returns: true # 开启publisher return机制
- none:关闭confirm机制
- simple:同步阻塞等待MQ的回执(回调方法)
- correlated:MQ异步回调返回回执
1.3 发送失败处理机制
java程序接收:
ReturnCallback() [回调检视路由是否失败]
ConfirmCallback()[确认消息投递成功成败,-[RabbitClient 工具类]使用sendMsg方法发送correlationData,在correlationData对象中实现回调逻辑 ↓↓↓↓↓]
如果失败:==>返回nack 持久化在数据表 ,之后定时获取数据重新发送,重发成功删除表中数据,失败继续发, 重发次数累计, 超过界限,通过人工转发
2. 消息持久化
为了提升性能,默认情况下MQ的数据都是在内存存储的临时数据,重启后就会消失。为了保证数据的可靠性,必须配置持久化 .
2.1 交换机持久化
交换机持久化是指将交换机的定义信息(元数据)持久化到RabbitMQ的数据库,重启后交换机定义仍然存在.
在RMQ中交换机创建默认为持久化 (Durable) [ 对应临时模式Transient]
2.2 队列持久化
队列持久化也是将队列的定义信息(元数据)持久化到RabbitMQ的数据库中。队列中存储的是消息的在队列中的位置、消息的ID、存储位置等. 队列持久化不能保证消息数据不会丢失。
2.3 消息持久化
3. 消费消息可靠性
需要知道消费者的处理状态,因为消息投递给消费者并不代表就一定被正确消费
消息投递过程中可能出现的问题
- 投递的过程中出现了网络故障
- 收到消息后突然宕机
- 收到消息后,因处理不当导致异常( )
3.1消费者确认机制
当消费者处理消息结束后,应该向RabbitMQ发送一个回执
- ack:成功处理消息,RabbitMQ从队列中删除该消息
- nack:消息处理失败,RabbitMQ需要再次投递消息
- reject:消息处理失败并拒绝该消息,RabbitMQ从队列中删除该消息
一般reject方式用的较少[使用场景-消息存在失误需要RMQ来解决问题]
SpringAMQP可以通过配置文件设置消息确认的处理方式,有三种模式:
none:不处理。即消息投递给消费者后消息会立刻从MQ删除。非常不安全,不建议使用manual:手动模式。需要自己在业务代码中调用api,发送ack或reject,存在业务入侵,但更灵活
通过 api指定是否重新入队,具体返回ack或nack通过手动编程实现。
由于手动模式需要通过api编程,需要在监听方法添加Channel、Message类型的参数,如下:
Message:是spring AMQP封装的底层消息对象。
Channel:是消费端与MQ基于通道的操作对象。
auto:自动模式。当业务正常执行时则自动返回ack. 当业务出现异常时,根据异常判断返回不同结果:
-
- 业务异常,会自动返回
nack; - 消息处理或校验异常,自动返回
reject返回
- 业务异常,会自动返回
-
-
- MessageConversionException(消息转换异常)
- MethodArgumentTypeMismatchException(方法参数类型不匹配异常)等
-
rabbitmq:
listener:
simple:
acknowledge-mode: auto # 自动ack
manual模式测试
3.2 消费者失败重试机制
极端情况就是消费者一直无法执行成功,消息投递就会无限循环,导致mq的消息处理飙升,带来不必要的压力
rabbitmq:
listener:
simple:
retry:
enabled: true # 开启消费者失败重试
initial-interval(最初时间间距): 1000ms # 初识的失败等待时长为1秒
multiplier(倍增): 1 # 失败的等待时长倍数,下次等待时长 = 上次等待时长 * multiplier
max-attempts: 3 # 最大重试次数
本地重试发送 1-2-4累计重试,失败后最终reject了
失败数据之后去哪里呢?
Spring允许我们自定义重试次数耗尽后的消息处理策略,这个策略是由MessageRecovery接口来定义的,它有3个不同实现:
RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机 (死信)
===>推荐使用RepublishMessageRecoverer,将失败消息投递到固定的交换机,通过交换机将消息转发到失败消息队列,程序监听失败消息队列,接收到失败消息,将失败消息存入失败消息表,通过定时任务进行处理。
3.3MQ消息幂等性--消费重复问题
幂等性是什么?
在程序开发中,是指同一个业务,执行一次或多次对业务状态的影响是一致的,成为幂等性(数据影响一致性)
天然支持幂等性的操作
- 根据id删除数据 一次, 二次删除[数据已经清空,所以重复的操作不影响]
- 查询数据
非幂等性:
- 添加操作: ==>多次执行,会插入多少数据==>唯一约束:只能解决一部分情况
- 更新: ==> 重复执行可能就会出现数据不一致的问题
- 业务相关:==>新建订单/库存扣除 由于诸多因素
数据的更新,新增一般是不符合幂等性,需:要进行额外处理
解决方案;
方案:数量统计 分布式锁
方案一: 唯一性ID -->数量统计 确定重复则放弃
方案二; (推荐) redis辅助存储
生产者发送消息设置携带id,若存在及证明有人正在消费,停止操作
如果不存在,表明为第一次消费,正常消费,并向存储Key 为消息id的数据存到redis,同时设置过期时间
业务判断幂等性:
保证性并非100%
举例;业务流转:
写入成功订单需要 更改支付状态就需要判断修改是否是未支付状态提前,,未对应,则状态流转失败
幂等性的适用性 情况一:运行之外的状态--宕机
支付状态确认-->手动确认/定时任务 手动则直接比对完成流程,或者定时任务自动确认
4、MQ可靠性保证并不是100%,所以有可能消费者没有收到MQ的消息,那么后续就无法执行
了。我们可以主动发起查询去获取最新结果,然后继续完成后面的业务操作.