两百万个生产者发送消息,仅一个消费者,如何高效设计锁?
- 标签:后端、场景题
🏛 业务架构师
200 万生产者、单消费者的场景,我的核心观点是:这题的答案不是"设计一把更快的锁",而是发现锁本身就是错误答案——两百万方并发竞争一把锁,无论怎么优化都是灾难;正确方向是消除竞争。
为什么锁竞争在这里无解
- 单把锁 = 串行化 = 吞吐上限锁死在临界区执行速度,200 万生产者的排队延迟会堆到天荒地老
- 锁竞争本身还有开销:缓存行乒乓、上下文切换,CPU 烧在"排队"而不是"干活"上
正确设计:分区 + 批量 + 无锁队列
- 按业务 Key 分区:消息按 hash(订单ID) 路由到 N 个分区/队列,不同分区无共享——锁竞争从"全局 1 把"摊薄为"N 把小锁",甚至无锁
- 内存队列用无锁结构:单生产者单消费者配对时用 RingBuffer(Disruptor 思路),CAS 操作代替互斥锁,生产者各自写自己的槽位,消费者批量读取
- 消费者批量拉取:一次拿一批(比如 500 条)再处理,把"每条消息一次同步"变成"每批一次",同步成本摊薄百倍
- 背压设计:队列满时让生产者阻塞或降级(丢弃策略按业务定),而不是无限堆积内存
如果业务要求全局严格 FIFO
那单消费者就是业务约束,此时该做的是:生产者写日志/队列(append-only 顺序写极快)、消费者批量消费,并把"单点消费能力"纳入容量规划——它就是系统吞吐的天花板,要么加机器横向分区,要么和业务谈"是否真的需要全局有序"。
收尾:这道题在真实业务里对应订单广播、行情推送等场景,我处理这类问题的第一步永远是质疑约束本身——大部分"全局单消费者"是设计惯性,不是业务需求。
🏗 系统架构师
两百万生产者、一个消费者——锁的竞争主体在生产侧的共享结构上。我的答案是消灭锁,而不是优化锁,按层次递进。
第一层:无锁化分区(首选) 如果这 200 万生产者争抢的是同一个队列/结构,直接按 key 分片:
text
shard = hash(producerId) % N每个 shard 独立队列、独立锁(甚至 CAS 无锁队列),不同 shard 之间零竞争。锁粒度从"全局 1 把"降到"1/N",吞吐随 N 线性扩展——这就是 LongAdder 用 Cell 数组替代 AtomicLong 的同一思想。
第二层:真正必须单点串行时用"批量 + 短临界区" 单消费者场景,生产者写入要尽量短:
- CAS 自旋(
LongAdder、ConcurrentLinkedQueue的入队)替代阻塞锁,避免线程挂起开销 - 批量提交:生产者攒批(如 64 条)再竞争一次,竞争次数降两个数量级
- 临界区内只做入队(引用赋值),业务逻辑、IO 全部移出锁外
第三层:架构上分散争用点 200 万生产者不该直连一个中心结构:前置按一致性哈希分散到多台 broker/partition(Kafka 的设计哲学),每个 partition 单写者序列化,天然 FIFO 且无锁竞争;消费者按 partition 消费。Kafka 用百万级 TPS 证明了这个模型。
给量级感:单把 synchronized 在高争用下吞吐几千 ops/s 就到顶,分片 + CAS 可以到千万级。核心答题线:能分片就分片,必须串行就缩短临界区 + 批量化,中心结构就学 Kafka 按 partition 分散写。
💻 全栈开发专家
两百万生产者、一个消费者,瓶颈必然在锁/队列的争用点上,我的思路是:要么无锁,要么把锁的粒度切到没有争用。
方案一:无锁队列(首选)
- 生产者侧用 MPSC 队列(多生产单消费者,JCTools 的
MpscQueue)或 Netty 的EventExecutor任务队列,入队纯 CAS,天然为"多生产者→单消费者"设计,无锁无争用。 - 极致吞吐用 Disruptor:环形数组 + 序号屏障,生产者只争一个 cursor 的 CAS,单消费者顺序消费,百万级 QPS 轻松。
方案二:锁分段(striped lock)
如果必须做"入队 + 某种聚合"的复合操作,把一把大锁拆成 N 把:
java
Striped<Lock> locks = Striped.lock(1024); // Guava
Lock l = locks.get(key);200 万生产者按 key 散列到 1024 段,单段争用概率趋近于零,消费者按段批量取,兼顾了顺序性。
方案三:本地聚合再上交
每个生产者线程先攒本地缓冲(ThreadLocal 队列或 LongAdder 思想),攒满一批或定时批量提交,把"200 万次争抢"变成"少量线程的批量提交",这是监控打点(Metrics Micrometer)的真实做法。
选型话术 面试说清楚:单消费者场景锁是大忌,正确姿势是 MPSC 无锁结构 + 批量消费;如果消费者也要扩展,就升级为 WorkPool(Disruptor 的 WorkerHandler)。