TAOCARTS 知识

消息队列选型:从RabbitMQ到RocketMQ,1688代采系统的实战对比

2026-06-26 系统功能介绍

本文适合后端开发、系统架构师,以及所有被消息队列坑过的同行。如果你只是想了解消息队列的概念,可以跳过代码部分。

需求:一个消息队列解决不了的场景

三年前,我们做了一套1688代采系统。核心流程很简单:用户下单 → 系统自动去1688采购 → 更新订单状态 → 通知用户。一开始用RabbitMQ做异步解耦,几百单的时候跑得挺好。直到有一天,1688 API突然大量报错,订单同步停了两个小时——调用太频繁被限流了。

更糟的是,RabbitMQ的消息积压导致内存溢出,3000多条订单状态没更新。团队花了一整天手动核对,客户群里的催单消息已经刷屏了。

这个教训让我意识到:**消息队列不是装上就能解决问题,选型和架构设计才是关键。**

对比:RabbitMQ vs RocketMQ的核心差异

我们当时在两个方案之间纠结:继续用RabbitMQ做优化,还是换RocketMQ。先看一个简单的性能对比:

| 指标 | RabbitMQ | RocketMQ |

||||

| 单机吞吐量 | 万级/秒 | 十万级/秒 |

| 消息堆积能力 | 内存堆积,容易OOM | 磁盘堆积,几乎无上限 |

| 消息可靠性 | 需手动确认 | 内置事务消息 |

| 延迟 | 微秒级 | 毫秒级 |

| 死信队列 | 需要手动配置 | 内置重试+死信机制 |

光看数字,RocketMQ好像全面碾压。但trade-off在于:**RabbitMQ的延迟更低,适合对实时性要求极高的场景**,比如即时通讯、实时通知。而RocketMQ的延迟虽然高一点,但它的消息堆积能力是RabbitMQ没法比的——这对代采系统至关重要。

Trade-off:为什么选了RocketMQ

我们最终选了RocketMQ,不是因为它的吞吐量高,而是因为三个关键点:

1. 消息堆积不丢数据

代采系统的核心痛点是:**1688 API限流时,消息不能丢**。RabbitMQ的消息默认存在内存里,一旦积压超过内存上限,直接OOM。RocketMQ的消息存在磁盘上,积压几万条也不会崩。

来看一个真实场景的代码对比:

```java

// RabbitMQ消费者(容易出问题)

@RabbitListener(queues = "order.queue")

public void handleOrder(OrderMessage message) {

try {

// 调用1688 API

orderService.purchase(message);

} catch (Exception e) {

// 限流时直接抛异常,消息被丢弃

log.error("处理订单失败", e);

// 没有重试机制

}

}

```

```java

// RocketMQ消费者(带重试)

@RocketMQMessageListener(topic = "order-topic", consumerGroup = "order-consumer")

public class OrderConsumer implements RocketMQListener {

@Override

public void onMessage(OrderMessage message) {

try {

orderService.purchase(message);

} catch (ApiLimitException e) {

// 限流异常:延迟重试

throw new RuntimeException("API限流,稍后重试", e);

} catch (Exception e) {

// 其他异常:进入死信队列

log.error("订单处理失败,进入死信", e);

throw e;

}

}

}

```

RocketMQ的重试机制默认16次,每次间隔递增。**重试机制可将临时失败的消息成功率从约85% 提升至99% 以上**。这个数据来自我们线上环境的统计。

2. 消息队列集群的横向扩展

当订单量从每天几百单涨到几千单时,单机RabbitMQ扛不住了。RocketMQ的集群架构天然支持横向扩展:

```yaml

# RocketMQ集群配置(简化版)

brokerClusterName: DefaultCluster

brokerName: broker-a

namesrvAddr: 192.168.1.1:9876;192.168.1.2:9876

# 主从模式,保证高可用

brokerRole: ASYNC_MASTER

flushDiskType: ASYNC_FLUSH

```

我们部署了3个Broker节点,每个节点2个从节点。**系统吞吐量从每秒处理几百单提升到每秒处理数千单**,而且节点故障时消息不丢。

3. 事务消息解决对账问题

代采系统的另一个痛点是:**支付成功但订单没创建,或者订单创建了但支付没到账**。RocketMQ的事务消息可以保证这两步的最终一致性:

```java

// 事务消息生产者

public class OrderTransactionProducer {

@Autowired

private RocketMQTemplate rocketMQTemplate;

public void createOrder(OrderDTO order) {

// 1. 发送半消息

Message message = MessageBuilder.withPayload(order).build();

TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(

"order-topic", message, order

);

// 2. 事务回查:如果半消息发送成功但本地事务失败,RocketMQ会回查

}

@RocketMQTransactionListener

public class OrderTransactionListener implements RocketMQLocalTransactionListener {

@Override

public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {

OrderDTO order = (OrderDTO) arg;

try {

// 执行本地事务:创建订单

orderService.saveOrder(order);

return RocketMQLocalTransactionState.COMMIT;

} catch (Exception e) {

return RocketMQLocalTransactionState.ROLLBACK;

}

}

@Override

public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {

// 回查:检查订单是否存在

OrderDTO order = (OrderDTO) msg.getPayload();

if (orderService.exists(order.getId())) {

return RocketMQLocalTransactionState.COMMIT;

}

return RocketMQLocalTransactionState.UNKNOWN;

}

}

}

```

这个机制解决了我们对账的噩梦。以前用RabbitMQ时,支付回调到了但订单没创建,数据对不上,得人工修复。现在事务消息保证了**支付和订单要么一起成功,要么一起失败**。

决策:不是RocketMQ更好,而是它更合适

说回trade-off。RocketMQ也有缺点:

  • **部署运维复杂**:需要部署NameServer、Broker、Console,比RabbitMQ重得多
  • **延迟比RabbitMQ高**:毫秒级vs微秒级,实时通知场景不适用
  • **社区生态不如RabbitMQ**:文档和插件相对少
  • 但对我们来说,**消息不丢、能堆积、事务一致性** 是刚需。RocketMQ的缺点可以通过运维工具弥补。比如Taocarts的部署方案里,我们用Docker Compose一键部署RocketMQ集群,降低了运维成本。

    **选型的核心不是选最好的,而是选最合适的。** 如果你的场景是实时通知、延迟敏感,RabbitMQ依然是最佳选择。但如果你需要高吞吐、消息堆积、事务支持,RocketMQ值得一试。

    最佳实践提炼

    从这次踩坑中,我总结了三点:

    1. **消息队列不是银弹**:先搞清楚你的场景是实时性优先还是可靠性优先,再选型

    2. **压力测试必须做**:模拟1688 API限流、消息积压等极端场景,验证系统能否扛住

    3. **监控和告警不能省**:RocketMQ的Console提供消息堆积、消费延迟等指标,必须配置告警

    最后,如果你正在做代采或类似的系统,建议先想清楚你的核心痛点是什么。**不要为了用消息队列而用消息队列,而是为了解决具体问题而用。**