日期:2026-07-11
标签:#面试 #八股 #后端 #并发 #队列 #场景题
一句话答案
这应设计为 MPSC 有界队列,而不是让两百万线程竞争一把锁;生产者通过原子序号领取槽位,单消费者顺序读取,并用分段、批量和背压控制竞争。
面试口语版
我会先质疑“两百万生产者”是否指两百万线程,如果是,线程模型本身不可行,应由少量 I/O EventLoop 接收连接。队列采用多生产者单消费者环形缓冲区:生产者用 CAS 获取递增 sequence,等待对应槽位可用后发布消息;消费者只有一个,可以无锁顺序推进 read sequence。为避免一个全局 CAS 成热点,可以在入口分片成多个 MPSC 队列,消费者轮询或批量归并。队列必须有界,满时阻塞、快速失败或落盘,不能无限堆积。
核心结构
flowchart LR
P1[生产者组1] --> Q1[MPSC分片1]
P2[生产者组2] --> Q2[MPSC分片2]
PN[生产者组N] --> QN[MPSC分片N]
Q1 --> C[单消费者批量处理]
Q2 --> C
QN --> C
关键细节
- 槽位发布需保证消息内容写入先于可见标记,使用 release/acquire 语义。
- 对 head/tail 和槽位序号做缓存行填充,降低伪共享。
- 单消费者吞吐是系统上限,必要时按 Key 分区增加消费者。
- CAS 自旋要有上限,队列满时执行明确背压。
面试官追问
- 为什么普通 ConcurrentLinkedQueue 不一定够?
- 环形队列如何防止覆盖未消费数据?
- 单消费者处理不过来怎么办?
面试官追问参考答案
1. 为什么普通 ConcurrentLinkedQueue 不一定够?
它通用且无界,会产生节点分配、GC 和指针跳转,两百万生产源下全局尾节点 CAS 也可能竞争。专用有界 MPSC 环形队列内存连续、少分配且有背压,但实现复杂,应优先使用成熟库。
2. 环形队列如何防止覆盖未消费数据?
每个槽位维护 sequence/generation。生产者只有确认槽位序号表示已被消费者释放后才能写入,写完再发布下一代序号;消费者读取已发布代后将槽位标为可复用,避免仅凭取模覆盖。
3. 单消费者处理不过来怎么办?
先批量处理、减少单条 I/O 和优化消费者;若仍不足,按业务 Key 分区为多个队列与消费者,保证分区内有序。无法扩消费且输入持续超载时必须限流、丢弃低优先级或持久化积压。
学习清单
- 理解 MPSC、环形缓冲和内存可见性。
- 能说明分片与背压方案。