1. 什么是分布式锁
分布式锁是一种用于协调多个分布式节点之间互斥访问共享资源的机制。在单机环境下,我们可以通过操作系统提供的锁(如 synchronized、ReentrantLock)来保证同一进程内多个线程对共享资源的互斥访问;但在分布式系统中,多个进程运行在不同的机器上,传统的本地锁无法跨进程生效,因此需要一种能够在分布式环境中全局生效的锁,这就是分布式锁。
分布式锁的核心目标是在多个节点之间保证「同一时刻只有一个节点能够持有锁并访问临界资源」,从而避免并发冲突、数据不一致等问题。这在我们的日常业务中非常常见,比如说我们的秒杀场景,我们的库存只有那么多,因此我们必须使用分布式锁去让有限个线程抢到这个商品,如果不添加分布式锁,那么当秒杀活动一开始,无数人同时点击抢的按钮,那么此时所有人都抢到了,但是库存是有限的,就会出现超卖的问题,因此我们需要使用分布式锁加以控制,去保证不超卖。
2. 分布式锁需要满足的条件
一个合格的分布式锁通常需要满足以下几个条件:
- 互斥性:任意时刻,只有一个客户端能够持有锁。
- 可重入性:同一客户端在持有锁的情况下可以再次获取锁,避免死锁。
- 高可用性:锁服务本身要具备高可用能力,避免单点故障导致锁不可用。
- 自动释放:当持有锁的客户端崩溃或超时时,锁能够自动释放,避免死锁。
- 公平性:按请求顺序获取锁,避免饥饿。这在我们的实际业务中是非常重要的一个点,这个是用户体验的关键,如果这个公平性没有保证,那么用户也不会相信这个平台,从而流失客户。
3. 基于 Redis 实现分布式锁
Redis 凭借其高性能和原子操作,成为实现分布式锁最常用的方案。核心思路是利用 SET key value NX EX timeout 命令,保证同一时刻只有一个客户端能成功设置 key,从而获得锁。
// 获取锁
String lockKey = "lock:order:1001";
String requestId = UUID.randomUUID().toString();
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, 30, TimeUnit.SECONDS);
// 释放锁(使用 Lua 脚本保证原子性)
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class),
Arrays.asList(lockKey), requestId);
其中 NX 表示只有 key 不存在时才设置成功,EX 设置过期时间防止死锁,requestId 用于标识锁的持有者,避免误删他人锁。释放锁时通过 Lua 脚本校验持有者身份,保证原子性。
4. 实现分布式锁的注意事项
在实际项目中实现分布式锁,还需要关注以下几个关键问题:
- 锁的过期时间:设置合理的过期时间,避免业务执行时间过长导致锁提前释放,引发并发问题。
- 锁的续期:对于耗时较长的业务,需要实现锁的自动续期机制(如 Redisson 的 watchdog),防止锁在业务执行期间过期。
- 锁的释放:释放锁时必须校验持有者身份,避免误删其他客户端持有的锁。
- 主从切换问题:Redis 主从架构下,主节点宕机可能导致锁丢失,需要引入 RedLock 等更复杂的方案来提升可靠性。
5. 实现分布式锁的注意事项
在实际项目中实现分布式锁,还需要关注以下几个关键问题:
- 锁的过期时间:设置合理的过期时间,避免业务执行时间过长导致锁提前释放,引发并发问题。
- 锁的续期:对于耗时较长的业务,需要实现锁的自动续期机制(如 Redisson 的 watchdog),防止锁在业务执行期间过期。
- 锁的释放:释放锁时必须校验持有者身份,避免误删其他客户端持有的锁。
- 主从切换问题:Redis 主从架构下,主节点宕机可能导致锁丢失,需要引入 RedLock 等更复杂的方案来提升可靠性。
5. 总结
分布式锁是分布式系统中保证互斥访问共享资源的重要手段。本文介绍了分布式锁的基本概念、需要满足的条件,以及基于 Redis 实现分布式锁的具体方案和注意事项。在实际项目中,应根据业务场景的并发量、可靠性要求和运维成本,选择最合适的实现方案。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/wuzhihua66/article/details/167779468




