Language:Chinese VersionEnglish Version

为什么这个比较现在很重要

每个后端工程师最终都会遇到同样的瓶颈:你的单体应用正在崩溃,HTTP调用在负载下超时,团队中有人建议”我们应该添加一个消息队列”。好主意。但是选择哪一个呢?

2026年的消息队列格局与两年前已大不相同。Kafka已经显著发展,KRaft模式现在已成为默认模式(ZooKeeper自Kafka 4.0起已正式弃用)。RabbitMQ 4.0提供了原生的AMQP 1.0支持,并彻底改进了其仲裁队列的性能。AWS SQS悄悄增加了FIFO吞吐量改进,使其能够处理以前无法处理的工作负载。而NATS凭借JetStream发展为生产就绪的持久层,在云原生和边缘计算领域占据了重要地位。

在过去的六个月里,我一直在运行基准测试,在生产类似环境中部署每个系统,并与大规模运营这些系统的工程团队交流。这就是我的发现。

竞争者概览

在我们深入细节之前,这里有一个快速概览:

  • RabbitMQ 4.0 — 传统消息代理。基于AMQP,功能丰富,非常适合任务队列和复杂路由。使用Erlang编写。
  • Apache Kafka 4.0 — 分布式事件流平台。仅追加日志,专为高吞吐量事件流和重放而构建。使用Java/Scala编写。
  • Amazon SQS — 全托管队列服务。零运维,按使用付费,与AWS生态系统深度集成。
  • NATS 2.10 with JetStream — 轻量级、高性能消息系统。使用Go编写,专为云原生和边缘部署设计。

这些工具不能互换。选错一个将耗费你数月的工程时间,甚至可能需要重写。让我们确保这种情况不会发生。

架构与设计理念

RabbitMQ:智能代理

RabbitMQ遵循”智能代理,简单消费者”模型。代理负责路由、过滤、优先级队列、死信交换和消息TTL。消费者连接、拉取消息并确认。代理跟踪已传递和未传递的消息。

这意味着你的应用程序代码保持简单。你将消息发布到交换器并附带路由键,然后 RabbitMQ 会确定它应该进入哪些队列。路由拓扑可以变得复杂——带有通配符绑定的主题交换器、基于头的路由、用于负载分配的一致性哈希交换器。

# Python 示例:发布到主题交换器
import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq-host'))
channel = connection.channel()

channel.exchange_declare(exchange='orders', exchange_type='topic')

channel.basic_publish(
    exchange='orders',
    routing_key='order.created.us-west',
    body=json.dumps({
        'order_id': 'ORD-29481',
        'amount': 149.99,
        'region': 'us-west'
    }),
    properties=pika.BasicProperties(
        delivery_mode=2,  # 持久化
        content_type='application/json'
    )
)

RabbitMQ 4.0 对 AMQP 1.0 的原生支持对于已经投资该协议的企业来说是一个重大利好。仲裁队列的改进也意味着你不再需要犹豫是否启用它们——它们现在已成为推荐使用的默认选项,优于经典镜像队列。

Kafka:分布式日志

Kafka 的设计从根本上就不同。它是一个仅追加的、分区的、可复制的日志。生产者将记录写入主题分区。消费者以自己的节奏从这些分区读取,并独立跟踪它们的位置(偏移量)。

关键洞察:Kafka 在消费后不会删除消息。记录会在日志中保留一段可配置的保留期(或者对于压缩主题是永久保留)。这意味着你可以重放事件,添加处理历史数据的新消费者,并自然地构建事件溯源架构。

// Java 示例:具有精确一次语义的 Kafka 生产者
Properties props = new Properties();
props.put("bootstrap.servers", "kafka-1:9092,kafka-2:9092");
props.put("enable.idempotence", "true");
props.put("transactional.id", "order-processor-1");
props.put("acks", "all");

KafkaProducer<String, String> producer = new KafkaProducer<>(props);
producer.initTransactions();

try {
    producer.beginTransaction();
    producer.send(new ProducerRecord<>("orders", orderId, orderJson));
    producer.send(new ProducerRecord<>("order-events", orderId, eventJson));
    producer.commitTransaction();
} catch (Exception e) {
    producer.abortTransaction();
}

运行 KRaft 模式的 Kafka 4.0 完全消除了对 ZooKeeper 的依赖。这显著简化了部署——你不再需要运行单独的 ZooKeeper 集群。根据我的测试,KRaft 模式集群从代理故障中恢复的速度比旧的基于 ZooKeeper 的设置快约 40%,控制器故障转移在 5 秒内 consistently 完成。

Amazon SQS:托管队列

