Rabbitmq消费手动提交basicNack时结合Redis实现消费重试次数

实习遇到一个场景:
在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 存储消息的重试次数,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);
            }

结果:
在这里插入图片描述

总结 投递成功 + 消费成功 + 幂等性保证

100% 投递到MQ成功
  1. 前提:开启confirm模式,在生产者那里设置了confirm模式之后,每次写的消息都会分配一个唯一的id,然后如果写入了RabbitMQ中,RabbitMQ回回传一个ack消息,告诉你这个消息ok了。如果没有的话,会回调给你一个nack接口,告诉你这个消息接受失败,你可以重试。(这里我只记录了一下日志)
  2. 发送消息的时候, 调用uuid随机生成一个msgId,作为消息的correlationData,可以确保消息的唯一性,并且用这个msgid构造Rediskey,然后生成消费表记录,设置状态为投递正在处理中0,然后进行发送。
  3. 如果投递到exchange成功,回调了ack接口,此时根据correlationData获取消息msgId,然后更改消费表里面的消息的消费状态为投递成功1
  4. 定时任务接入: 定时任务去扫描消息消费表,扫描那些已经超时(2分钟)但是消息状态还在投递中的消息,进行重新投递(此时就直接投递,不需要表记录),并设置retrycount,如果大于最大的retrycount(3次),就更改消息状态为投递失败。
  • 消息表结构
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持久化,消息写入之后会持久化到磁盘,哪怕Broker挂了,回复之后会自动读取之前存储的数据。

设置持久化有两个步骤 :

  • 创建queue的时候将其设置为持久化,这样就可以保证RabbitMQ持久化queue的原数据,但是它不会持久化queue里的数据
  • 第二个是发送消息的时候将消息的deleveryMode设置为2,就是将消息设置为持久化的,此时RabbitMQ就会将消息持久化到磁盘上去

持久化可以跟生产者那边的 confirm 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者 ack 了,所以哪怕是在持久化到磁盘之前,Broker 挂了,数据丢了,生产者收不到 ack,你也是可以自己重发的

Consumer 消费重试 以及幂等性保证

  1. 前提: 关闭自动ACK,改为消费端手动应答,但是关闭自动ACK的时候springboot中配置的retry次数就会失效,手动提交的时候basicNack最后一个参数requeue = true的时候,消息会被无限次的重新放入到消费队列重新消费,而requeue =false的时候,这个消息又会立马进入到死信队列。
    怎么才能设置一定的消费重试次数呢?

  2. 幂等性保证
    根据msgID从消费记录表中取值,如果该记录状态已经为CONSUMED_SUCCESS,则直接返回

    
        MsgLog msgLog = msgLogService.selectByMsgId(msgId);
        if (null == msgLog || msgLog.getStatus().equals(Constant.MsgLogStatus.CONSUMED_SUCCESS)) {// 消费幂等性
            log.info("重复消费, msgId: {}", msgId);
            return;
        }
    
  3. 然后如果短信发送成功了,就回送MQ一个ACK,并且更改消费记录表中为消费成功

  4. 如果短信发送失败了,此时根据redis key 获取消息的重试次数,如果小于3次的话,requeu = true重新放入队列中并且重新消费,重试次数+1,如果大于3次的话,会送NACK,并且queue = false,此时消息被拒收放入死信队列。 进入死信队列可以去查看消息消费失败的原因

  • 2
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值