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

文章讨论了分布式锁中过期时间可能导致的问题,提出通过存储线程标识并在解锁时进行一致性检查。然后引入Lua脚本确保标识比较和解锁操作的原子性,以防止因垃圾回收导致的并发问题。
摘要由CSDN通过智能技术生成

分布式锁中的问题

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

针对这个情况如何改进:

  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);
    }
  • 13
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值