Redisson实现Redis分布式锁的底层原理
网上很多关于Redisson实现Redis的分布式原理的分析文章,我这里就不重复写了,看到的比较好的一遍文章如下http://t.csdn.cn/VagLh;
基于Redis自定义实现分布式锁
鉴于目前基于redission的封装过重,并且开发同学使用redission需要自己释放锁,为了尽可能的避免问题,我们自己定义一套分布式锁模板。但是redis锁不一定靠谱(主从切换时有问题),如果考虑可靠性的话建议使用zk实现的分布式锁。
-
我没使用StringRedisTemplate进行key-value的操作;
private final StringRedisTemplate stringRedisTemplate;
-
默认超时时间设置为5分钟;
/** * 默认锁5分钟 */ private static final Duration LOCK_DURATION = Duration.ofMinutes(5L);
-
实现加锁方法
public void lock() { log.debug("get distribute lock {}", key); while (true) { Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, LOCK_DURATION); if (success != null && success) { WATCH_DOG_EXECUTOR.schedule(new WatchDogTask(this), DEFAULT_SLEEP_TIME, TimeUnit.MILLISECONDS); break; } }
上面的方法使用了WatchDog,给锁续命;
5. WatchDog的实现
private static class WatchDogTask implements Runnable {
private final RedisDistributeLock redisDistributeLock;
public WatchDogTask(RedisDistributeLock redisDistributeLock) {
this.redisDistributeLock = redisDistributeLock;
}
@Override
public void run() {
if (redisDistributeLock.isWatchDogFinished()) {
return;
}
log.debug("expire distribute lock {}", redisDistributeLock.key);
Boolean expireResult = false;
try {
expireResult = redisDistributeLock.stringRedisTemplate.execute(
EXPIRE_SCRIPT,
Arrays.asList(redisDistributeLock.key),
redisDistributeLock.uuid,
"300"
);
}
catch (Exception e) {
log.error("expire key {} error", redisDistributeLock.key, e);
}
if (expireResult != null && expireResult && !redisDistributeLock.isWatchDogFinished()) {
WATCH_DOG_EXECUTOR.schedule(new WatchDogTask(this.redisDistributeLock), DEFAULT_SLEEP_TIME, TimeUnit.MILLISECONDS);
}
}
}
锁续命使用的是lua脚本,即看门狗中用到的EXPIRE_SCRIPT变量;
if redis.call('get',KEYS[1]) == ARGV[1] then
redis.call('expire', KEYS[1], ARGV[2])
return 1
else
return 0
end
-
释放锁的方法
public void unlock() { log.debug("release distribute lock {}", key); try { stringRedisTemplate.execute(RELEASE_SCRIPT, Arrays.asList(key), uuid); } catch (Exception e) { log.error("release lock {} error", key, e); } }
执行的lua脚本(RELEASE_SCRIPT变量)
if redis.call('get',KEYS[1]) == ARGV[1] then redis.call('del',KEYS[1]) return 1 else return 0 end
大家可能发现了,上述代码实现的锁不支持可重入;因为从使用场景来看,可重入锁并没有使用的必要,也没必要实现可重入锁,而且增加了可重入锁实现起来也麻烦;但是和jdk的多线程可重入锁还是有却别的,那个的可重入锁是必要的;