【Redis 开发】分布式锁中的常见问题和Lua脚本

发布于:2024-04-30 ⋅ 阅读:(29) ⋅ 点赞:(0)

分布式锁中的问题

分布式锁中我们设置的过期时间:
如果有一个线程获取锁之后在进行操作时,到达了锁的过期时间,之后就会有别的线程获得锁,如果这时,第一个线程执行完成后释放锁,就会将第二个锁的线程删除

针对这个情况如何改进:

  1. 在获取锁时存入线程标示(可以用UUID)
  2. 在释放锁时先获取锁中的线程标示,判断是否与当前线程标识一致
  3. 如果一致则释放锁
  4. 如果不一致则不释放锁

改进分布式锁添加释放锁的判断

public class SimpleRedisLock implements ILock{

    private String name;
    private StringRedisTemplate stringRedisTemplate;

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    private static final String key_prefix="lock:";
    private static final String id_prefix= UUID.randomUUID().toString()+"-";
    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程的标识
        String threadId= id_prefix+Thread.currentThread().getId();
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key_prefix + name, threadId, timeoutSec, TimeUnit.SECONDS);
        //自动拆箱的返回
        return Boolean.TRUE.equals(aBoolean);
    }

    @Override
    public void unlock() {
        //获取线程标识
        String threadid = id_prefix + Thread.currentThread().getId();
        //获取锁中的标识
        String s = stringRedisTemplate.opsForValue().get(key_prefix + name);
        //判断标识是否一致
        if(threadid.equals(s)) {
            //释放锁
            stringRedisTemplate.delete(key_prefix+name);
        }
        
    }
}

上述我们做了修改进行判断,但是还存在一种极端情况,当线程操作完毕需要释放锁的时候,这个时候已经判断完毕,但是由于比如说垃圾回收等问题对线程的释放操作进行阻塞,这个时候如果超过等待时间,这是还是会出现上述问题,在阻塞结束之后,会删除其他线程的锁

要彻底避免这种情况的发生,需要将判断锁标识的动作与释放锁标识的动作进行原子性操作,此时就会用到Lua脚本

Lua脚本

Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性,Lua脚本时一种编程语言
地址:https://www.runoob.com/lua/lua-tutorial.html

编写Lua脚本

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by LENOVO.
--- DateTime: 2024/4/26 19:01
---比较线程标识与锁中的标识是否一致
if(redis.call('get',KEY[1]) == ARGV[1]) then
    --- 释放锁资源
    return redis.call('del',KEY[1])
end
return 0

调用Lua脚本:

    @Override
    public void unlock() {
        //调用lua脚本
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(key_prefix+name),
                id_prefix+Thread.currentThread().getId());
    }

其中的UNLOCK_SCRIPT是脚本对象,需要提前进行定义

    //设置脚本对象
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;

    static{
        UNLOCK_SCRIPT =new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }

网站公告

今日签到

点亮在社区的每一天
去签到