IWA
2025-08-05
点 赞
0
热 度
4
评 论
0

Redis 分布式锁:原理、实现与最佳实践

在高并发系统中,分布式锁 是解决资源竞争的关键技术。Redis 因其高性能和原子性操作,成为实现分布式锁的常用方案。本文将详细介绍 Redis 分布式锁的核心原理、实现方式、常见问题及优化方案,帮助你构建健壮的分布式锁机制。


1. 为什么需要分布式锁?

在单机环境下,可以使用 synchronizedReentrantLock 保证线程安全。但在分布式系统中,多个服务实例可能同时操作共享资源(如库存扣减、订单创建),这时就需要 分布式锁 来确保:
互斥性:同一时间只有一个客户端能持有锁。
避免死锁:即使客户端崩溃,锁也能自动释放。
高性能:加锁、解锁操作要高效,避免成为系统瓶颈。


2. Redis 分布式锁的核心实现

2.1 基础实现(SETNX + EXPIRE)

最简单的 Redis 锁使用 SETNX(SET if Not eXists)命令:

Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock:order:123", "1");
if (lock) {
    redisTemplate.expire("lock:order:123", 30, TimeUnit.SECONDS); // 设置过期时间
    try {
        // 执行业务逻辑...
    } finally {
        redisTemplate.delete("lock:order:123"); // 释放锁
    }
} else {
    throw new RuntimeException("系统繁忙,请重试!");
}

问题SETNXEXPIRE 不是原子操作,如果在这两步之间程序崩溃,会导致死锁!


2.2 原子性加锁(SET NX EX)

Redis 2.6.12+ 支持 SET key value NX EX seconds,可以一步完成加锁和设置过期时间:

Boolean lock = redisTemplate.opsForValue().setIfAbsent(
    "lock:order:123", 
    "1", 
    30,  // 过期时间
    TimeUnit.SECONDS
);
if (!lock) {
    throw new RuntimeException("系统繁忙,请重试!");
}

优点
✔ 原子性操作,避免死锁
✔ 代码简洁


2.3 更安全的锁(UUID + Lua 释放)

问题:如果锁过期后业务仍在执行,其他客户端可能误删锁!优化方案

  1. 锁的值使用唯一标识(如 UUID),防止误删。

  2. 释放锁时用 Lua 脚本,保证原子性。

String lockKey = "lock:order:123";
String lockValue = UUID.randomUUID().toString(); // 唯一标识
// 加锁(原子操作)

Boolean locked = redisTemplate.opsForValue().setIfAbsent(

lockKey,

lockValue,

30,

TimeUnit.SECONDS

);if (!locked) {

throw new RuntimeException("系统繁忙,请重试!");

}try {

// 执行业务逻辑…

} finally {

// 释放锁(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<>(luaScript, Long.class),
    Collections.singletonList(lockKey),
    lockValue
);
}

优点
✔ 防止误删其他客户端的锁
✔ 释放锁是原子操作


3. 进阶优化方案

3.1 锁自动续期(WatchDog)

问题:如果业务执行时间超过锁的过期时间,会导致锁提前释放!解决方案

  • Redisson 提供了 WatchDog 机制,后台线程定期检查锁,如果业务未完成,自动续期。

RLock lock = redissonClient.getLock("lock:order:123");
try {
    lock.lock(30, TimeUnit.SECONDS); // 加锁,默认 WatchDog 每 10 秒续期
    // 执行业务...
} finally {
    lock.unlock();
}

3.2 可重入锁

问题:同一线程多次获取锁时,普通 Redis 锁会阻塞自己!解决方案

  • Redisson 可重入锁:记录锁的持有者和重入次数。

RLock lock = redissonClient.getLock("lock:order:123");
lock.lock();  // 第一次获取
lock.lock();  // 可重入,不会阻塞
lock.unlock();
lock.unlock();

3.3 高可用 Redis 锁(RedLock)

问题:单点 Redis 宕机可能导致锁失效!解决方案

  • RedLock 算法:在多个 Redis 节点上加锁,多数成功才算加锁成功。

RedissonRedLock redLock = new RedissonRedLock(
    redissonClient.getLock("lock:1"),
    redissonClient.getLock("lock:2"),
    redissonClient.getLock("lock:3")
);
redLock.lock();
try {
    // 执行业务...
} finally {
    redLock.unlock();
}

4. Redis 锁的常见问题

问题

原因

解决方案

死锁

加锁后未设置过期时间

使用 SET NX EX 原子操作

误删锁

锁过期后,其他客户端删错了锁

使用 UUID + Lua 脚本释放

锁续期问题

业务未完成,锁已过期

使用 Redisson WatchDog

不可重入

同一线程无法重复获取锁

使用 Redisson 可重入锁

单点故障

Redis 宕机导致锁失效

使用 RedLock 多节点锁


5. 最佳实践总结

  1. 优先使用 SET key value NX EX seconds(原子性加锁)。

  2. 锁的值用 UUID,防止误删。

  3. 释放锁用 Lua 脚本,保证原子性。

  4. 长时间任务使用 WatchDog 自动续期(如 Redisson)。

  5. 高可用场景用 RedLock(多 Redis 节点)。


6. 完整代码示例

// 加锁
String lockKey = "lock:order:123";
String lockValue = UUID.randomUUID().toString();
Boolean locked = redisTemplate.opsForValue().setIfAbsent(

lockKey,

lockValue,

30,

TimeUnit.SECONDS

);if (!locked) {

throw new RuntimeException("系统繁忙,请重试!");

}try {

// 执行业务逻辑…

} finally {

// 释放锁(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<>(luaScript, Long.class),
    Collections.singletonList(lockKey),
    lockValue
);
}


7. 结论

Redis 分布式锁是实现高并发控制的重要手段,但直接使用 SETNX 容易遇到死锁、误删等问题。推荐:

  • 简单场景SET NX EX + Lua 释放。

  • 复杂场景:使用 Redisson(支持可重入锁、WatchDog、RedLock)。

正确使用 Redis 锁,可以让你的分布式系统更稳定、更高效! 🚀


用键盘敲击出的不只是字符,更是一段段生活的剪影、一个个心底的梦想。希望我的文字能像一束光,在您阅读的瞬间,照亮某个角落,带来一丝温暖与共鸣。

IWA

estp 企业家

具有版权性

请您在转载、复制时注明本文 作者、链接及内容来源信息。 若涉及转载第三方内容,还需一同注明。

具有时效性

文章目录

IWA的艺术编程,为您导航全站动态

10 文章数
9 分类数
9 评论数
12标签数
最近评论
M丶Rock

M丶Rock


😂

M丶Rock

M丶Rock


感慨了

M丶Rock

M丶Rock


厉害了

访问统计