redis 实现分布式锁和重入锁
通过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