RabbitMQ保证消息的一致性
一、采用confirm消息确认机制及return返回机制 确保消息发送成功
二、将队列以及消息设置持久化 保证rabbitmq突然宕机消息仍然存在
三、手动确认接收消息方式 消息处理失败拒收重回队列
1. yml配置
| spring: |
| rabbitmq: |
| host: 10.134.22.232 |
| port: 5672 |
| username: guest |
| password: guest |
| ##消息发送确认回调 |
| publisher-confirms: true |
| #采用confirm以及return机制 发送返回监听回调 |
| publisher-confirm-type: correlated |
| publisher-returns: true |
| listener: |
| type: simple |
| simple: |
| #手动接收消息方式 |
| acknowledge-mode: manual |
2. RabbitMQ配置类
| @Configuration |
| @Slf4j |
| @AllArgsConstructor |
| public class RabbitmqConfig { |
| private final ConnectionFactory connectionFactory; |
| private final RabbitLogsMapper rabbitLogsMapper; |
| |
| @Bean |
| public RabbitTemplate rabbitTemplate(){ |
| RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); |
| |
| rabbitTemplate.setConfirmCallback((correlationData,ack,cause) -> { |
| String msgId = correlationData.getId(); |
| if (ack) { |
| |
| log.info("消息成功发送 , msgId: {}," ,msgId); |
| |
| BiddingRabbitLogs biddingRabbitLogs = new BiddingRabbitLogs(); |
| biddingRabbitLogs.setStatus(SendStatus.SEND_SUCCESS.getValue()); |
| rabbitLogsMapper.update(biddingRabbitLogs, Wrappers.lambdaUpdate(BiddingRabbitLogs.class).eq(BiddingRabbitLogs::getId,msgId).notIn(BiddingRabbitLogs::getStatus,"4")); |
| } else { |
| |
| log.error("消息发送失败, {}, cause: {}, msgId: {}", correlationData, cause, msgId); |
| |
| BiddingRabbitLogs biddingRabbitLogs = new BiddingRabbitLogs(); |
| biddingRabbitLogs.setStatus(SendStatus.SEND_FAILD.getValue()); |
| rabbitLogsMapper.update(biddingRabbitLogs, Wrappers.lambdaUpdate(BiddingRabbitLogs.class).eq(BiddingRabbitLogs::getId,msgId).notIn(BiddingRabbitLogs::getStatus,"4")); |
| } |
| }); |
| rabbitTemplate.setMandatory(true); |
| rabbitTemplate.setReturnCallback((message,replyCode,replyText,exchange,routingKey) -> { |
| |
| log.error("消息从Exchange路由到Queue失败: exchange: {}, route: {}, replyCode: {}, replyText: {}, message: {}", exchange, routingKey, replyCode, replyText, message); |
| |
| String msgId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation"); |
| BiddingRabbitLogs biddingRabbitLogs = new BiddingRabbitLogs(); |
| biddingRabbitLogs.setStatus(SendStatus.SEND_FAILD.getValue()); |
| rabbitLogsMapper.update(biddingRabbitLogs, Wrappers.lambdaUpdate(BiddingRabbitLogs.class).eq(BiddingRabbitLogs::getId,msgId).notIn(BiddingRabbitLogs::getStatus,"4")); |
| }); |
| return rabbitTemplate; |
| } |
| @Bean |
| public RabbitAdmin rabbitAdmin(RabbitTemplate rabbitTemplate){ |
| RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitTemplate); |
| rabbitAdmin.setAutoStartup(true); |
| return rabbitAdmin; |
| } |
| } |
说明:
- confirm机制只是确保了消息是否成功发送到交换机
- Return机制确保了消息是否从交换机发送到指定的队列
- - ConfirmCallback则根据状态判断发送成功还是失败 进行更新日志表记录状态
- ReturnCallback则根据收到消息就是未找到队列发送失败,未收到消息就是发送成功 进行更新日志表记录状态
3. 声明的队列一定要将队列持久化
| public String createQueue(String queueName) { |
| BiddingQueueConfig biddingQueueConfig = queueMapper.selectOne(Wrappers.lambdaQuery(BiddingQueueConfig.class).eq(BiddingQueueConfig::getQueue, queueName)); |
| if (biddingQueueConfig == null) { |
| biddingQueueConfig = new BiddingQueueConfig(); |
| biddingQueueConfig.setCreatetime(new Date()); |
| biddingQueueConfig.setQueue(queueName); |
| biddingQueueConfig.setStatus("1"); |
| int insert = queueMapper.insert(biddingQueueConfig); |
| |
| rabbitAdmin.declareQueue(new Queue(queueName,true)); |
| return queueName + "队列创建成功"; |
| } |
| return queueName + "队列创建失败"; |
| } |
4. 发送消息 将发送的消息设置为持久化
发送消息前首先将发送的数据插入数据库,状态变为发送中
5. 消费者监听队列
- 如果根据消息id查询日志表为空的话那么是没有发送消息,消息自动接收,发送成功消息后日志表会有数据
- 判断是否重复消费 根据状态是否成功消费以及失败重试次数判断
- 处理业务逻辑,如果成功消息接收 状态更新
- 如果处理业务逻辑失败报错则会拒收,消息重回队列重新处理此条消息,当处理次数超过3次处理失败则消息改为接收
| |
| @RabbitListener(queuesToDeclare = { @Queue("queue_work_dontask") }) |
| @RabbitHandler |
| @SneakyThrows |
| public void receiveDonTask(String data, Message message, Channel channel){ |
| |
| String msgId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation"); |
| |
| BiddingRabbitLogs biddingRabbitLogs = remoteLogsService.get(msgId, SecurityConstants.FROM_IN).getData(); |
| if (biddingRabbitLogs == null) { |
| log.error("消息ID查询 biddingRabbitLogs:null"); |
| channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); |
| return; |
| } |
| |
| if (SendStatus.CONSUME_SUCCESS.getValue().equals(biddingRabbitLogs.getStatus()) || SendStatus.SEND_FAILD.getValue() == String.valueOf(biddingRabbitLogs.getTryTimes())) { |
| |
| log.info("消息ID:{},重复消费",msgId); |
| channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); |
| return; |
| } |
| try { |
| |
| Map map = JSON.parseObject(data, Map.class); |
| String dataString = (String) map.get("data"); |
| String username = (String) map.get("username"); |
| Integer tenantId = (Integer) map.get("tenantId"); |
| ApproveParam approveParam = JSON.parseObject(dataString, ApproveParam.class); |
| R<String> stringR = doneTask(approveParam,username,tenantId); |
| |
| channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); |
| biddingRabbitLogs.setStatus(SendStatus.CONSUME_SUCCESS.getValue()); |
| biddingRabbitLogs.setSuccesstime(new Date()); |
| remoteLogsService.updateById(biddingRabbitLogs,SecurityConstants.FROM_IN); |
| log.info("消费成功,消息ID:{}",msgId); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| if (biddingRabbitLogs.getTryTimes() >= Integer.parseInt(SendStatus.TRY_TIMES.getValue())) { |
| |
| channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); |
| log.error("多次消费失败,消息ID:{}",msgId); |
| } else { |
| |
| channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true); |
| log.error("消费失败,消息ID:{}",msgId); |
| } |
| biddingRabbitLogs.setStatus(SendStatus.CONSUME_FAILD.getValue()); |
| biddingRabbitLogs.setTryTimes(biddingRabbitLogs.getTryTimes()+1); |
| remoteLogsService.updateById(biddingRabbitLogs,SecurityConstants.FROM_IN); |
| } |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY