此文参考江南一点雨大神的项目
有的时候会因为网络问题导致消息并没有发出去,此文将会提供一种解决方案来保证消息的可靠发送,另外可能消息会存在多次发送的情况,此文也会解决消息重复问题。
RabbitMQ消息可靠性解决方案
- 创建数据表记录消息发送状态
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;
- 配置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;
}
- 消息发送时标记消息唯一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));
- 定时任务巡查消息是否发送成功
@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()));
}
});
}
- 开启rabbitmq回调监控配置
spring:
rabbitmq:
host: localhost
# 开启回调监控
publisher-confirms: true
publisher-returns: true
RabbitMQ消息重复消费解决方案
- 开启消息手动确认机制
spring:
rabbitmq:
host: localhost
listener:
simple:
# 手动确认消息
acknowledge-mode: manual
# 消息预加载数量
prefetch: 100
- 消息消费者幂等性判断
// 判断此消息是否已经消费过
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;
}
- 消息消费成功时
// 标记消息被处理过
redisTemplate.opsForHash().put("message_log", msgId, "value");
channel.basicAck(tag, false);
- 消息消费失败时(有异常时)
// 消息处理失败 true阻止消息重新回到队列中两次消费
channel.basicNack(tag, false, true);