问题场景
在使用Spring RabbitMQ做消息监听时,如果监听程序处理异常了,且未对异常进行捕获,会一直重复接收消息,然后一直抛异常。为了更好的描述问题,下面写个简单的例子。
通过访问null对象来引发空指针异常,消息监听处理程序代码清单:
|
|
往消息监听队列发送一条消息,控制台不停打印异常日志:
|
|
问题来了:为什么异常时会一直重复接收这条消息?
抓包验证
消息监听程序异常的过程到底发生了什么?为了一探究竟,笔者使用Wireshark抓包工具来查看消息处理过程。
首先,修改下监听程序,收到字符串exception时产生异常
|
|
1.监听程序正常处理情况
启动监听程序,Wireshark抓包情况:
我们主要关注最后一列,这一列展示了请求的AMQP协议方法信息,AMQP协议方法包含类名+方法名+参数,这一列主要展示了类名和方法名,点击对应行可以查看参数信息。比如上图:
- Connection.Start:请求服务端开始建立连接
- Channel.Open:请求服务端建立信道
- Queue.Declare:声明队列
- Basic.Consume:开始一个消费者,请求指定队列的消息
AMQP协议方法更详细介绍可以查看官网
然后,通过客户端发送一条消息
|
|
抓包:
分析:
- Basic.Publish: 客户端发送Basic.Publish方法请求,将消息发布到exchange,rabbitmq server会根据路由规则转发到队列中
- Basic.Deliver: 服务端发送Basic.Deliver方法请求,投递消息到监听队列的客户端消费者
- Basic.Ack: 客户端发送Basic.Ack方法请求,告知rabbimq server,消息已接收处理
2.监听程序异常处理情况
通过客户端发送exception字符串,制造异常
抓包:
分析:
- Basic.Reject: 客户端发送Basic.Reject方法请求,表示无法处理消息,拒绝消息,此时的requeue参数为true,将消息返回原来的队列
- Basic.Deliver: 服务端调用Basic.Deliver方法,和第一次Basic.Deliver方法不同的是,此时的redeliver参数为true,表示重新投递消息到监听队列的消费者
然后这两步会一直重复下去。对于Basic.Reject方法,可以设置requeue参数为false,这样消息无法处理的时候就不会重新入队了,他会根据异常类型选择直接丢弃或加入dead-letter-exchange中。Spring RabbitMQ配置:
结论
RabbitMQ消息监听程序异常时,消费者会向rabbitmq server发送Basic.Reject,表示消息拒绝接受,由于Spring默认requeue-rejected配置为true,消息会重新入队,然后rabbitmq server重新投递,造成了程序一直异常的情况。所以说了这么多,我们通过rabbitmq监听消息的时候,程序一定要添加try…catch语句!!!当然你也可以根据实际情况,选择设置requeue-rejected为false来丢弃消息。
参考
[1] AMQP协议方法
[2] Wireshark抓包教程
[3] http://blog.csdn.net/u013256816/article/details/55515234
[4] http://fengchj.com/?p=2234
[5] https://yemengying.com/2017/01/30/how-does-rabbitmq-handle-exception/