背景
项目采用了rabbitmq作为消息中间件,并且开启了手动确认机制。但是抛出异常时没有主动确认,因此导致消息不断在消息队列中堆积,影响程序运行。因此花了一些时间查了rabbitmq的消息确认机制。
消息确认机制
- auto(默认)
- manual
援引rabbitmq官方文档的说法
在自动确认模式下,消息在发送后立即被视为成功投递。这种模式会牺牲更高的吞吐量(只要消费者能够跟上)以降低交付和消费者处理的安全性。这种模式通常被称为“即发即忘”。与手动确认模型不同,如果消费者的 TCP 连接或通道在成功传递之前关闭,则服务器发送的消息将丢失。因此,自动消息确认应被视为不安全 且不适合所有工作负载。
因此大部分情况我们会选择开启手动确认模式,即:manual来保证消息的可达。
手动确认的开启
rabbitmq:
username: admin
password: admin
host: 127.0.0.1
port: 5672
publisher-confirms: true #设置此属性配置可以确保消息成功发送到交换器
publisher-returns: true #可以确保消息在未被队列接收时返回
listener:
simple:
acknowledge-mode: manual #设置手动确认
prefetch: 100
与手动模式开启后相对应的确认操作函数有三个
- basic.ack用于肯定确认
- basic.nack用于否定确认(注意:这是[AMQP 0-9-1](https://www.rabbitmq.com/nack.html)的[RabbitMQ 扩展](https://www.rabbitmq.com/nack.html))
- basic.reject用于否定确认,但与basic.nack相比有一个限制
basic.reject和basic.nack主要用来当消费者无法立即处理交付且其他实例可能能够处理时,调用这两个方法使得消息重新进入消息队列排队并让另外一个消费者接收和处理它
当消息重新排队时,如果可能,它将被放置在其队列中的原始位置。如果不是(由于多个消费者共享一个队列时其他消费者的并发传递和确认),则消息将重新排队到更靠近队列头的位置。
使用手动确认时,任何未确认的传递(消息)都会在发生传递的通道(或连接)关闭时自动重新排队。这包括客户端的 TCP 连接丢失、消费者应用程序(进程)故障和通道级协议异常
因此需要注意的是,使用手动确认时,一定要保证消息的确认,否则消息可能会因为客户端的故障异常而导致重新进行排队,如果消费时的异常未回滚,则会导致业务的重复执行,从而引起数据或者逻辑错乱。
对于程序而言,最好就是用try..finally来保证消息的确认。笔者的情况就是因为没有使用try..finally保证消息的确认,从而导致了同一条消息消费失败后重新回到了队列当中,导致一致在重复消费消息,大大影响了系统性能。
参考链接:Consumer Acknowledgements and Publisher Confirms — RabbitMQ