Skip to content

让你设计一个消息队列,怎么设计? ​

  • 标签:后端、系统设计、场景题

🏛 业务架构师 ​

这个题我的第一反应是反问:为什么自研? 消息中间件是极度成熟的基础设施,RocketMQ、Kafka、Pulsar 经过万亿级消息验证,自研的消息队列 99% 的场景是投入产出比为负——人力成本千万级,稳定性风险却由核心链路承担。只有消息规模极大(成本敏感)或有特殊场景(低延迟证券行情)才值得自研。

但如果面试官就要考察设计能力,我会按业务价值分层讲:

它解决什么业务问题

  • 削峰填谷:秒杀 10 万 QPS 写入,下游按 2000 QPS 平稳消费,保护数据库。
  • 系统解耦:订单创建后,积分、物流、通知各自订阅,主流程不因下游迭代而变更。
  • 最终一致:跨系统数据同步的载体。

核心设计要点

  • 存储:顺序写日志文件 + OS 页缓存,吞吐的根基;数据分段 + 索引支持按 offset 消费。
  • 投递语义:至少一次投递 + 消费端幂等,明确不承诺精确一次,把复杂度交给业务侧用幂等表解决。
  • 顺序性:按业务 Key(订单号)哈希到同一队列,保证局部有序即可,全局有序吞吐代价太高。
  • 高可用:多副本同步刷盘 + 主从切换。
  • 堆积能力:消息落盘而非驻留内存,消费不动时不丢数据。

运营能力最容易被低估:控制台的消息查询/重放/死信处理、按业务方的配额隔离,这些决定了故障时的恢复速度。中间件的竞争力一半在生态和可运维性,不在算法。

🏗 系统架构师 ​

设计 MQ 我会从四个维度入手:存储模型、架构角色、可靠性语义、性能手段。

存储模型(性能的地基)

  • 顺序写 CommitLog + PageCache + 零拷贝(sendfile),单机可到几十万 TPS,这是 Kafka/RocketMQ 共同验证过的路径。
  • RocketMQ 模式:所有消息顺序写 CommitLog,再建 ConsumeQueue 逻辑队列做消费索引,兼顾顺序写和队列语义。

架构角色

  • NameServer/元数据层无状态、可水平扩展(甚至最终一致即可);Broker 分主从,高可靠场景用 Raft 多副本(RocketMQ DLedger)避免主从异步切换丢数据。
  • 客户端拉模式消费,天然背压,消费者按能力拉取,不会被打挂。

可靠性语义(要明确给用户的承诺)

  • 生产端:同步刷盘/异步刷盘、同步复制/异步复制,按消息级别选择,做好发送重试。
  • 消费端:至少一次投递 + 消费幂等(业务键去重),这是分布式下的标配组合。
  • 事务消息:半消息 + 本地事务 + 回查,解决"本地落库和发消息的原子性"。
  • 失败兜底:重试队列(延迟梯度递增)+ 死信队列 + 控制台人工干预。

功能设计 延迟消息(时间轮实现任意精度)、消息轨迹、堆积告警、按 Tag 过滤。最后我会强调:MQ 的本质是用最终一致换取吞吐和解耦,所有设计取舍(同步刷盘性能减半换可靠性)都要能配置化,让业务按场景选。

💻 全栈开发专家 ​

我会按「消息不丢、消息不乱、能扛堆积」三个目标来设计,直接参考 RocketMQ 的成熟架构。

整体架构 Producer → Broker(CommitLog 顺序写)→ ConsumeQueue 索引 → Consumer(推/拉模式)。

存储层(核心)

  • 所有消息追加写单一 CommitLog,磁盘顺序写能到几百 MB/s,这是吞吐的根基。
  • 每个队列建逻辑索引 ConsumeQueue(offset + size + tag hash),消费时先查索引再回 CommitLog 读消息,读靠 page cache 命中。
  • 刷盘策略可配:同步刷盘(flushDiskType=SYNC_FLUSH,可靠但慢)与异步刷盘(默认,靠 OS 刷盘 + 主从复制兜底)。

高可用 主从复制,同步复制(SYNC_MASTER)保证主挂了消息不丢;Controller/raft 选主(RocketMQ 5 的 DLedger)实现自动切换。

投递语义与消费设计

  • At least once + 消费端幂等(业务唯一键去重)是标配,不做 exactly once,成本太高。
  • ACK 机制:消费成功才推进 offset;失败进重试队列,重试 16 次仍失败进死信队列人工处理。
  • 顺序消息:按业务键(如 orderId)hash 到同一队列,单线程消费。

堆积能力 消息本身不删、只推进消费位点,天然支持 TB 级堆积和回溯消费。

生产实践中的坑

  • 消费者 falsely? 消费位点保存与消息处理不在一个事务,宕机会重复消费——所以幂等必须做在业务侧。
  • 「消息不丢」要三个环节都确认:生产用同步发送 + 重试,Broker 同步刷盘/同步复制,消费手动 ACK。
  • Topic/队列数规划好,扩队列会破坏顺序。

一句话:顺序写 + 索引分离 + 主从复制 + at least once + 幂等消费,这就是业界验证过的答案。