目录
RabbitMQ高级特性
消息如何保障100%的投递成功?
幂等性概念详解
在海量订单产生的业务高峰期,如何避免消息的重复消费问题?
Confirm确认消息、Return返回消息
如何实现Confrim确认消息?
Return消息机制
自定义消费者
消息的ACK与重回队列
消息的限流
TTL消息
死信队列
延迟队列
优先级队列
什么是生产端的可靠性投递?
保障消息的成功发出
保障MQ节点的成功接收
发送端到MQ节点(Broker)确认应答
完善的消息进行补偿机制
BAT/TMD互联网大厂的解决方案:
1.消息落库,对消息状态进行打标
图的解读:
step1:入库,保存订单消息 create message入库 (保存业务消息,对业务消息进行记录,两次数据库的写操作)step2:发送消息 step3:消息应答
数据库的两次写操作发送给生产端,此时消息的状态是0,生产端发送消息给Broker,Broker确认消息 然后监听者将消息更新为1,step将状态为1的数据取出来。
假如在step3出现了网络故障,因为是RPC协议,那么此时消息永远为0(消息发送中),所以得有一个消息超时处理机制,比如每5分钟定时从数据库中取出消息状态为0的数据进行step6,step7是需要人工操作的(很少发生)。
2.消息的延迟投递,做二次确认,回调检查
保障MQ我们思考如果第一种可靠性投递,在高并发的场景下是否适合?
图的解读:
订单消息入库然后是step1发送第一次消息的发送,业务消息入库之后再对消息进行发送,step2第二条消息是延迟投递,step3监听 step4:发送响应(成功的消息生成一条新的消息发送给callback 服务)step5:监听消息成功/失败的新的消息,callback然后再数据库存储msg db step2消息需要callback服务区做投递。如果step4有问题了,就要callback服务做补偿,发起RPC command命令,然后再走入库然后step1到。。关注性能。
少做了一次DB存储
幂等性是什么?
无论执行多少次任务结果都一致。
消费端实现幂等性,就意味着,我们的消息永远不会消费多次,即使我们收到了多条一样的消息。
业界主流的幂等性操作:
1.唯一ID + 指纹码机制,利用数据库主键去重
SELECT COUNT(1) FROM T_ORDER WHERE ID = 唯一ID + 指纹码
好处:实现简单
坏处:高并发下有数据库写入的性能瓶颈
解决方案:跟进ID进行分库分表进行算法路由
2.利用Redis的原子性去实现
使用Redis进行幂等,需要考虑的问题
第一:我们是否要进行数据落库,如果落库的话,关键解决的问题是数据库和缓存如何做到原子性?
第二:如果不进行落库,那么都存储到缓存中,如何设置定时同步的策略?
Confirm确认消息流程解析
确认机制流程图:
第一步:在channel上开启确认模式:channel.confirmSelect()
第二步:在channel上添加监听:addConfrimListen,监听成功和失败的返回结果,根据具体的结果对消息记性重新发送、或记录日志等后续处理!
Return Listener 用于处理一些不可路由的消息!
我们的消息生产者,通过指定一个Exchange和Routingkey,把消息送达到某一个队列中去,然后我们的消费者监听对列,进行消费处理操作!
但是在某些情况下,如果我们在发送消息的时候,当前的exchange不存在或者指定的路由key路由不到,这个时候如果我们需要监听这种不可达的消息,就要使用Return Listener!
在基础API中有一个关键的配置项:Mandatory:如果为true,则监听器会接收到路由不可达的消息,然后进行后续处理,如果为false,那么broker端自动删除该消息!所以我们得更改为true
消费者自定义监听:原型图
项目中原来的使用
自定义消费者:
消费端的手工ACK和NACK
消费端进行消费的时候,如果由于业务异常我们可以进行日志的记录,然后进行补偿!
如果由于服务器宕机等严重问题,那我们就需要手动进行ACK保障消费端消费成功!
消费端的重回队列
消费端的重回队列是为了对没有处理成功的消息,把消息重新回递给Broker!
一般我们在实际应用中,都会关闭重回队列,也就是设置为false
把发送失败的消息重新放到尾端
消费端限流
RabbitMQ提供了一种qos(服务质量保证)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过基于consume或者channel设置qos的值)未被确认前,不进行消费新的消息。
void BasicQos(uint prefetchSize,ushort prefetchCount,boolean global);
注意:
prefetchSize:0
global:false
prefetchCount在no_ask= false的情况下生效,即在自动应答的情况下不生效。
Time To Live,生存时间
RabbitMQ支持消息的过期时间,在消息发送时可以进行指定
RabbitMQ支持队列的过期时间,从消息入队列开始计算,只要超过了队列的超时时间配置,那么消息会自动的清除
设置TTL两种方式:在管控台设置
在代码中设置
DLX,Dead-Letter-Exchange
利用DLX,当消息在一个队列中变成死信(dead message)之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX。
消息变成死信情况:
消息被拒绝
TTL过期
队列达到最大长度
DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。
当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。
可以监听这个队列中消息做相应的处理,这个特性可以弥补RabbitMQ3.0以前支持的Immediate参数的功能。
死信队列设置:
交换机
队列
绑定关系
只不过需要在队列中加上一个参数:arguments.put("x-dead-letter-exchange","dlx.exchange");
这样消息在过期、requeue、队列达到最大长度时,消息可以直接路由到死信队列。
produce:
下面这幅图和上面图的区别是消息设置了属性,添加了过期时间
consumer:
生成正常的队列,在声明队列的时候加上参数arguments,如果此时路由失败要进行死信队列的声明,这里的exchange和路由key做一个绑定。
当消息被发送以后,不让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。
延迟队列的使用场景
订单系统,用户下单通常有30分钟的时间进行支付,如果30分钟内没有支付成功,这个订单将会进行异常处理,这时就是使用延迟队列来处理订单。
用户希望通过手机远程遥控家里面的智能设备在指定的时间内进行工作。将用户指令发送给延迟队列。
具有高优先级的队列具有高的优先权,优先级高的消息具备优先被消费的特权。