专项攻克——redis分布式锁-用LUA脚本解决setNX锁不释放问题

Redis分布式锁:Lua脚本解决setNX锁不释放(面试版)

问题背景:

SETNX key value + EXPIRE key timeout 分开两条命令,不是原子

  • setnx抢到锁,程序崩溃,还没执行expire → 锁永久不释放,死锁
  • 另外一个坑:线程A的锁超时,线程B拿到锁,A执行del把B的锁释放,锁误删除

正确现代写法:SET lock uuid NX EX 30 一条原子命令加锁;但是解锁必须用Lua脚本保证原子,并且校验锁属于当前持有者,不能直接del

核心为什么用Lua

  1. Lua脚本在Redis中是原子执行,整个脚本执行期间不会被其它命令打断。
  2. 解锁两件事要原子做:①判断锁的value是不是当前线程的UUID;②匹配才删除。
    如果不用Lua:get lock 判断 + del lock 两条命令,中间会有时间窗口,会出现误删别人锁。

1、加锁(Java伪代码)

不要用 setnx + expire 分开!

复制代码
// 生成唯一标识,UUID,代表当前客户端/线程
String lockValue = UUID.randomUUID().toString();
// NX:不存在才设置;EX:过期时间秒
String result = redisTemplate.opsForValue()
    .setIfAbsent("lock:order", lockValue, 30, TimeUnit.SECONDS);

2、解锁Lua脚本(重点,防止锁不释放 + 防止误删别人锁)

脚本逻辑:

  1. 获取锁key的value

  2. 判断和传入的当前客户端uuid是否相等

  3. 相等才删除,不相等直接返回0,不做任何操作

    -- unlock.lua
    -- KEYS[1]:锁key lock:order
    -- ARGV[1]:当前客户端唯一uuid
    if redis.call('GET',KEYS[1]) == ARGV[1] then
    return redis.call('DEL',KEYS[1])
    else
    return 0
    end

  • 返回1:解锁成功;返回0:不是自己的锁,不删除。

⚠️这个脚本解决两个问题:

①不会删除别的线程持有的锁;

②判断+删除原子,不会中间出现线程切换。

Java调用Lua示例

复制代码
String script = "if redis.call('GET',KEYS[1]) == ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
// 执行脚本
Long res = redisTemplate.execute(redisScript, Collections.singletonList("lock:order"), lockValue);
if(res == 1){
    System.out.println("解锁成功");
}

❓那锁超时没处理完业务怎么办?(锁自动过期带来的新问题)

Lua解锁脚本解决不了业务执行超过锁过期时间的问题

比如设置30s锁,业务跑了60s;锁自动过期释放,其他线程拿到锁,并发问题出现。

解决方案:看门狗(续期),Redisson已经封装。

  • 拿到锁之后,后台线程每隔 过期时间/3(默认10s)续期重置过期时间;
  • 如果业务正常执行完毕,调用Lua解锁脚本,看门狗停止。

面试问答

Q1:setnx为什么会锁不释放?

setnx、expire是两条独立命令。如果setnx成功,服务宕机,expire永远执行不到,key没有过期时间,锁永久存在死锁。

解决方案:使用SET key value NX EX time 一条原子命令完成加锁+设置过期,加锁阶段就避免死锁。

Q2:解锁为什么不能先get再del,要Lua脚本?

get 和 del是两条命令,中间存在时间窗口:

  1. 线程A get发现是自己的锁
  2. 刚好锁超时,redis自动过期删除,线程B拿到同一个锁
  3. 线程A执行del,把B刚拿到的锁删掉>

造成锁误释放,并发安全失效。Lua脚本保证判断+删除整个逻辑原子执行。

Q3:Lua脚本能解决业务执行超过锁超时的问题吗?

不能。Lua只保证解锁逻辑原子。业务耗时大于锁过期时间,锁会自动释放,其他线程进来,会并发。要靠看门狗续期(Redisson)。

Q4:Redisson底层加解锁是不是Lua?

是的,Redisson加锁、解锁、锁续期全部使用Lua脚本保证原子性。

Q5:Redis分布式锁还存在什么缺陷?

主从集群场景:主节点写完锁,还没同步到从节点,主宕机,从晋升为主;锁丢失,出现并发。

解决方案:Redlock红锁(生产很少用,运维重);业务层面兜底。

面试一句话背诵版

使用SET key uuid NX EX原子完成加锁设置过期,避免setnx成功宕机导致锁永不释放;

解锁使用Lua脚本,原子完成校验锁持有者+删除,避免get+del非原子误删除别的线程锁;

Lua脚本无法解决业务执行超过锁过期时间,需要看门狗自动续期,生产推荐Redisson。

相关推荐
会博通·代码搬运工1 小时前
双层PDF技术实现与档案数字化系统的API集成实践
数据库·人工智能·分布式·python·计算机视觉·api集成
天远Date Lab2 小时前
零信任架构实战:基于天远天远风控经营异常预警构建分布式企业合规监控网关
人工智能·分布式·架构
Escalating_xu4 小时前
【C++入门基础(下)】默认参数、函数重载、引用、inline 与 nullptr
android·c++·redis
宠友信息15 小时前
社区类源码开发实践中的仿小红书系统技术要点分析
java·spring boot·redis·mysql·uni-app·vue·内容运营
南城以南溫暖如初14717 小时前
从零搭建24小时自助健身系统:技术选型与核心模块实战
java·spring boot·redis·mysql·vue·mybatis
hweiyu0017 小时前
Redis命令:EXPIRE
redis
InfinitePlus17 小时前
redis数据持久化
数据库·redis·缓存
小白酷爱学习19 小时前
鸿蒙OS的开发语言与工具链:如何驾驭全新开发生态!
分布式·华为·架构·harmonyos
Baron X20 小时前
Redis 7.0.9 完整部署指南
数据库·redis