消息顺序、消息重复问题
消息的顺序问题

关键问题暴露点
- 消息顺序乱序风险
- M1(先发)→ S1 → 消费者1
- M2(后发)→ S2 → 消费者2
- 若消费者2处理速度 > 消费者1 ,则 M2 结果先于 M1 生效 → 业务状态错乱
- 典型场景:订单创建(M1)和支付(M2),若先执行支付将报错
- 消息重复消费可能
- 图中虚线通知环节 隐含风险:
- 若
通知:M1收到
因网络抖动丢失 - S1 触发 M1 重发 → 消费者1 重复处理
- 若
- 图中虚线通知环节 隐含风险:
完整解决方案
消息顺序问题:分区保序方案
M1 订单ID:001 M2 订单ID:001 Hash订单ID %3 Hash订单ID %3 生产者 路由层 队列1 消费者1-单线程消费
技术落地:
-
RabbitMQ :使用
一致性Hash交换器
+单队列单消费者
-
Kafka/RocketMQ :指定
Message Key=订单ID
,保证同Key进同一分区 -
代码示例(Spring Boot + RabbitMQ):
java// 发送时绑定订单ID到路由Key rabbitTemplate.convertAndSend("order_exchange", "order_001", message); // 消费者配置:队列绑定单个消费者 @RabbitListener(queues = "queue_order_001", concurrency = "1") public void handleOrder(Message message) { ... }
消息重复问题:三层幂等拦截

技术落地:
-
Redis原子锁(防并发重复):
javaBoolean isDuplicate = redisTemplate.opsForValue() .setIfAbsent("msg:"+messageId, "1", 10, TimeUnit.MINUTES); if (Boolean.FALSE.equals(isDuplicate)) return;
-
数据库去重表:
sqlCREATE TABLE msg_uniq ( id VARCHAR(64) PRIMARY KEY COMMENT '消息ID', biz_id VARCHAR(32) COMMENT '业务ID', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );
-
业务层乐观锁:
sqlUPDATE orders SET status='paid' WHERE order_id='001' AND status='unpaid' // 仅状态匹配才更新
消息零丢失方案(增强架构图)
- 事务消息 2. 镜像队列 2. 镜像队列 2. 镜像队列 3. 手动ACK 生产者 RabbitMQ集群 S1 Master S2 Slave S3 Slave 消费者
关键配置:
-
生产者端:
java// RabbitMQ 发送确认 rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (!ack) log.error("消息未抵达Broker: {}", cause); });
-
MQ端:
shell# RabbitMQ镜像队列策略 rabbitmqctl set_policy ha-all "^order" '{"ha-mode":"all"}'
-
消费者端:
java// 关闭自动ACK @RabbitListener(queues = "queue", ackMode = "MANUAL") public void handle(Message message, Channel channel) { try { process(message); // 业务处理 channel.basicAck(deliveryTag, false); // 手动确认 } catch (Exception e) { channel.basicNack(deliveryTag, false, true); // 重试 } }
生产级架构建议
消费者集群 MQ集群 生产者集群 1. 带Key的消息 Hash Key路由 Hash Key路由 镜像复制 镜像复制 单线程消费 单线程消费 ACK确认 ACK确认 防重:Redis+DB 防重:Redis+DB 消费者1 消费者2 MQ 队列1 队列2 队列1副本 队列2副本 路由层 生产者 分布式锁
各组件职责说明
组件 | 关键职责 |
---|---|
路由层 | 根据业务Key(如订单ID)哈希选择队列,保证同业务消息顺序性 |
镜像队列 | 主从实时同步,任一节点宕机不影响消息可用性(RabbitMQ需部署磁盘节点) |
单线程消费 | 每个队列仅允许一个消费者,避免并发导致顺序混乱(注意:消费者内部用线程池并行会破坏顺序!) |
分布式锁 | Redis锁控制消费幂等性入口,去重表兜底 |