MQ 选型最难的不是比吞吐,而是先判断你要的是事件日志、任务队列,还是业务消息

📅 2026/7/2 11:25:37
MQ 选型最难的不是比吞吐,而是先判断你要的是事件日志、任务队列,还是业务消息
MQ 选型最难的不是比吞吐而是先判断你要的是事件日志、任务队列还是业务消息一个订单系统准备引入 MQ。架构评审刚开始讨论很容易进入一张参数表Kafka 吞吐高RabbitMQ 路由灵活RocketMQ 支持顺序、延迟和事务消息。有人说 Kafka 生态成熟有人说 RocketMQ 更适合业务系统也有人觉得 RabbitMQ 简单好用。这些判断都没错但如果只停在这里选型很容易跑偏。因为系统上线以后真正让人头疼的问题往往不是“TPS 不够”而是这些更具体的问题订单创建消息能不能重复消费支付成功消息要不要严格顺序库存扣减失败以后怎么重试消息已经发出去了本地事务却失败了怎么办消费者上线晚了历史消息还要不要重新跑消息堆积以后是扩消费者、扩分区、拆队列还是拆 Topic这条消息到底是一条业务事实还是一项待处理任务到这一步才会发现MQ 不是一个简单的“异步工具”。它会决定系统如何记录事实、如何分发任务、如何推进状态、如何处理失败。所以这篇文章不想问“Kafka、RabbitMQ、RocketMQ 谁更好”而是换一个问题当你发出一条消息时你到底希望系统记住什么、投递什么、重试什么、回放什么这才是 MQ 选型的起点。1. 三者不是同一种 MQ只是都叫消息中间件很多人把 MQ 当成“发消息和收消息”的工具但不同 MQ 对“消息”的理解完全不同。如果只看 API三者都可以发消息、收消息、异步解耦、削峰填谷。但从架构模型看它们解决的是三类不同问题中间件更像什么核心模型最擅长回答的问题Kafka事件日志Topic、Partition、Offset、Consumer Group发生过什么谁消费到哪里了能不能重放RabbitMQ任务队列Exchange、Queue、Binding、Ack这条任务该投给谁是否处理完成RocketMQ业务消息平台Topic、MessageQueue、Producer、Consumer Group业务消息如何可靠投递、重试、顺序、延迟和事务化这张表比吞吐表更重要。因为业务系统里所谓“消息”其实可能是三种东西。第一种是事件。比如用户注册了、订单创建了、支付成功了、额度变化了。这类消息更像一条事实记录。事实一旦发生后面可能会有很多系统关心它风控要看报表要看推荐要看数据仓库也要看。它的关键不是“交给谁处理完”而是“发生过什么以及后面能不能重新读取”。第二种是任务。比如发送短信、发送邮件、生成报表、调用三方接口、推送通知。这类消息更像一项待处理工作。它的关键不是长期保留历史而是“交给哪个 worker处理成功没有失败了要不要重回队列或者进入死信”。第三种是业务状态推进。比如订单超时关闭、支付成功后入账、库存扣减后通知履约、还款成功后冲账。这类消息不只是异步通知它参与了交易链路的状态变化。它的关键是顺序、延迟、事务、重试、死信和可恢复。所以一个更准确的判断是Kafka 解决“事件流怎么存和消费”RabbitMQ 解决“任务怎么路由和确认”RocketMQ 解决“业务消息怎么可靠推进”。如果一开始没有把这三类消息分清楚后面所有参数对比都会变得很虚。2. Kafka核心不是队列而是可回放的事件日志如果你把 Kafka 只当成一个高吞吐队列就会忽略它最重要的能力保留事件历史让不同消费者按自己的节奏读取。Kafka 的核心不是“消息被消费者拿走”。它更像是把消息追加到一条日志里。Producer 把消息写入 Topic。Topic 会被拆成多个 Partition。每个 Partition 内部是一条有序追加的日志。Consumer 读取消息时并不是把消息从 Broker 里删除而是根据 Offset 记录自己读到了哪里。这几个概念决定了 Kafka 的性格。Producer - Topic - Partition 0: msg0, msg1, msg2, msg3 ... - Partition 1: msg0, msg1, msg2, msg3 ... - Consumer Group A: 记录自己的 offset - Consumer Group B: 记录自己的 offsetKafka 为什么适合事件流和回放原因不在一句“吞吐高”而在它的几个底层机制。第一消息消费和消息保留是分离的。在很多队列模型里消息被消费确认以后就倾向于从队列里移除。Kafka 不是这个思路。Kafka 的消息会按照保留策略留在日志里消费者只是记录自己的 Offset。这意味着一个消费者读完了不代表其他消费者不能读。也意味着一个新系统晚几天上线只要消息还在保留期内它可以从过去的位置重新消费。第二消费进度属于 Consumer Group。同一个 Topic 可以被多个 Consumer Group 消费。风控系统有自己的消费进度报表系统有自己的消费进度数据同步系统也有自己的消费进度。它们互不影响。这对事件流非常关键。因为一个事件发生以后经常不是一个下游关心而是一批下游按不同节奏关心。第三Partition 同时承担顺序边界和并发边界。同一个 Partition 内部有序不同 Partition 可以并行。如果你按orderId、userId这类 key 分区就可以让同一个业务对象的事件落到同一个 Partition从而获得局部顺序。与此同时多个 Partition 又能让整个 Topic 横向扩展。这就是 Kafka 适合日志采集、CDC、用户行为流、订单事件流、实时计算的原因。这些场景的核心问题不是这条任务交给谁处理而是这个事实发生过后面很多系统可能都要读取它有的现在读有的以后读有的还要重放。举个例子订单状态流可以这样进入 KafkaOrderCreated OrderPaid OrderShipped OrderFinished OrderRefunded风控系统只关心其中一部分事件。数据仓库可能全量消费。搜索系统可能消费订单创建和取消。如果某个消费者代码有 bug修复以后还可以把 Offset 调回去重新消费。这就是事件日志模型的价值。但也正因为 Kafka 的核心是日志它并不是天然围绕“单条任务处理完成”设计的。比如你想做下面这些事情复杂路由把不同消息按规则投到不同队列单条任务失败以后马上进入某个死信队列每条消息有明确的 ack / nack 处理语义延迟 30 分钟以后再触发某个业务动作本地事务成功以后再确认消息可见。Kafka 不是不能做而是很多能力需要应用层、框架层或周边组件一起补。所以对 Kafka 更准确的理解是当消息代表“系统发生过的事实”并且这个事实以后还可能被多个系统重复读取Kafka 往往更合适。3. RabbitMQ核心不是日志而是灵活路由和任务确认RabbitMQ 更关心的不是“历史事件能不能回放”而是“这条消息应该被路由到哪个队列并且有没有被处理完成”。RabbitMQ 的模型和 Kafka 很不一样。Producer 通常不是直接把消息写到某个 Queue而是先把消息发到 Exchange。Exchange 根据类型、Routing Key 和 Binding 规则把消息路由到一个或多个 Queue。Consumer 从 Queue 中取消息处理完成以后通过 Ack 确认。可以把它理解成Producer - Exchange - Binding rule A - Queue A - Consumer A - Binding rule B - Queue B - Consumer B - Binding rule C - Queue C - Consumer CRabbitMQ 为什么适合任务分发和复杂路由因为它的核心模型就是“先路由再排队再确认”。第一Exchange 负责路由。RabbitMQ 的 exchange 可以有 direct、fanout、topic、headers 等类型。direct 适合精确匹配fanout 适合广播topic 适合基于模式的路由。这让 RabbitMQ 很适合表达“不同消息交给不同队列”。比如通知系统里notify.sms - 短信队列 notify.email - 邮件队列 notify.push - App 推送队列 notify.audit.* - 审计队列这类路由在 RabbitMQ 里是它的主模型不是额外补出来的能力。第二Queue 负责排队。消费者处理不过来任务可以在队列里等。如果短信服务暂时慢了短信队列积压如果邮件服务正常邮件队列继续消费。不同任务可以被隔离在不同队列里。第三Ack / Nack 负责确认。Consumer 处理成功以后 AckBroker 才知道这条消息可以从待处理集合中移除。Consumer 处理失败可以 Nack、Reject、重回队列或者通过死信交换机进入失败处理路径。这件事对任务队列很重要。因为任务系统最关心的是这个活到底干完了没有第四Prefetch 可以控制消费者压力。如果一个消费者处理很慢不应该一下子塞给它太多未确认消息。RabbitMQ 可以通过 prefetch 限制单个消费者同时持有的未确认消息数量。这对后台任务、通知发送、三方接口调用都很实用。所以 RabbitMQ 很适合这些场景邮件、短信、App 推送后台任务分发请求削峰多规则路由投递异步 RPC 或轻量服务解耦消费失败后需要明确 ack、nack、死信路径。但 RabbitMQ 不应该被简单理解成“轻量版 Kafka”。它的主模型不是长期事件日志。消息一旦被确认消费队列模型就倾向于把它从待处理集合中移除。虽然现代 RabbitMQ 也有 quorum queue、stream 等能力可以覆盖高可用队列和部分流式场景但 RabbitMQ 最自然的思路仍然是任务路由和处理确认。如果你要做的是大规模用户行为日志、CDC 数据流、多个消费者独立回放历史事件RabbitMQ 就会显得不够顺手。它不是不能传这些消息而是模型不贴。对 RabbitMQ 更准确的判断是当消息更像一项“待处理任务”并且系统重点是路由、确认、失败转移和任务分发RabbitMQ 往往更合适。4. RocketMQ核心是面向业务系统的消息语义RocketMQ 的价值不只是“能发消息”而是把很多交易系统常见的消息语义做成了一等能力。如果说 Kafka 更像事件日志RabbitMQ 更像任务队列那么 RocketMQ 更像一套面向业务系统的消息平台。它也有 Topic、MessageQueue、Producer、Consumer Group 这些基本概念但它更容易被业务系统记住的往往是这些能力普通消息FIFO / 顺序消息延迟消息事务消息消费重试死信消息。这些能力为什么重要因为订单、支付、库存、信贷这类系统里消息经常不是简单通知而是在推进业务状态。顺序消息同一个业务对象的状态不能乱订单状态不能乱序。你不能先消费OrderPaid再消费OrderCreated。也不能库存先释放再扣减。对同一个业务对象来说状态推进有前后关系。RocketMQ 的 FIFO 消息可以通过消息组等机制把同一组消息按顺序投递和消费。这里的关键不是全局有序而是业务对象级别的局部有序。在真实系统里我们通常也不需要全局有序。全局有序代价太高也没有必要。更常见的需求是orderId 1001 的消息按顺序 orderId 1002 的消息按顺序 orderId 1001 和 1002 之间可以并行这正好是交易系统最常见的顺序诉求。延迟消息不是现在处理而是到点再处理很多业务消息不是立刻消费而是到某个时间点再触发。比如订单创建 30 分钟未支付自动关单支付发起后 10 分钟查询最终结果还款日前一天发送提醒优惠券领取后到期前提醒风控审核超时后触发补偿。如果没有延迟消息业务系统通常会自己维护一张定时任务表再跑定时扫描。这当然能做但系统会多出一套补偿调度逻辑。RocketMQ 把延迟消息作为消息类型支持业务系统可以更自然地表达这条业务消息现在产生但应该未来某个时间点再被消费。事务消息本地事务和消息发送不能各说各话交易系统里最常见的问题之一是数据库事务成功了但消息没发出去怎么办消息发出去了但本地事务回滚了怎么办比如订单创建成功以后要发送OrderCreated消息。如果先写库再发消息写库成功但发消息失败下游就不知道订单创建了。如果先发消息再写库消息已经出去了但订单事务失败下游就会看到一条不存在的订单。RocketMQ 的事务消息用半消息、提交、回滚、事务回查这套机制来缓解这个问题。它不是让分布式事务从此没有成本而是给业务系统一个更标准的模式发送半消息 - 执行本地事务 - 本地事务成功提交消息 - 本地事务失败回滚消息 - 状态未知Broker 回查事务状态这对订单、支付、库存这类系统很有价值。因为这些场景最怕的不是失败而是本地状态和消息状态不一致。重试和死信失败以后还要能恢复业务消费失败很常见。下游系统超时、数据库短暂不可用、三方接口失败、业务状态还没准备好都可能导致消费失败。RocketMQ 的消费重试和死信机制本质上是在回答这条消息没处理成功系统接下来怎么办它可以根据消费结果触发重试。多次重试仍然失败后进入死信队列等待后续人工或专门任务处理。这对交易系统很关键。因为交易系统里的失败通常不是“丢掉算了”而是要留下恢复入口。所以 RocketMQ 适合订单、支付、库存、信贷这类业务系统不是因为它只能做这些而是因为这些系统最常见的难点正好是顺序延迟事务重试死信状态推进。反过来如果你的场景只是海量日志采集、用户行为流分析、CDC 到数据湖RocketMQ 也能传消息但它的优势不会像在交易型业务消息里那么突出。对 RocketMQ 更准确的判断是当消息直接参与业务状态推进并且顺序、延迟、事务、重试是主问题RocketMQ 往往更贴近业务系统。5. 不同业务场景真正要问的问题不一样MQ 选型不能从产品名开始而要从消息在业务链路中的职责开始。同样叫“订单消息”它可能代表完全不同的东西。如果这条消息是订单事实流后面有风控、报表、搜索、数据仓库多个系统消费它更像事件。如果这条消息是“请发送订单通知短信”它更像任务。如果这条消息是“订单 30 分钟未支付要关闭”它就是业务状态推进。所以不要问订单消息用 Kafka、RabbitMQ 还是 RocketMQ要问这条订单消息在系统里到底承担什么职责可以先用这张表判断。场景消息本质更推荐原因用户行为日志采集事件流Kafka高吞吐、可保留、可被多个下游消费订单状态变更广播业务事件Kafka / RocketMQ偏数据流用 Kafka偏交易推进用 RocketMQ邮件短信发送异步任务RabbitMQ / RocketMQRabbitMQ 路由和 ack 清晰RocketMQ 适合业务重试延迟关单延迟业务消息RocketMQ延迟消息语义更贴近业务支付成功后多系统通知业务消息RocketMQ / Kafka交易链路推进用 RocketMQ事件分发和回放用 Kafka数据库变更同步数据变更日志KafkaCDC 和流式处理生态更成熟复杂路由投递路由任务RabbitMQexchange 和 binding 模型直接匹配秒杀削峰异步缓冲RocketMQ / Kafka看是交易消息还是事件流削峰风控实时特征流实时事件流Kafka多下游、可回放、流式计算友好本地事务后发送消息事务业务消息RocketMQ事务消息模型更贴近业务一致性这里故意没有给所有场景唯一答案。因为很多场景本来就有分叉。比如“支付成功后通知多系统”。如果它表达的是交易链路推进通知履约、积分、账务继续处理那么 RocketMQ 更贴近。如果它表达的是一条支付事实流风控、报表、数据分析、审计系统都要订阅那么 Kafka 更贴近。再比如“秒杀削峰”。如果只是把请求流量削成可消费的事件流Kafka 可以很好地承接。如果后面要处理订单创建、库存扣减、超时取消、失败重试RocketMQ 的业务消息能力就更有价值。所以同样叫“订单消息”如果它是事件流可能适合 Kafka如果它是待处理任务可能适合 RabbitMQ如果它要推进交易状态可能适合 RocketMQ。6. 选错 MQ 后问题通常不是马上爆炸而是慢慢变成架构债MQ 选错最麻烦的地方不是第一天不能用而是后面每加一个场景都要绕过原来的模型。很多错误选型短期看都能跑。但它会把复杂度慢慢推到业务代码里。用 RabbitMQ 当长期事件日志RabbitMQ 可以传事件但如果你把它当长期事件日志就会越来越别扭。因为它的主模型是队列投递和消费确认。你会遇到这些问题历史消息回放不自然多个消费者独立进度管理不自然新下游晚加入以后补历史数据不自然大规模数据管道场景下队列和绑定会变得复杂。最后你可能会发现自己在 RabbitMQ 外面又补了一套事件存储。用 Kafka 做复杂任务路由Kafka 可以做异步任务但如果任务路由很复杂也会不顺手。比如不同类型任务要进不同队列不同失败原因要走不同死信路径不同消费者处理能力差异很大还要精细 ack / nack。这些在 Kafka 里不是完全不能做但经常要靠 Topic 拆分、消费者逻辑、重试 Topic、死信 Topic、调度任务来拼。模型会变得比较重。用 Kafka / RabbitMQ 硬拼交易消息语义交易系统最常见的问题是顺序怎么保证延迟怎么触发本地事务和消息发送怎么对齐消费失败怎么重试重试多次仍失败怎么进入人工处理如果所选 MQ 没有把这些能力做成相对标准的模型业务系统就会自己补。补着补着就会出现很多表消息发送表消息补偿表延迟任务表重试记录表死信处理表人工修复表。这些表不是不能有但如果每个业务系统都自己设计一套后面维护成本会很高。为了统一技术栈让所有场景共用一个 MQ还有一种常见错误是公司已经有 Kafka所以所有消息都进 Kafka或者公司已经有 RocketMQ所以所有异步都用 RocketMQ。这看起来降低了运维复杂度但可能增加业务复杂度。日志流、任务队列、交易消息本来就是三种模型。如果强行用一个 MQ 承接所有场景系统很容易变成事件流里混着任务任务队列里混着交易状态交易消息里混着日志采集每个消费者都在用自己的方式解释消息语义。这时问题不是 MQ 不够强而是模型被混在了一起。所以不要把“技术统一”当成唯一目标。技术统一不是目标语义匹配才是目标。一个系统里同时存在两类 MQ并不一定是坏设计。7. 架构选型应该看七个问题真正成熟的 MQ 选型不是问“用哪个”而是连续问七个业务语义问题。可以用下面这张清单做第一轮判断。问题如果答案是“是”倾向消息是否需要长期保留并可回放是Kafka是否有多个下游按不同节奏消费同一类事件是Kafka是否需要复杂路由把不同消息投递到不同队列是RabbitMQ是否强调单条任务处理完成确认是RabbitMQ是否需要顺序消息推进业务状态是RocketMQ是否需要延迟消息、事务消息、消费重试、死信治理是RocketMQ消息是业务事实、异步任务还是交易状态推进先分类再选型还可以再压缩成一个决策树这条消息以后要不要回放 是 - Kafka 否 - 是否需要复杂路由和任务确认 是 - RabbitMQ 否 - 是否需要顺序、延迟、事务、业务重试 是 - RocketMQ 否 - 三者都能做按团队经验和运维成本选当然真实选型不会只看这一张表。还要看团队经验、运维能力、现有基础设施、云厂商支持、语言栈、监控体系、容量模型、故障恢复能力。但这些是第二层问题。第一层问题永远是这条消息到底是什么如果第一层错了后面参数选得再漂亮系统也会别扭。8. 结尾选 MQ本质上是在选择系统如何表达事实MQ 不是把同步调用改成异步调用这么简单它决定了系统如何记录事实、分发任务和推进状态。Kafka、RabbitMQ、RocketMQ 的差异可以收成一句话Kafka 关注“发生过什么”RabbitMQ 关注“交给谁处理”RocketMQ 关注“业务状态如何可靠推进”。所以选型时不要先问哪个 MQ 性能最好先问我这条消息是事件日志、任务队列还是业务消息如果它是事件日志你要关心 Topic、Partition、Offset、Consumer Group、保留策略、回放和多下游消费。如果它是任务队列你要关心 Exchange、Queue、Binding、Ack、Nack、Prefetch、死信和失败转移。如果它是业务消息你要关心顺序、延迟、事务、重试、死信和状态推进。很多系统的 MQ 问题不是出在“消息没发出去”而是出在“系统从来没定义清楚这条消息代表什么”。只要这个问题没想清楚后面就会出现各种症状消费者不知道能不能重放重试以后不知道会不会重复入账死信以后不知道谁来处理延迟任务不知道有没有漏扫顺序消息不知道按什么维度保证本地事务和消息状态各说各话。所以 MQ 选型的真正起点不是中间件产品而是业务事实建模。最后可以用这张表收束你真正需要的是什么更贴近的模型代表中间件记录事实允许多个系统读取和回放事件日志Kafka分发任务确认处理完成和失败转移任务队列RabbitMQ推进业务状态处理顺序、延迟、事务和重试业务消息RocketMQ如果把这件事想清楚Kafka、RabbitMQ、RocketMQ 的选型就不会只是参数比较而会变成一次系统模型设计。