RabbitMQ消息确认(ACK)机制
一、概念
在MQ接受消息处理过程中,消费者进程在处理消息时发生异常,那么这条消息就没有完成消费,数据就会丢失,为了确保数据不会丢失,RabbitMQ支持消息确认-ACK
ACK机制是在消费者进程从RabbitMQ收到消息处理完成后,反馈给RabbitMQ,RabbitMQ收到反馈后才能将此消息从队列中删除。如果出现消费者服务器或者进程宕机,没有及时反馈ACK,那么RabbitMQ就不会将消息删除,并重新返回队列。
但是如果出现业务逻辑出错,也就是忘记或者逻辑错误没有反馈ACK,那么消息就会一直堆积在内存中,在RabbitMQ中表示消息显示Unack,达到上限后,应用将无法消费队列中消息,单个应用进程上限是通道数量channlCount * 预读取prefetchCount (channlCount通道数量由Concurrency,max-concurrency决定)
二、模拟实验
1、MQ连接配置
开启ACK模式
方式一、使用Spring AMQP连接框架,在Spring连接工厂或者自定义的连接工厂中设置, acknowledge-mode:manual
方式二、使用RabbitMQ Client在basicConsume接收消息方法中定义
消费者数量
cocurrency:5
max- cocurrency:10
设置预读取
prefetch:5
2、代码示例
连接工厂配置
接收代码:监听接收队列名称为“queue”队列,打印message body模拟耗时3秒。处理完成后手动ACK并打印消息tag,如出现错误反馈NACK,应用于单条消息,不requeue销毁或者进入死信。
发送队列:定义发送通道,定义队列queue并发送message,循环i次间隔1秒。
测试正常效果:发送30条消息并接收
通道状态(发送)
队列状态
通道状态(接收):与我们配置的一样5个消费通道,每个预处理5条
队列状态:由于我处理时间存在3秒的等待,所以出现图表统计中体现Unacked数量
接受应用日志:与我们预计的一样,5个通道每批次处理5条,每批次tagid一致
异常测试一:模拟空指针异常处理逻辑,异常被捕获
发送与之前一样,接受效果如下,由于异常捕获后应答Nack所以队列中的消息也可以被处理,不会堆积
日志中显示报错,但能处理消息
如果定义死信队列,可以在死信队列找到消息,并可以看出原因
异常测试二:模拟接受消息为空,对消息处理出现空指针异常,异常未被捕获或者应答时候出现异常,比如Message出现null或者io Exception未捕获,就是方法逻辑写在try/catch外,Message异常也会引起问题,比如没有定义finally,将应答写在finally中。
两者现象都一致如下,也就是当逻辑出现问题没有正常应答,消息就在通道中,也就是消费到通道个数5*预读取prefetch5=25个的时候,就出现阻塞,无法消费后续Ready的消息
进程重启后这些unacked消息会回到队列,观察到Unacked数字一下归零,Ready数字变成30
如果在阻塞时,启动新的消费者,Ready的消息可以被消费,Unacked消息无法被消费直到应答或者应用进程中断
三、总结
调整cocurrency和prefetch大小可以缓解阻塞问题的发生频率,但要根本解决阻塞问题还需要从逻辑上完成应答处理。