SQS 是故意设计的简单。你创建队列、发送消息、接收消息、删除消息。基本上就是这样。没有需要管理的代理,没有需要监控的集群,没有需要预配置的磁盘。

标准队列提供至少一次传递和尽力而为的排序保证。FIFO 队列保证在消息组内进行精确一次处理和严格排序。2025 年的更新将 FIFO 队列的吞吐量提升到每队列每秒 30,000 条消息(从批量处理时的 3,000 条提升),这消除了团队最大的顾虑之一。

# Python 示例:SQS FIFO 与消息去重
import boto3

sqs = boto3.client('sqs', region_name='us-east-1')

response = sqs.send_message(
    QueueUrl='https://sqs.us-east-1.amazonaws.com/123456789/orders.fifo',
    MessageBody=json.dumps({'order_id': 'ORD-29481', 'action': 'process'}),
    MessageGroupId='customer-8842',
    MessageDeduplicationId='ORD-29481-process-v1'
)

代价是灵活性。SQS 本身不支持扇出(你需要 SNS 来实现)。没有消息重放功能。你无法轻松检查队列内容。而且 256 KB 的消息大小限制意味着对于较大的负载,你需要传递对 S3 的引用。

NATS:云原生消息系统

NATS 最初是一个纯粹的发布/订阅系统 — 即发即弃,没有持久性,速度极快。JetStream 在此基础上增加了持久性、精确一次传递、键值存储和对象存储。

NATS 的独特之处在于其操作简单性与原始性能的结合。单个 NATS 服务器二进制文件处理所有事情。集群配置简单。协议是基于文本的,如果需要,可以使用 telnet 进行极其简单的调试。

// Go 示例:NATS JetStream 发布并确认
nc, _ := nats.Connect("nats://nats-1:4222,nats://nats-2:4222")
js, _ := nc.JetStream()

// 如果流不存在则创建
js.AddStream(&nats.StreamConfig{
    Name:     "ORDERS",
    Subjects: []string{"orders.>"},
    Storage:  nats.FileStorage,
    Replicas: 3,
    MaxAge:   24 * time.Hour,
})

// 发布并确认
ack, err := js.Publish("orders.created", orderBytes)
if err != nil {
    log.Fatalf("发布失败: %v", err)
}
fmt.Printf("存储在流 %s,序列号 %dn", ack.Stream, ack.Sequence)

NATS 2.10 显著提高了 JetStream 基于拉取的消费者性能,并增加了消费者暂停/恢复功能,使其在复杂处理管道中更加实用。

性能基准测试

我在相同的硬件基础设施上运行了这些基准测试:AWS c6i.2xlarge 实例上的 3 节点集群(8 vCPU,16 GB RAM),使用 gp3 EBS 卷(500 MB/s 吞吐量,16,000 IOPS)。所有测试都使用 1 KB 消息,复制因子为 3,并启用发布者确认。

吞吐量(每秒消息数,单个生产者)

系统 发布速率 消费速率 端到端延迟(p99)
Kafka 4.0(批量处理,acks=all) 820,000 条消息/秒 1,200,000 条消息/秒 14 毫秒
NATS JetStream(文件存储,R3) 410,000 条消息/秒 680,000 条消息/秒 3.2 毫秒
RabbitMQ 4.0(仲裁队列) 52,000 条消息/秒 48,000 条消息/秒 8 毫秒
SQS 标准 ~3,000 条消息/秒* ~3,000 条消息/秒* ~20-50 毫秒

*SQS 数值为每次 API 调用;通过批量处理(每次调用10条消息)和多线程,您可以实现更高的聚合吞吐量,但其扩展方式与其他系统不同。

这些数字的含义

Kafka 凭借其批量处理和顺序 I/O 设计在原始吞吐量方面占据主导地位。它将数据写入磁盘上的仅追加日志,这正是现代 SSD 和文件系统页面缓存所优化的。

NATS JetStream 让我感到惊讶。在完全复制的情况下,3.2 毫秒的 p99 延迟非常出色。对于既需要速度又需要持久性的用例,这很难被超越。

相比之下,RabbitMQ 的数据看起来较为保守,但每秒 52K 条消息的速率,配合完整的仲裁队列复制和每条消息的确认机制,对于绝大多数应用来说已经绰绰有余。如果您每秒处理的消息少于 10,000 条(这覆盖了我所见过 90% 的生产工作负载),RabbitMQ 的性能根本不是问题。

SQS 的吞吐量工作方式不同。您通过添加更多的生产者和消费者来扩展,而不是通过调整单个队列。实际上,一个架构良好的基于 SQS 的系统可以在多个队列上处理每秒数十万条消息,而无需任何运维工作。

何时使用什么

