三、RabbitMQ可靠性消息投递

278 阅读10分钟

一 、Spring整合RabbitMQ


1、添加maven依赖

<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>2.0.8.RELEASE</version>
</dependency>

2、配置ConnectionFactory

@Configuration
public class RabbitmqConfig {

    /**
     * todo 创建连接工厂
     * @return
     */
    @Bean
    public ConnectionFactory connectionFactory () {
        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
        cachingConnectionFactory.setHost("47.93.60.129");
        cachingConnectionFactory.setPort(5672);
        cachingConnectionFactory.setVirtualHost("cyan");
        cachingConnectionFactory.setUsername("cyan");
        cachingConnectionFactory.setPassword("cyan");
        cachingConnectionFactory.setConnectionTimeout(10000);
        cachingConnectionFactory.setCloseTimeout(10000);
        return cachingConnectionFactory;
    }

    /**
     * todo RabbitAdmin用于创建、绑定、管理队列与交换机
     * @param connectionFactory
     * @return
     */
    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        //todo spring容器启动加载该类
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }

    //todo =====================================声明三个交换机===========================================================
    @Bean
    public TopicExchange topicExchange() {
        TopicExchange topicExchange = new TopicExchange("cyan.topic.exchange",true,false);
        return topicExchange;
    }

    @Bean
    public DirectExchange directExchange() {
        DirectExchange directExchange = new DirectExchange("cyan.direct.exchange",true,false);
        return directExchange;
    }

    @Bean
    public FanoutExchange fanoutExchange() {
        FanoutExchange fanoutExchange = new FanoutExchange("cyan.faout.exchange",true,false);
        return fanoutExchange;
    }

    //todo ===========================================声明队列===========================================================
    @Bean
    public Queue testTopicQueue1() {
        Queue queue = new Queue("testTopicQueue1",true,false,false,null);
        return queue;
    }

    @Bean
    public Queue testTopicQueue2() {
        Queue queue = new Queue("testTopicQueue2",true,false,false,null);
        return queue;
    }

    @Bean
    public Queue testDirectQueue() {
        Queue queue = new Queue("testDirectQueue",true,false,false,null);
        return queue;
    }

    @Bean
    public Queue testFaoutQueue() {
        Queue queue = new Queue("testfaoutQueue",true,false,false,null);
        return queue;
    }

    //... ...省略部分队列声明

    //todo ========================================声明绑定关系==========================================================
    @Bean
    public Binding topicBingding1() {
        return BindingBuilder.bind(testTopicQueue1()).to(topicExchange()).with("topic.#");
    }

    @Bean
    public Binding topicBingding2() {
        return BindingBuilder.bind(testTopicQueue2()).to(topicExchange()).with("topic.key.#");
    }

    @Bean
    public Binding directBinding() {
        return BindingBuilder.bind(testDirectQueue()).to(directExchange()).with("direct.key");
    }

    @Bean
    public Binding orderQueueBinding() {
        return BindingBuilder.bind(orderQueue()).to(directExchange()).with("rabbitmq.order");
    }

   //... ...省略部分绑定关系

    /**
     * todo rabbitTemplate模板
     * @return
     */
    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
        rabbitTemplate.setReceiveTimeout(50000);
        return rabbitTemplate;
    }
}

3、自定义消息委托类

public class CyanMsgDelegate {

    public void handleMessage(String msgBody) {
        System.out.println("CyanMsgDelegate...... handleMessage"+msgBody);
    }

    public void consumerMsg(String msg){
        System.out.println("CyanMsgDelegate。。。。。。consumerMsg"+msg);
    }

    public void consumerTopicQueue1(String msgBody) {
        System.out.println("CyanMsgDelegate。。。。。。consumerTopicQueue1"+msgBody);

    }

    public void consumerTopicQueue2(String msgBody) {
        System.out.println("CyanMsgDelegate。。。。。。consumerTopicQueue2"+msgBody);

    }

