在电商秒杀、支付、库存同步等高并发业务场景中,消息中间件既要保证高可靠、高可用,又要防止重复消息对业务造成副作用。本文结合真实生产环境,分享RabbitMQ集群搭建、HA策略、Publisher Confirms与幂等消费方案的实战经验。
为了满足上述需求,我们选择RabbitMQ作为核心消息队列,并通过集群、镜像策略与消息幂等处理实现高可用和高稳定性。
为什么选RabbitMQ?
集群设计方案对比
幂等实现方式
综合考虑,我们采用3节点RabbitMQ集群+镜像队列(ha-mode=all)+Publisher Confirms+消费端幂等方案。
在rabbitmq.conf
中启用集群配置:
cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config
cluster_formation.classic_config.nodes.1 = rabbit@node1
cluster_formation.classic_config.nodes.2 = rabbit@node2
cluster_formation.classic_config.nodes.3 = rabbit@node3
# 高可用队列策略:所有镜像
policies.ha-all.pattern = ^ha\.
policies.ha-all.definition.ha-mode = all
policies.ha-all.definition.ha-sync-mode = automatic
policies.ha-all.priority = 0
policies.ha-all.apply-to = queues
在管理界面或CLI中创建策略:
# CLI示例
rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all","ha-sync-mode":"automatic"}'
所有需要高可用的队列名称前缀加ha.
,例如:ha.order.queue
。
通过Publisher Confirms确认消息被Broker接收:
Spring Boot示例:
spring:
rabbitmq:
host: node1.example.com
port: 5672
username: guest
password: guest
publisher-confirm-type: correlated # 开启确认模式
publisher-returns: true # 开启退回
template:
mandatory: true # 必须开启mandatory
@Component
public class RabbitPublisher {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (!ack) {
// 记录失败日志/重试
log.error("Message send failed: {}", cause);
}
});
rabbitTemplate.setReturnsCallback(returned -> {
log.warn("Message returned: {}", returned);
// 保存到DB或重发队列
});
}
public void sendOrder(String messageJson) {
CorrelationData data = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend(
"ha.order.exchange", "order.routing.key", messageJson, data);
}
}
msgId
)。@Component
public class OrderConsumer {
private static final String DEDUPE_SET = "dedupe:order";
@Autowired
private StringRedisTemplate redis;
@RabbitListener(queues = "ha.order.queue")
public void onMessage(String payload, Channel channel, Message message) throws IOException {
String msgId = message.getMessageProperties().getHeader("msgId");
Boolean isNew = redis.opsForSet().add(DEDUPE_SET, msgId) == 1;
if (!isNew) {
// 幂等:重复消息,直接ACK
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return;
}
try {
// 业务处理:调用库存、下单、支付微服务...
processOrder(payload);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception ex) {
// 处理失败,NACK并重回队列或DLX
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
}
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true
initial-interval: 1000
max-attempts: 3
队列配置(CLI):
rabbitmqctl set_policy dlx ".*" '{"dead-letter-exchange":"dlx.exchange"}'
rabbitmqadmin declare queue name=dead_letter_queue
rabbitmqadmin bind queue name=dead_letter_queue exchange=dlx.exchange routing_key="#"
message-service/
├── src/main/java/com/example/mq/
│ ├── RabbitPublisher.java
│ ├── OrderConsumer.java
│ ├── config/
│ │ └── RabbitConfig.java
│ └── util/
│ └── RedisDedupeUtil.java
├── src/main/resources/
│ └── application.yml
└── Dockerfile
镜像队列全同步模式下,节点加入同步耗时较长,导致集群不稳定。
ha-sync-mode: automatic
+ha-sync-batch-size
配置,减少全量同步。Publisher Confirms里未捕获returned
回调导致消息丢失。
publisher-returns
与mandatory
使用,遇路由失败落盘或重试。Consumer端NACK后无限重试造成死锁。
Redis去重Set过大导致内存抖动。
EXPIRE
策略)或采用CuckooFilter减低内存占用。通过以上RabbitMQ高可用与幂等实践,能够在真实电商高并发场景中实现消息的高可靠、可恢复与防重复,帮助开发者快速落地稳定的消息系统。