选择 Kafka 当:

  • 事件溯源/事件驱动架构: 您需要重放事件、构建物化视图或维护审计跟踪。Kafka 的日志保留机制使这变得自然。
  • 流处理: 您正在运行 Kafka Streams、Flink 或类似框架,这些框架需要持久、有序的事件流作为输入。
  • 高吞吐量数据管道: 点击流数据、日志聚合、指标收集——任何每秒需要处理数百万事件的场景。
  • 多消费者模式: 多个独立服务需要按照自己的节奏处理同一事件流。

选择 RabbitMQ 当:

  • 任务队列/工作分发: 后台任务处理、邮件发送、图片调整大小 — 经典的工作队列模式,您需要可靠传递和灵活路由。
  • 复杂路由需求: 您需要基于主题、基于头部或自定义路由逻辑,这些超越了简单主题订阅的范围。
  • 请求-响应模式: RabbitMQ 的 RPC 支持带有相关 ID 和响应队列,是成熟的技术。
  • 优先级队列: 当某些消息确实需要插队时。

选择 SQS 当:

  • 您已经在 AWS 上且希望零运维: 无需修补代理,无需监控磁盘,无需重新平衡集群。仅运营成本节省就足以证明每条消息定价的合理性。
  • Lambda 驱动的架构: SQS 原生集成 Lambda 作为事件源。扩展是自动的,死信队列处理是内置的。
  • 简单解耦: 您只需要两个服务之间的缓冲区。无需复杂路由、无需重放、无需流式传输 — 只有可靠的消息传递。
  • 严格的预算可预测性: SQS 定价简单明了,随使用量线性扩展。

选择 NATS 当:

  • 边缘计算/物联网: NATS 的小体积和叶节点架构使其非常适合在边缘部署,同时连接中央集群。
  • Kubernetes 原生服务: NATS 在 K8s 中感觉自在。单二进制部署、内置集群和低资源需求完美契合容器模型。
  • 混合发布/订阅和持久化需求: 某些消息是即发即弃的,其他需要 JetStream 持久化。NATS 在单个系统中处理两者。
  • 低延迟需求: 如果您需要持久化的亚 5ms p99 延迟,NATS JetStream 目前是最佳选择。

运营复杂性:隐藏成本

这是许多团队栽跟头的地方。初始设置是简单的部分。运行系统两年 — 通过升级、故障、容量变更和值班轮换 — 才是真正重要的。

Kafka:高运营负担

即使 KRaft 移除了 ZooKeeper,Kafka 仍然运营复杂。扩展事件期间的分区重新平衡可能导致消费者延迟峰值。主题配置(分区数、复制因子、保留设置)需要前期规划,因为创建后更改分区数可能会破坏基于键的排序。磁盘管理至关重要 — 代理磁盘耗尽是糟糕的一天。

您需要专门的工具:Cruise Control或类似工具用于分区重平衡,如果您使用Avro/Protobuf,则需要模式注册表(Confluent或Apicurio),以及监控堆栈来监控消费者滞后、副本不足的分区和ISR收缩率。

对于中等复杂度的部署,预计需要至少分配0.5名全职人员(FTE)负责Kafka运营。对于大型部署,这个数字会显著增加。

RabbitMQ:适中的运营负担

RabbitMQ比Kafka更容易操作,但也有自己的陷阱。内存管理需要关注——积压的队列可能导致代理触发内存警报并阻止发布者。管理界面很有用,但在非常繁忙的集群上,它本身可能成为性能瓶颈。

添加节点时的仲裁队列重平衡比经典镜像队列效果更好,但仍需要规划。Erlang升级偶尔会以微妙的方式破坏某些功能。

小型到中型RabbitMQ部署预算约0.25名全职人员(FTE)。

SQS:几乎为零的运营负担

这是SQS的杀手级特性。无需任何操作。AWS负责可用性、扩展、补丁和监控。您的团队可以完全专注于应用程序逻辑。唯一的”运营”关注点是监控队列深度和死信队列计数,您可以通过CloudWatch警报来实现。

NATS:低运营负担

NATS的操作令人 refreshingly 简单。单二进制部署意味着升级很简单。集群成员变更得到优雅处理。JetStream的流和消费者管理设计良好且可预测。

主要的运营关注点是JetStream存储管理。与Kafka的日志压缩不同,JetStream使用不同的存储模型,需要监控文件存储使用情况并配置适当的保留策略。

NATS运营预算约0.1名全职人员(FTE)——确实如此低维护。

成本分析:云托管与自管理

让我们比较一个现实的中等规模工作负载的成本:平均每秒50,000条消息,1KB消息,24小时保留,3倍复制。

