【redis 实现分布式锁和重入锁】

文章介绍了如何使用Redis通过setnx命令简易实现分布式锁,并通过LUA脚本实现更安全的分布式锁和可重入锁。同时,文中讨论了加入看门狗机制来避免锁过早释放的问题,以及使用Redis的Hash数据结构来支持可重入特性。
摘要由CSDN通过智能技术生成

通过setnx简易实现分布式锁

public interface Lock {

    boolean lock(String uuid);

    boolean lock(String uuid,long exTime);

    boolean unlock(String uuid);


}

实现类

public class RedisLock implements Lock{


    static {
        jedis = new Jedis("localhost", 6379);

    }
    private static final Jedis jedis;

    private static final long expiressTime=10000;


    @Override
    public boolean lock(String uuid) {

        long expires = System.currentTimeMillis()+expiressTime;
        String expTime = String.valueOf(expires);


        long setnx = jedis.setnx("dis-lock", uuid+"-"+expTime);

        boolean flag = false;
        if(setnx==1) {
            flag=true;
            String s = jedis.get("dis-lock").split("-")[1];
            if(s != null && Long.valueOf(s) <System.currentTimeMillis()){
                String oldValue = jedis.getSet("dis-lock", expTime).split("-")[1];
                if (s !=null && s==oldValue){ //CAS思想
                    flag = true;
                }
            }
        }
        return flag;
    }

    @Override
    public boolean lock(String uuid, long exTime) {
        return false;
    }

    @Override
    public boolean unlock(String uuid) {
        String s = jedis.get("dis-lock").split("-")[0];
        if(uuid.equals(s)){
        long del = jedis.del("dis-lock");
            if(del >=1) return true;
        }
        return false;
    }


}

通过LUA脚本实现

加锁脚本

-- lock 脚本
local key=KEYS[1]
local val=ARGV[1]
local ttl=tonumber(ARGV[2])

local localSet=redis.call('setnx',key,val)
if localSet==1 then
    redis.call('PEXPIRE',key,ttl)
else
    local value=redis.call('get',key)
    if val==value then
        localSet=1
        redis.call('PEXPIRE',key,ttl)
    end
end
return localSet

解锁脚本

-- unlock 脚本
local key=KEYS[1]
local val=ARGV[1]
local value=redis.call('get',key)
if val==value then
   return redis.call('del',key)
else
    return 0
end
public boolean lock(String uuid) {


        boolean flag = false;
        Long eval = (Long)jedis.eval(getLuaScript(lockLua),
                                     Arrays.asList(new String[]{key}),
                                     Arrays.asList(new String[]{uuid,String.valueOf(TTL)}));
        if(eval==1L) return true;
        return flag;
    }
    
   public boolean unlock(String uuid) {
        Long eval = (Long)jedis.eval(getLuaScript(unlockLua),
                                     Arrays.asList(new String[]{key}),
                                     Arrays.asList(new String[]{uuid}));
        if(eval==0L) {
            if(jedis !=null){
                jedis.close();
            }
            return false;
        }
        return true;
    }

加入看门狗

上面的分布式都存在个缺陷,就是程序运行时间超过redis的失效时间,自动释放锁,但是程序没执行完就释放锁,其他线程就可以获得锁了,这违反了我们的初衷了,所以加入看门狗 为其续命。

-- 给redis过期key 续命
local key=KEYS[1]
local val=ARGV[1]
local ttl=tonumber(ARGV[2])

local result=0

if val==redis.call('get',key) then
    result=1
    redis.call('PEXPIRE',key,ttl)
    return result
else
    return result
end
 public boolean lock(String uuid,long exTime) {

        boolean flag = false;
        long end = System.currentTimeMillis()+exTime;
        while(System.currentTimeMillis()<end){
            Long eval = (Long)jedis.eval(getLuaScript(lockLua),
                                         Arrays.asList(new String[]{key}),
                                         Arrays.asList(new String[]{uuid,String.valueOf(TTL)}));

            if(eval==1L) {
                this.watcher(uuid);
                return true;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return flag;
    }

可重入锁

上述锁是不可重入锁,用redis的hash数据结构创建。

-- lock 可重入脚本 脚本
local key=KEYS[1]
local val=ARGV[1]
local ttl=tonumber(ARGV[2])


local localLock=0; -- 0上锁失败 1上锁成功
-- 判断是否有锁
local isExists=redis.call('HSETNX',key,'value',val)
-- local localSet=redis.call('setnx',key,val)
if isExists==1 then
    redis.call('HMSET',key,'count',1);
    redis.call('PEXPIRE',key,ttl)
    localLock=1
    -- 如果已经存在了 看看是不是同一线程了
else
    local value=redis.call('HGET',key,'value')
    if val==value then
        localLock=1
        redis.call('HINCRBY',key,'count',1)
        redis.call('PEXPIRE',key,ttl)
    end
end
return localLock
-- unlock 脚本
local key=KEYS[1]
local val=ARGV[1]
local value=redis.call('HGET',key,'value')
if val==value then
   local lockNums=redis.call('HINCRBY',key,'count',-1)
    if lockNums==0 then
        redis.call('del',key)
    end
   return 1
else
    return 0
end

-- 给redis过期key 续命
local key=KEYS[1]
local val=ARGV[1]
local ttl=tonumber(ARGV[2])

local result=0

if val==redis.call('HGET',key,'value') then
    result=1
    redis.call('PEXPIRE',key,ttl)
    return result
else
    return result
end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值