解决RabbitMQ消息可靠性和消息重复消费问题

511 阅读2分钟

此文参考江南一点雨大神的项目

有的时候会因为网络问题导致消息并没有发出去,此文将会提供一种解决方案来保证消息的可靠发送,另外可能消息会存在多次发送的情况,此文也会解决消息重复问题。

RabbitMQ消息可靠性解决方案

  1. 创建数据表记录消息发送状态
CREATE TABLE `t_message_log`  (
  `msg_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '消息唯一ID',
  `status` int(11) NULL DEFAULT 0 COMMENT '状态:0发送中,1发送成功,2发送失败',
  `count` int(11) NULL DEFAULT 0 COMMENT '重试次数',
  `try_time` datetime NULL DEFAULT NULL COMMENT '第一次重试时间',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`msg_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '记录消息是否发送成功' ROW_FORMAT = Compact;
  1. 配置RabbitMQ消息发送成功或失败回调方法
@Bean
public RabbitTemplate createRabbitTemplate() {
    RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
    rabbitTemplate.setConfirmCallback((data, ack, cause) -> {
        // 消息唯一ID
        if (ack) {
            log.info("消息发送成功");
            messageLogService.updateStatus(data.getId(), 1);
        } else {
            log.info("消息发送失败");
        }
    });
    rabbitTemplate.setReturnCallback((msg, repCode, repText, exchange, routingkey) -> {
        log.info("消息发送失败");
    });
    return rabbitTemplate;
}
  1. 消息发送时标记消息唯一ID
// 生成消息唯一ID
String msgId = UUID.randomUUID().toString();
MessageLog messageLog = new MessageLog();
messageLog.setMsgId(msgId);
messageLog.setTryTime(LocalDateTime.now().plusMinutes(1));
messageLog.setCreateTime(LocalDateTime.now());
messageLogService.save(messageLog);
// 发送mq消息
rabbitTemplate.convertAndSend("system_notice_exchange", null, message, new CorrelationData(msgId));
  1. 定时任务巡查消息是否发送成功
@Scheduled(cron = "0/10 * * * * ?")
public void messageSendTask() {
    List<MessageLog> logs = messageLogService.getByStatus();
    if (logs.size() == 0) {
        return;
    }
    logs.forEach(item -> {
        if (item.getCount() >= 3) {
            // 消息发送失败
            messageLogService.updateStatus(item.getMsgId(), 2);
        } else {
            // 更新重试数量
            messageLogService.updateCount(item.getMsgId());
            // 发送消息   内容不作重点演示
            rabbitTemplate.convertAndSend("system_notice_exchange", null, "message", new CorrelationData(item.getMsgId()));
        }
    });
}
  1. 开启rabbitmq回调监控配置
spring:
  rabbitmq:
    host: localhost
    # 开启回调监控
    publisher-confirms: true
    publisher-returns: true

RabbitMQ消息重复消费解决方案

  1. 开启消息手动确认机制
spring:
  rabbitmq:
    host: localhost
    listener:
      simple:
        # 手动确认消息
        acknowledge-mode: manual
        # 消息预加载数量
        prefetch: 100
  1. 消息消费者幂等性判断
// 判断此消息是否已经消费过
String msgId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
long tag = message.getMessageProperties().getDeliveryTag();
if (redisTemplate.opsForHash().entries("message_log").containsKey(msgId)) {
    log.info(msgId + ":消息已经被消费过");
    channel.basicAck(tag, false);
    return;
}
  1. 消息消费成功时
// 标记消息被处理过
redisTemplate.opsForHash().put("message_log", msgId, "value");
channel.basicAck(tag, false);
  1. 消息消费失败时(有异常时)
// 消息处理失败 true阻止消息重新回到队列中两次消费
channel.basicNack(tag, false, true);