大体上的设计是由一条线程 1 执行从等待列表中获取任务插入任务队列再由线程池中的线程从任务队列中取出任务去执行.

添加一条线程 1 主要是防止在执行耗时的任务时阻塞主线程.当执行耗时任务时,添加的任务的操作快于取出任务的操作,

当任务队列长度达到最大值时, 线程 1 将被阻塞, 等待线程 2,3… 从任务队列取出任务执行。

这个”生产者线程 + 有界队列 + 消费者线程池”的设计是进程内消息队列的最小可行形态(本质是 BlockingQueue 模式)。但”自己实现消息队列”这个问题在面试与设计中通常指向更高一层:从零设计一个跨进程 MQ 需要哪些核心模块。本文两层都讲:进程内实现给出可运行代码,跨进程设计给出模块清单与关键取舍。

第一层:进程内队列(原文设计的完整实现)

class InProcMQ<T> {
private final BlockingQueue<T> queue; // 有界队列=背压来源
private final ExecutorService consumers;
private final Consumer<T> handler;

InProcMQ(int capacity, int workers, Consumer<T> handler) {
this.queue = new ArrayBlockingQueue<>(capacity);
this.consumers = Executors.newFixedThreadPool(workers);
this.handler = handler;
for (int i = 0; i < workers; i++)
consumers.submit(this::consumeLoop);
}

boolean publish(T msg, long timeoutMs) throws InterruptedException {
return queue.offer(msg, timeoutMs); // 满则阻塞/超时=背压
}

private void consumeLoop() {
while (!Thread.currentThread().isInterrupted()) {
try {
T msg = queue.take();
handler.accept(msg); // 业务处理
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); break;
} catch (Exception e) {
retryOrDead(e); // 重试/死信策略
}
}
}
}

原文设计的三个要点在代码中的对应:线程 1(publish 调用方)不阻塞=offer 异步入队;队列满阻塞生产者=背压(有界队列是防 OOM 的关键);线程池消费=并行度。补充原文没有的:消费失败的 retryOrDead(重试次数+死信列表)、优雅关闭(drain 队列再停)。

适用边界:进程内队列不跨进程、不持久化、进程重启消息全丢——适合进程内异步解耦(日志缓冲、事件分发),不适合业务可靠消息。

第二层:跨进程 MQ 的核心模块设计

从零设计一个可靠 MQ 需要六块:

  1. 存储层:消息落盘(顺序写 commitlog,Kafka 式分段文件 / RocketMQ 式统一 commitlog+索引文件);刷盘策略(同步/异步)决定可靠性与吞吐取舍
  2. 索引与消费模型:队列模型(partition/queue,offset 顺序消费)vs 路由模型(exchange/binding,RabbitMQ);消费位点(offset)管理(broker 存 vs 消费者存)
  3. 传输层:长连接 + 自定义协议(请求响应 requestId 匹配);零拷贝(sendfile/mmap)是吞吐关键
  4. 生产可靠性:同步发送+重试 / 事务消息 / 回调确认
  5. 消费可靠性:手动 ack + 重投(未 ack 超时重投)+ 死信队列;幂等留给消费端(at-least-once 语义)
  6. 集群与高可用:副本同步(ISR/主从)、controller 选举、rebalance(消费者-分区分配)

关键取舍三对:

  • 吞吐 vs 可靠:异步刷盘+异步复制快但丢窗口大;同步双写慢但可靠——按业务定 SLA
  • 顺序 vs 并行:全局序=单分区(牺牲并行);分区序=key 哈希(常用折中);无序=最大并行
  • 推 vs 拉:push 实时性好但易压垮消费者;pull 消费者自控速率(长轮询补实时性)——主流选 pull/长轮询

为什么通常不该自研

六模块每个都有深坑(rebalance 风暴、副本丢失、offset 回退、刷盘抖动);Kafka/RocketMQ/RabbitMQ 是十年级生产验证的成品。自研 MQ 的合理场景只有:进程内轻量队列(第一层)、嵌入式特殊需求(如单机任务队列用 SQLite/WAL 实现)、学习目的。业务消息直接用成熟 MQ + 把精力花在使用正确性(幂等/积压/死信)上,投入产出比高一个量级。

面试答法结构

“自己实现消息队列”的标准回答结构:先给进程内最小实现(BlockingQueue+背压+重试死信,证明理解队列本质)→ 再展开跨进程六模块与三对取舍(证明理解分布式 MQ 的设计空间)→ 最后点明”生产选成熟中间件,自研只在学习与嵌入式场景”(工程判断力)。三层递进,比背 Kafka 架构更能体现理解深度。