Redis的分布式锁问题(九)Redis + Lua 脚本实现分布式锁
Redis的分布式锁问题(八)基于Redis的分布式锁_面向鸿蒙编程的博客-CSDN博客
https://blog.csdn.net/weixin_43715214/article/details/127967364我们在上一个章节中解决了“分布式锁误删问题”,改进后的代码逻辑如下所示:

但是这仍然不是最佳的实现方案,它在极端的情况下还是会发生问题!
- public void unlock() {
- // 获取线程标示
- String threadId = ID_PREFIX + Thread.currentThread().getId();
- // 获取锁中的标示
- String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
- // 判断标示是否一致
- if(threadId.equals(id)) {
- // 释放锁
- stringRedisTemplate.delete(KEY_PREFIX + name);
- }
- }
如上述代码,如果“判断锁标识”和“释放锁”,之间发生了阻塞呢?(JVM触发FULL GC)
那么之前一节所讲的“锁误删”就有可能发生!!!
所以,我们的解决思路是要保证这两个操作的原子性!
我们可以使用Redis的事务+乐观锁来解决这个问题,但是这样子做非常复杂!这里我们使用 Lua 脚本来实现分布式锁
Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。
Lua 教程 | 菜鸟教程 (runoob.com)
https://www.runoob.com/lua/lua-tutorial.html
- if(0)
- then
- print("0 为 true")
- end



如果脚本中的key、value不想写死,可以作为参数传递。key类型参数会放入KEYS数组,其它参数会放入ARGV数组,在脚本中可以从KEYS和ARGV数组获取这些参数:
在Lua中,数组的下标是从1开始的!!!

- 获取锁中的线程标示
- 判断是否与指定的标示(当前线程标示)一致
- 如果一致则释放锁(删除)
- 如果不一致则什么都不做


- -- 获取锁标识,是否与当前线程一致?
- if(redis.call('get', KEYS[1]) == ARGV[1]) then
- -- 一致,删除
- return redis.call('del', KEYS[1])
- end
- -- 不一致,直接返回
- return 0

- /**
- * redis的分布式锁
- * 实现ILock接口
- */
- public class SimpleRedisLock implements ILock {
-
- // 不同的业务有不同的锁名称
- private String name;
- private StringRedisTemplate stringRedisTemplate;
- private static final String KEY_PREFIX = "lock:";
- private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
- // DefaultRedisScript,
- private static final DefaultRedisScript
UNLOCK_SCRIPT; -
- public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
- this.name = name;
- this.stringRedisTemplate = stringRedisTemplate;
- }
-
- // 初始化 UNLOCK_SCRIPT,用静态代码块的方式,一加载SimpleRedisLock有会加载unlock.lua
- // 避免每次调unLock() 才去加载,提升性能!!!
- static {
- UNLOCK_SCRIPT = new DefaultRedisScript<>();
- // setLocation() 设置脚本位置
- UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
- // 返回值类型
- UNLOCK_SCRIPT.setResultType(Long.class);
- }
-
- /**
- * 获取锁
- */
- @Override
- public boolean tryLock(long timeoutSec) {
- // 获取线程标示
- String threadId = ID_PREFIX + Thread.currentThread().getId();
- // 获取锁
- // set lock thread1 nx ex 10
- // nx : setIfAbsent(如果不存在) , ex : timeoutSec(秒)
- Boolean success = stringRedisTemplate.opsForValue()
- .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
- // 自动拆箱(Boolean -> boolean)!!!可能有风险
- return Boolean.TRUE.equals(success);
- }
-
- /**
- * 解决判断(锁标识、释放锁)这两个动作,之间产生阻塞!!!
- * JVM的 FULL GC
- * 要让这两个动作具有原子性
- */
- @Override
- public void unlock() {
- // 调用lua脚本
- stringRedisTemplate.execute(
- UNLOCK_SCRIPT,
- Collections.singletonList(KEY_PREFIX + name),
- ID_PREFIX + Thread.currentThread().getId());
- }
- }