    /**
     * todo 处理json
     * @param jsonMap
     */
    public void consumerJsonMessage(Map jsonMap) {
        System.out.println("CyanMsgDelegate ============================处理json"+jsonMap);
    }

    /**
     * todo 处理order对象
     * @param order
     */
    public void consumerJavaObjMessage(Order order) {
        System.out.println("CyanMsgDelegate ============================处理java对象"+order.toString());
    }

    /**
     * todo 处理文件(和图片)对象
     * @param file
     */
    public void consumerFileMessage(File file) {
        System.out.println("CyanMsgDelegate========================处理文件"+file.getName());
    }
}

4、使用默认的监听方法监听消息

@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer() {
    //todo 创建简单消息监听容器
    SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory());
    //todo 监听我们的队列
    simpleMessageListenerContainer.setQueues(testTopicQueue1(),testDirectQueue(),testTopicQueue2(),orderQueue(),addressQueue(),fileQueue());
    //todo 消费者的数量
    simpleMessageListenerContainer.setConcurrentConsumers(5);
    //todo 最大消费者数量
    simpleMessageListenerContainer.setMaxConcurrentConsumers(10);
    //todo 签收模式
    simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
    //todo 设置拒绝重回队列
    simpleMessageListenerContainer.setDefaultRequeueRejected(false);
    
    //设置使用默认的监听方法
    MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new CyanMsgDelegate());
    simpleMessageListenerContainer.setMessageListener(messageListenerAdapter);
    return simpleMessageListenerContainer;
}

发送消息测试:

@Test
public void simpleMessageListenerContainerTest() {
    rabbitTemplate.convertAndSend("cyan.topic.exchange","topic.xixi","你好 青子");
}

5、指定监听方法监听消息

//... ...省略部分代码
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new CyanMsgDelegate());
messageListenerAdapter.setDefaultListenerMethod("consumerMsg");
simpleMessageListenerContainer.setMessageListener(messageListenerAdapter);

发送消息测试:

@Test
public void simpleMessageListenerContainerTest() {
    rabbitTemplate.convertAndSend("cyan.topic.exchange","topic.xixi","你好 青子");
}

6、给指定队列绑定指定监听方法

//... ...省略部分代码
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new CyanMsgDelegate());
Map<String,String> queueMaps = new HashMap<>();
queueMaps.put("testTopicQueue1","consumerTopicQueue1");
queueMaps.put("testTopicQueue2","consumerTopicQueue2");
messageListenerAdapter.setQueueOrTagToMethodName(queueMaps);
simpleMessageListenerContainer.setMessageListener(messageListenerAdapter);

发送消息测试:

@Test
public void messageListenerAdaperQueueOrTagToMethodName(){
    rabbitTemplate.convertAndSend("cyan.topic.exchange","topic.xixi","你好 青子");
    rabbitTemplate.convertAndSend("cyan.topic.exchange","topic.key.xixi","你好 青子");
}

7、指定方法监听消息处理json数据

//... ...省略部分代码
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new CyanMsgDelegate());
messageListenerAdapter.setDefaultListenerMethod("consumerJsonMessage");
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
messageListenerAdapter.setMessageConverter(jackson2JsonMessageConverter);
simpleMessageListenerContainer.setMessageListener(messageListenerAdapter);

发送消息测试:

@Test
public void sendJson() throws JsonProcessingException {
    Order order = new Order();
    order.setOrderNo(UUID.randomUUID().toString());
    order.setCreateDt(new Date());
    order.setPayMoney(10000.00);
    order.setUserName("青子");
    ObjectMapper objectMapper = new ObjectMapper();
    String orderJson = objectMapper.writeValueAsString(order);
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setContentType("application/json");
    Message orderMsg = new Message(orderJson.getBytes(),messageProperties);
    rabbitTemplate.convertAndSend("cyan.direct.exchange","rabbitmq.order",orderMsg);
}

7、指定方法监听消息处理java对象

MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new CyanMsgDelegate());
messageListenerAdapter.setDefaultListenerMethod("consumerJavaObjMessage");
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
//todo 设置json转java对象
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
javaTypeMapper.setTrustedPackages("com.cyan.pojo");
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
messageListenerAdapter.setMessageConverter(jackson2JsonMessageConverter);
simpleMessageListenerContainer.setMessageListener(messageListenerAdapter);

发送消息测试:

@Test
public void sendJavaObj() throws JsonProcessingException {
    Order order = new Order();
    order.setOrderNo(UUID.randomUUID().toString());
    order.setCreateDt(new Date());
    order.setPayMoney(10000.00);
    order.setUserName("青子");
    ObjectMapper objectMapper = new ObjectMapper();
    String orderJson = objectMapper.writeValueAsString(order);
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setContentType("application/json");
    messageProperties.getHeaders().put("__TypeId__","com.cyan.pojo.Order");
    Message orderMsg = new Message(orderJson.getBytes(),messageProperties);
    rabbitTemplate.convertAndSend("cyan.direct.exchange","rabbitmq.order",orderMsg);
}

8、指定方法监听消息处理文件和图片

MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new CyanMsgDelegate());
messageListenerAdapter.setDefaultListenerMethod("consumerFileMessage");
//todo 设置转换器
ContentTypeDelegatingMessageConverter messageConverter = new ContentTypeDelegatingMessageConverter();
messageConverter.addDelegate("img/png",new CyanImageConverter());
messageConverter.addDelegate("img/jpg",new CyanImageConverter());
messageConverter.addDelegate("application/word",new CyanWordConverter());
messageConverter.addDelegate("word",new CyanWordConverter());
messageListenerAdapter.setMessageConverter(messageConverter);
simpleMessageListenerContainer.setMessageListener(messageListenerAdapter);

自定义图片转换器:

public class CyanImageConverter implements MessageConverter {

    @Override
    public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
        return null;
    }

    @Override
    public Object fromMessage(Message message) throws MessageConversionException {
        System.out.println("自定义的图片转换器................");
        String msgContentType = message.getMessageProperties().getContentType();
        String fileSuffix = null;
        if(msgContentType !=null &&(msgContentType.contains("png")||msgContentType.contains("jpg"))) {
            fileSuffix = msgContentType.split("/")[1];
        }else {
            fileSuffix="jpg";
        }
        byte[] msgBody = message.getBody();
        String filePrefixName = UUID.randomUUID().toString();
        String filePath = "d:/cyan/file02/"+filePrefixName+"."+fileSuffix;
        System.out.println("文件路径:"+filePath);

        File file = new File(filePath);
        try {
            Files.copy(new ByteArrayInputStream(msgBody), file.toPath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }
}

自定义word转换器:

public class CyanWordConverter implements MessageConverter {

    @Override
    public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
        return null;
    }

    @Override
    public Object fromMessage(Message message) throws MessageConversionException {
        System.out.println("自定义的文档转换器................");
        String msgContentType = message.getMessageProperties().getContentType();
        String fileSuffix = null;
        if(msgContentType !=null && msgContentType.contains("word")) {
            fileSuffix = "docx";
        }else {
            fileSuffix="doc";
        }
        byte[] msgBody = message.getBody();
        String filePrefixName = UUID.randomUUID().toString();
        String filePath = "d:/cyan/file02/"+filePrefixName+"."+fileSuffix;
        System.out.println("文件路径:"+filePath);
        File file = new File(filePath);
        try {
            Files.copy(new ByteArrayInputStream(msgBody), file.toPath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }
}

发送图片测试:

@Test
public void sendImage() throws IOException {
    byte[] imgBody = Files.readAllBytes(Paths.get("D:/cyan/file01","cyan.jpg"));
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setContentType("img/png");
    Message message = new Message(imgBody, messageProperties);
    rabbitTemplate.send("cyan.direct.exchange","rabbitmq.file",message);
}

发送word文件测试:

@Test
public void sendWord() throws IOException {
    byte[] imgBody = Files.readAllBytes(Paths.get("D:/cyan/file01","cyan.docx"));
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setContentType("application/word");
    Message message = new Message(imgBody, messageProperties);
    rabbitTemplate.send("cyan.direct.exchange","rabbitmq.file",message);
}

二、RabbitMQ如何保障消息的可靠性投递


1、什么是生产端的可靠性投递

  • 保障消息成功发送出去
  • 保障mq节点成功接收消息
  • 消息发送端需要收到mq服务的确认应答
  • 完善的消息补偿机制(百分百成功需要该步骤)

2、解决保障可靠性投递的方案(消息落库+定时任务)

1)正常链路流程

  • 在发送消息的时候,把消息插入到消息表中,初始状态为0
  • 把消息投递到消息队列中
  • broker来确认消息发送到队列
  • 根据收到的broker确认消息来更新数据库中的消息记录的状态

2)异常链路流程

  • 在发送消息的时候,把消息插入到消息表中,初始状态为0
  • 把消息投递到消息队列中
  • 由于网络闪断,生产者无法收到broker的消息确认,那么该条状态在数据库中永远是0,这个时候,就需要对这种情况做出补偿

3)补偿机制

启动一个分布式的定时任务,不定时的去扫描消息表状态为0的消息记录,然后根据业务来设置扫描重发规则

  • 规则一:扫描消息表中状态为0的消息,每隔五分钟重试发送一次
  • 规则二:若重试次数超过5次状态还是0,则更改消息状态为2,此时需要人工去确认状态为2的消息是什么原因导致没有发送成功

消息入库缺点: 在高并发的环境下,会有性能瓶颈

3、延时投递,做二次确认检测,回调检测

三、RabbitMQ如何保障消息的幂等性


1、什么是接口的幂等性

对同一接口发起一次调用和多次调用,所产生的结果都是一样的。

若接口没有保障幂等性,那么就有可能会出现问题

2、幂等性保障应用场景

比如订单提交过程中,用户点击了一次提交,但是由于网络等原因,导致后端处理延时,客户就连续点了多次,在没有幂等性的条件下,那么就会造成订单的重复提交。

解决方案: 在保存订单的时候,根据生成的系统全局唯一ID(可以是订单号+业务类型),并且把该唯一ID调用redis的setnx命令保存起来,在第一次保存的时候,由于redis中没有该key,那么就会把全局唯一ID保存到redis中,此时订单就会保存成功,这个时候若出现前端重复点击按钮,由于第一步已经setnx保存上了,所有重复提交不会保存到redis中

3、RabbitMQ是如何解决幂等性的

1)消息重复发送的原因

为了保障消息的百分之百的投递,我们使用了消息重发,确认机制,使得消息可能被重复发送,由于网络原因,无论是上半场(MQ服务端到生产端)ack丢失还是下半场(MQ服务端到消费端)ack丢失,都会导致消息重复发送

2)消息重复发送导致的后果

比如上半场消息生产者是用户支付模块,专门是用来给用户扣费的,而下半场的消息消费者服务是会员卡服务,是通过接受扣费服务发送的消息来进行发卡的,由于ack丢失,那么就会导致上游服务重复发送消息就会导致扣多次款,发多次卡

3)MQ是如何保障幂等性的

消息队列的服务中,对每一条消息都会生成一个全局唯一的与业务无关的ID(inner_msg_id),当mq_server接受到消息的时候,先根据inner_msg_id是否需要重复发送,再决定消息是否落库,这样保证每条消息都只会落库一次

4)消费端如何做到幂等性

把对每条消息做生成一个唯一性的ID,通过redis的来setnx命令来保证幂等性

四、RabbitMQ保障消息的可靠性与幂等性代码示例(以消息落库+定时任务为例)


1、生产端可靠性保障

1)创建消息表

