《高并发优化方案一》:本地锁 + 分布式锁实战详解
高并发优化方案一:本地锁 + 分布式锁实战详解🚀
- 一、 🌐场景背景
- 二、🧠 为什么使用本地 + 分布式锁?
- 三、🔒 本地锁和分布式锁做什么?
- 四、🧰 实战:商品库存扣减代码
- 五、📈 多级锁处理流程图
- 六、⚠️ 注意事项
- 七、❌ 不推荐用`Synchronized(productId.intern() )`代替锁池
- 八、🧠 总结
在高并发场景下,如何保障系统的一致性同时提升性能,一直是后端工程师绕不开的核心问题。今天我们来聊一个实用而高效的优化方案 —— 本地锁 + 分布式锁 的双层锁机制。
这是高并发分布式系统中一种优化在高并发场景下使用Redis分布锁所产生的网络抖动、延迟增加的手段之一,适用于 秒杀库存、订单幂等等保证线程安全的分布式高并发资源场景。
一、 🌐场景背景
你可能遇到过这样的情况:
- 处于分布式系统
- 多个用户几乎同时抢购同一商品
- 因为分布式系统,使用了 Redis 分布式锁来控制并发,但高并发下频繁 Redis 请求导致网络抖动、延迟增加,积累的请求越多,网络请求越来越阻塞,最后甚至可能导致网络奔溃。
- Redis 一旦慢了,整个业务都被拖住
那么,有没有办法减少 Redis 的压力,同时又能保证线程互斥?
答案就是 —— 本地锁 + 分布式锁 多级联动!
二、🧠 为什么使用本地 + 分布式锁?
✅ 优点总结:
优点 | 说明 |
---|---|
🚀 降低 Redis 压力 | 本地锁先阻挡大部分请求,只有一个线程发起 Redis 锁请求 |
📉 减少网络开销 | 减少 Redis I/O 消耗,提高响应速度 |
⚡ 提升吞吐性能 | 实现资源级别的并发处理(不同商品并发互不干扰) |
🧩 可组合扩展 | 支持 tryLock、超时、限流等控制策略 |
🧷 提高系统健壮性 | Redis 故障时仍可依靠本地限流部分挡住请求,防止系统雪崩 |
三、🔒 本地锁和分布式锁做什么?
- 本地锁(JVM 内存锁):挡住同一节点上的并发请求。一般使用
ReentrantLock
或synchronized
,但是不推荐synchronized
,因为这个锁的条件一般只能是实例和calss对象,导致锁的粒度下降,性能降低。 - 分布式锁(如 Redis 锁):用于集群中多个节点之间的互斥。推荐使用 Redisson。
两者搭配使用,可以实现既快又安全的高并发处理方案。
四、🧰 实战:商品库存扣减代码
下面是一个库存扣减逻辑的完整双锁实现。
@Service
public class StockService {private final RedissonClient redissonClient;// 本地锁池(用于按商品ID管理锁)private final Cache<String, ReentrantLock> localLockPool =CacheBuilder.newBuilder().maximumSize(10000).expireAfterAccess(5, TimeUnit.MINUTES).build();@Autowiredpublic StockService(RedissonClient redissonClient) {this.redissonClient = redissonClient;}public boolean decreaseStock(String productId, int quantity) {String redisKey = "lock:stock:" + productId;ReentrantLock localLock = localLockPool.get(productId, ReentrantLock::new);localLock.lock();try {RLock redisLock = redissonClient.getLock(redisKey);if (redisLock.tryLock(5, 10, TimeUnit.SECONDS)) {try {// 查询库存int stock = getStock(productId);if (stock >= quantity) {updateStock(productId, stock - quantity);return true;} else {System.out.println("库存不足");return false;}} finally {redisLock.unlock();}} else {System.out.println("获取分布式锁失败");return false;}} catch (Exception e) {e.printStackTrace();return false;} finally {localLock.unlock();}}private int getStock(String productId) {return 10; // 模拟数据库}private void updateStock(String productId, int newStock) {System.out.printf("商品 %s 库存更新为:%d%n", productId, newStock);}
}
过程:
- 对每一个商品ID单独建立一个锁池条件,对于同一个商品ID会产生互斥的作用,从而保证线程安全,以及多个不同商品ID可以并发的情况。
- 从锁池中获取对应的商品ID的锁,然后进行根据本地ID上锁。等获取锁成功后再进行redis分布式锁的获取,后续就是业务逻辑,然后先释放 Redis锁 再进行对应的 本地锁 释放。
五、📈 多级锁处理流程图
┌──────────────┐│ 接收请求 │└─────┬────────┘▼┌──────────────────────┐│ 获取本地锁(Reentrant)│└────────┬─────────────┘▼┌─────────────────────────────┐│ 获取分布式锁(Redisson) │└─────┬─────────────┬────────┘▼ ▼┌────────────┐ ┌──────────────┐│ 扣减库存 │ │ 返回失败 │└────┬───────┘ └──────────────┘▼┌────────────────┐│ 解锁(本地+Redis)│└────────────────┘
六、⚠️ 注意事项
风险 | 应对策略 |
---|---|
🧠 本地锁池内存泄漏 | 使用 Guava Cache/Caffeine 设置过期时间 |
⛓ 分布式锁未释放 | 使用 Redisson 自动过期 + WatchDog |
🔄 锁冲突过多 | 加指数退避、队列缓存等策略 |
🧱 粒度太粗全系统串行 | 按资源 ID(如商品ID)拆分锁粒度 |
七、❌ 不推荐用Synchronized(productId.intern() )
代替锁池
虽然可以用 productId.intern()
来实现基于字符串的同步,但这种方式:
- 会导致字符串常量池污染(内存泄漏):大量不同商品ID都存放在字符冲常量池中时不会回收,最终导致内存溢出
- 不适用于大规模的动态资源
- 不易追踪调试
建议始终使用 ConcurrentHashMap
或缓存结构来构建锁池,灵活、安全、可控。
如果一定要使用 Synchronized
,可以使用如下方式:
ConcurrentHashMap<String, Object> lockPool = new ConcurrentHashMap<>();
Object lock = lockPool.computeIfAbsent(resourceId, k -> new Object());
synchronized (lock) {// 安全互斥
}
八、🧠 总结
- 本地锁 + 分布式锁是一种性能与安全兼顾的高并发控制策略。
- 正确的锁粒度、锁对象复用和生命周期管理,是该策略能否成功落地的关键。
- 多级锁是构建高可用电商/交易系统的基础能力之一。