P09 分布式集群架构下怎么保证并发安全
面试题:系统部署了多个实例(集群),原来单机上的 synchronized/Lock 还管用吗?怎么保证并发安全?
核心:单机锁在集群下失效
synchronized、ReentrantLock 锁的是单个 JVM 内的对象,集群里有多个 JVM,各自持有一把锁,锁不住其他实例的线程。所以需要跨进程的互斥机制。
常用方案
1. 分布式锁
- Redis 分布式锁(
SETNX+ 过期时间 + Lua 释放),或直接用 Redisson:- 注意:要设置过期时间防止死锁;释放时校验 value 防止误删;高可用可用 Redlock/红锁(一般不用);
- Zookeeper 分布式锁(临时顺序节点 + Watch):
- 强一致、无过期时间问题,但性能不如 Redis,适合对一致性要求高的场景;
- 场景:抢单、扣库存、任务调度(分布式定时任务只让一台执行)。
2. 数据库层保证(最强兜底)
并发安全最终要以数据库为准:
- 乐观锁:
UPDATE ... SET version = version + 1 WHERE id = ? AND version = ?; - 条件更新:扣库存用
SET stock = stock - 1 WHERE id = ? AND stock > 0; - 唯一索引:防止重复插入(重复下单、幂等)。
3. 原子操作前置化
把高频并发操作放到 Redis 用 Lua 脚本原子执行(判断 + 扣减一步完成),再异步落库,数据库只做最终校验。
4. MQ 串行化
同一业务维度(同一用户、同一订单)的消息路由到同一个队列/分区,消费端串行处理,天然避免并发冲突。
设计原则
- 能无锁就无锁:用不可变对象、ThreadLocal、单线程化(如 Disruptor/队列)规避;
- 锁的范围尽量小:只锁必要资源(如按 userId 分片加锁),避免全局锁拖垮吞吐;
- 最终一致性兜底:并发控制解决"抢",数据库约束解决"错";
- 幂等设计:重试、重复请求不产生副作用。
一句话总结
集群并发安全靠"分布式锁做互斥 + 数据库乐观锁/唯一约束做兜底 + MQ/分片串行化降并发冲突",并优先考虑无锁化设计。