CREATE TABLE `message_content` (
  `msg_id` varchar(50) NOT NULL COMMENT '唯一标识',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `msg_status` int(10) DEFAULT NULL COMMENT '消息状态 0=发送中 1=mq的broker确认接受到消息 3=没有对应交换机 4=没有对应的路由 5=消费端成功消费消息',
  `exchange` varchar(50) DEFAULT NULL COMMENT '交换器',
  `routing_key` varchar(50) DEFAULT NULL COMMENT '路由键',
  `err_cause` varchar(1000) DEFAULT NULL COMMENT '错误信息',
  `order_no` bigint(32) DEFAULT NULL COMMENT '订单ID',
  `max_retry` int(10) DEFAULT NULL COMMENT '最大重试次数',
  `current_retry` int(10) DEFAULT NULL COMMENT '当前重试次数',
  `product_no` int(10) DEFAULT NULL COMMENT '产品ID',
  PRIMARY KEY (`msg_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='消息表';

2)保存订单

public void saveOrderInfoWithMessage(OrderInfo orderInfo) throws JsonProcessingException {
    //todo 构建消息对象
    MessageContent messageContent = new MessageContent();
    String msgId = UUID.randomUUID().toString();
    messageContent.setMsgId(msgId);
    messageContent.setCreateTime(new Date());
    messageContent.setUpdateTime(new Date());
    messageContent.setExchange(MqConst.ORDER_TO_PRODUCT_EXCHANGE_NAME);
    messageContent.setRoutingKey(MqConst.ORDER_TO_PRODUCT_QUEUE_NAME);
    messageContent.setMsgStatus(MsgStatusEnum.SENDING.getCode());
    messageContent.setOrderNo(orderNo);
    messageContent.setProductNo(productNo);
    messageContent.setMaxRetry(MqConst.MSG_RETRY_COUNT);

    //todo 保存数据库(业务失败,直接抛出异常,不会发送消息到mq)
    saveOrderInfo(orderInfo,messageContent);

    //todo 构建消息发送对象
    MsgTxtBo msgTxtBo = new MsgTxtBo();
    msgTxtBo.setMsgId(messageContent.getMsgId());
    msgTxtBo.setOrderNo(orderInfo.getOrderNo());
    msgTxtBo.setProductNo(orderInfo.getProductNo());

    //todo 发送消息(消息发送失败,调用ConfirmListener)
    msgSender.senderMsg(msgTxtBo);
}

//如果业务出现异常,消息不会入库,也不会发送到mq中
@Transactional
@Override
public void saveOrderInfo(OrderInfo orderInfo, MessageContent messageContent) {
    try {
        orderInfoMapper.saveOrderInfo(orderInfo);
        //插入消息表
        msgContentMapper.saveMsgContent(messageContent);
    }catch (Exception e) {
        LOGGER.error("操作数据库失败:{}",e);
        throw new RuntimeException("操作数据库失败");
    }
}

public void senderMsg(MsgTxtBo msgTxtBo){
    LOGGER.info("发送的消息ID:{}",msgTxtBo.getMsgId());
    //todo 唯一可靠性保障
    CorrelationData correlationData = new CorrelationData(msgTxtBo.getMsgId());
    rabbitTemplate.convertAndSend(MqConst.ORDER_TO_PRODUCT_EXCHANGE_NAME,MqConst.ORDER_TO_PRODUCT_ROUTING_KEY,msgTxtBo,correlationData);
}

3)消息确认机制

@Component
public class CyanMsgComfirm implements RabbitTemplate.ConfirmCallback{

    private static final Logger LOGGER = LoggerFactory.getLogger(TulingMsgComfirm.class);

    @Autowired
    private MsgContentMapper msgContentMapper;

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String msgId = correlationData.getId();
        if(ack) {
            LOGGER.info("消息Id:{}对应的消息被broker签收成功",msgId);
            updateMsgStatusWithAck(msgId);
        }else{
            //todo 消息发送失败,更改消息状态
            LOGGER.warn("消息Id:{}对应的消息被broker签收失败:{}",msgId,cause);
            updateMsgStatusWithNack(msgId,cause);
        }
    }
}

4)消息不可达监听

@Component
public class TulingMsgRetrunListener implements RabbitTemplate.ReturnCallback {

    private static final Logger LOGGER = LoggerFactory.getLogger(TulingMsgRetrunListener.class);

    @Autowired
    private MsgContentMapper msgContentMapper;

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        try{
            ObjectMapper objectMapper = new ObjectMapper();
            MsgTxtBo msgTxtBo = objectMapper.readValue(message.getBody(),MsgTxtBo.class);
            LOGGER.info("无法路由消息内容:{},cause:{}",msgTxtBo,replyText);

            //构建消息对象
            MessageContent messageContent = new MessageContent();
            messageContent.setErrCause(replyText);
            messageContent.setUpdateTime(new Date());
            messageContent.setMsgStatus(MsgStatusEnum.SENDING_FAIL.getCode());
            messageContent.setMsgId(msgTxtBo.getMsgId());
            //更新消息表
            msgContentMapper.updateMsgStatus(messageContent);
        }catch (Exception e) {
            LOGGER.error("更新消息表异常:{}",e);
        }
    }
}

5)定时任务

public void retrySend() {
    System.out.println("-----------------------------");
    //todo 定时查询消息状态还没有完结的消息
    List<MessageContent> messageContentList = msgContentMapper.qryNeedRetryMsg(MsgStatusEnum.CONSUMER_SUCCESS.getCode(), MqConst.TIME_DIFF);

    for(MessageContent messageContent:messageContentList) {

        if(messageContent.getMaxRetry()>messageContent.getCurrentRetry()) {
            MsgTxtBo msgTxtBo = new MsgTxtBo();
            msgTxtBo.setMsgId(messageContent.getMsgId());
            msgTxtBo.setProductNo(messageContent.getProductNo());
            msgTxtBo.setOrderNo(messageContent.getOrderNo());
            //更新消息重试次数
            msgContentMapper.updateMsgRetryCount(msgTxtBo.getMsgId());
            msgSender.senderMsg(msgTxtBo);
        }else {
            LOGGER.warn("消息:{}以及达到最大重试次数",messageContent);
        }

    }
}

2、消费端可靠性保障

@RabbitListener(queues = {ORDER_TO_PRODUCT_QUEUE_NAME})
@RabbitHandler
public void consumerMsgWithLock(Message message, Channel channel) throws IOException {

    ObjectMapper objectMapper = new ObjectMapper();
    MsgTxtBo msgTxtBo = objectMapper.readValue(message.getBody(), MsgTxtBo.class);
    Long deliveryTag = message.getMessageProperties().getDeliveryTag();
    //todo 利用redis分布式锁解决消息幂等性问题
    if (redisTemplate.opsForValue().setIfAbsent(LOCK_KEY + msgTxtBo.getMsgId(), msgTxtBo.getMsgId())) {
        LOGGER.info("消费消息:{}", msgTxtBo);
        try {
            //todo 更新消息表也业务表(出异常catch更新消息状态)
            productService.updateProductStore(msgTxtBo);
            //todo 消息签收(这里出现异常,业务事务已提交,会存在幂等性问题)
            System.out.println(1/0);
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            /**
             * 更新数据库异常说明业务没有操作成功需要删除分布式锁
             */
            if (e instanceof BizExp) {
                BizExp bizExp = (BizExp) e;
                LOGGER.info("数据业务异常:{},即将删除分布式锁", bizExp.getErrMsg());
                //todo 删除分布式锁
                redisTemplate.delete(LOCK_KEY);
            }

            //todo 更新消息表状态
            MessageContent messageContent = new MessageContent();
            messageContent.setMsgStatus(MsgStatusEnum.CONSUMER_FAIL.getCode());
            messageContent.setUpdateTime(new Date());
            messageContent.setErrCause(e.getMessage());
            messageContent.setMsgId(msgTxtBo.getMsgId());
            msgContentMapper.updateMsgStatus(messageContent);
            channel.basicReject(deliveryTag,false);
        }
    } else {
        LOGGER.warn("请不要重复消费消息{}", msgTxtBo);
        channel.basicReject(deliveryTag,false);
    }
}