实习遇到一个场景:
在rabbitmq消息确认设置为手动提交的时候,消息怎么在重试一定次数的情况下才放入死信队列。
rabbitmq的配置:消费端手动应答
在手动确认的条件下这些参数不太好使
原以为使用如上配置消息在重试三次之后,就会放入死信队列,事实上手动提交的时候,basicNack的最后一个参数requeue = true时,消息会被无限次的放入消费队列重新消费,直至回送ACK。
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
但是当requeue = false 的时候,此时消息就会立马进入到死信队列。
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
那么手动提交怎么去设置消息消费失败后回到队列的思路呢?
RabbitMQ实现重试次数方法一-SpringRetry
RabitMQ实现重试次数二-放入消息体
使用redis 存储消息的重试次数,redis 的key 就是消息的Id, value为消息的重试次数,代码如下
//消费失败重试3次,3次失败后放入死信队列
msgId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
int retryCount = (int) redisUtil.get(msgId);
System.out.println("------ retryCount : " + retryCount);
if (retryCount >= MAX_RECONSUME_COUNT) {
//requeue = false 放入死信队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
} else {
//requeue = true 放入消费队列重试消费
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
redisUtil.set(msgId, retryCount + 1);
}
CREATE TABLE `msg_log` (
`msg_id` varchar(255) NOT NULL DEFAULT '' COMMENT '消息唯一标识',
`msg` text COMMENT '消息体, json格式化',
`exchange` varchar(255) NOT NULL DEFAULT '' COMMENT '交换机',
`routing_key` varchar(255) NOT NULL DEFAULT '' COMMENT '路由键',
`status` int(11) NOT NULL DEFAULT '0' COMMENT '状态: 0投递中 1投递成功 2投递失败 3已消费',
`try_count` int(11) NOT NULL DEFAULT '0' COMMENT '重试次数',
`next_try_time` datetime DEFAULT NULL COMMENT '下一次重试时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`msg_id`),
UNIQUE KEY `unq_msg_id` (`msg_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息投递日志';
select <include refid="Base_Column_List"/>
from c_consume_log
where status = 0
and nextTryTime <= now()
开启Broker持久化,消息写入之后会持久化到磁盘,哪怕Broker挂了,回复之后会自动读取之前存储的数据。
设置持久化有两个步骤 :
持久化可以跟生产者那边的 confirm 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者 ack 了,所以哪怕是在持久化到磁盘之前,Broker 挂了,数据丢了,生产者收不到 ack,你也是可以自己重发的
前提: 关闭自动ACK,改为消费端手动应答,但是关闭自动ACK的时候springboot中配置的retry次数就会失效,手动提交的时候basicNack最后一个参数requeue = true的时候,消息会被无限次的重新放入到消费队列重新消费,而requeue =false的时候,这个消息又会立马进入到死信队列。
怎么才能设置一定的消费重试次数呢?
幂等性保证
根据msgID从消费记录表中取值,如果该记录状态已经为CONSUMED_SUCCESS,则直接返回
MsgLog msgLog = msgLogService.selectByMsgId(msgId);
if (null == msgLog || msgLog.getStatus().equals(Constant.MsgLogStatus.CONSUMED_SUCCESS)) {// 消费幂等性
log.info("重复消费, msgId: {}", msgId);
return;
}
然后如果短信发送成功了,就回送MQ一个ACK,并且更改消费记录表中为消费成功
如果短信发送失败了,此时根据redis key 获取消息的重试次数,如果小于3次的话,requeu = true重新放入队列中并且重新消费,重试次数+1,如果大于3次的话,会送NACK,并且queue = false,此时消息被拒收放入死信队列。 进入死信队列可以去查看消息消费失败的原因