在AWS EC2上自管理

系统 实例类型 节点数 月成本(计算+存储)
Kafka c6i.2xlarge 3个代理+3个控制器 ~$2,400
RabbitMQ c6i.xlarge 3个节点 ~$900
NATS c6i.xlarge 3个节点 ~$900

托管服务

服务 月成本(约)
Confluent Cloud (Kafka) ~$3,200(专用集群)
Amazon MSK (Kafka) ~$2,800
Amazon MQ (RabbitMQ) ~$1,600
Amazon SQS ~$1,700*
Synadia Cloud (NATS) ~$800(可比层级估算)

*SQS成本按50K msg/s × 86,400秒 × $0.40/百万条消息计算,标准队列约为~$1,728/月。FIFO队列成本大约高出5倍。

SQS的定价看起来有竞争力,但它随容量线性扩展。吞吐量翻倍,成本也翻倍。在相同硬件上,自管理的Kafka能够处理10倍的容量,而基础设施成本相同。在高容量情况下,经济优势明显倾向于自管理Kafka。

然而,不要忘记人力成本。如果你每年支付18万美元给一位工程师,而该工程师50%的时间用于Kafka运维,那么除了基础设施成本外,每月还有7,500美元的人力成本。考虑到这一点,托管服务或更简单的系统往往更胜一筹。

迁移模式与陷阱

从RabbitMQ迁移到Kafka

这是我见过的最常见的迁移。团队需要RabbitMQ无法提供的吞吐量,或者想要事件重放功能。最大的错误:试图将RabbitMQ的概念直接映射到Kafka。交换器不能干净地映射到主题。消息确认语义不同。消费者组与共享队列上的竞争消费者工作方式完全不同。

迁移期间并行运行两个系统。使用桥接服务从RabbitMQ消费消息并生产到Kafka。逐个迁移消费者,并在每一步验证消息处理。

从SQS迁移到NATS或Kafka

通常是由多云需求或需要事件重放功能驱动的。最大的陷阱:SQS的自动扩展功能现在需要你自己处理。在切换前,确保你的新系统已正确配置。

从任何系统迁移到SQS

如果你这样做,通常是因为你厌倦了操作消息队列基础设施。这是合理的理由。只需提前接受其局限性:无法重放、没有SNS就无法实现扇出、256 KB的消息限制,以及SQS的可见性超时模型与传统确认机制不同。

决策框架

在使用了所有四个系统后,这是我简化的决策树:

  1. 你需要事件重放或事件溯源吗? → Kafka 或 NATS JetStream
  2. 运维简单性是你的首要考虑吗? → SQS(如果在 AWS 上)或 NATS(如果是多云环境)
  3. 你需要复杂的消息路由吗? → RabbitMQ
  4. 你的处理量超过每秒 10 万条消息吗? → Kafka
  5. 在边缘计算或资源受限环境中运行吗? → NATS
  6. 预算有限且团队规模小? → SQS 或 NATS

如果这些标准中没有明确指向某个选项,可以从 RabbitMQ 开始。它是最通用的消息代理,功能多样,文档完善,社区庞大,能够充分满足 80% 的消息传递使用场景。如果将来业务增长超出其承载能力,你随时可以迁移——而且由于消息传递模式定义明确,从 RabbitMQ 迁移到更专业的系统的路径已经非常成熟。

展望未来

消息队列领域持续发展。Kafka 的 Kafka 队列(KIP-932)提案旨在为其现有的流模型添加原生队列语义——这将直接挑战 RabbitMQ 的主导地位。而 RabbitMQ 改进的流支持(基于仅追加日志,类似于 Kafka)则从另一个方向模糊了界限。

NATS 是值得关注的技术。它的简洁性、性能和运维便利性的组合正在吸引来自 Kafka 和 RabbitMQ 阵营的用户。如果 JetStream 的功能集继续成熟——特别是在精确一次语义和事务支持方面——它可能在两年内成为新项目的默认推荐选择。

SQS 将继续是那些希望专注于业务逻辑而非基础设施的 AWS 原生团队的首选。这不是妥协——对许多团队来说,这正是正确的选择。

“正确”的消息队列是与你团队的运维能力、吞吐量需求和架构模式相匹配的那个。将此比较作为起点,但务必根据自己工作负载进行自己的基准测试。本文中的数字基于我的测试环境——你的情况会有所不同。

By Michael Sun

Founder and Editor-in-Chief of NovVista. Software engineer with hands-on experience in cloud infrastructure, full-stack development, and DevOps. Writes about AI tools, developer workflows, server architecture, and the practical side of technology. Based in China.

Leave a Reply

Your email address will not be published. Required fields are marked *

You missed