三种常见实现分布式锁的方式:Redis、Zookeeper、数据库
redis 分布式锁
加锁
- setnx 命令,如果返回1表示加锁成功,否则加锁失败;
- 记得要加过期时间,否则可能会出现持有锁的程序挂掉导致锁无法释放,最后出现死锁问题
- 加锁时要设置唯一的 value,比如用 UUID 生成,这样只有持有锁的线程才知道锁是它的
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
释放锁
eval 命令执行 lua 脚本来实现
- 不能仅用 del 命令实现,否则会出现释放了别人的锁的情况;应该先比较是否是自己的锁,如果是,再释放;所以这里用 lua 脚本
- redis 的 lua 脚本将以原子性的方式的执行
public class RedisTool {
private static final Long RELEASE_SUCCESS = 1L;
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
简单的命令参考:
// 获取锁
// NX是指如果key不存在就成功,key存在返回false,PX可以指定过期时间
SET d_lock unique_value NX PX 30000
// 释放锁:通过执行一段lua脚本
// 释放锁涉及到两条指令,这两条指令不是原子性的
// 需要用到redis的lua脚本支持特性,redis执行lua脚本是原子性的
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
补充:RedisTemplate 也封装了相关的方法,可以实现上述的加锁、解锁功能;
Redission 这个 Redis 客户端也实现了分布式锁,并添加了 watch dog 看门狗机制来给锁续命;
- watch dog: 创建一个守护线程,让其每隔一段时间给未释放的锁续命
扩展:分布式可重入锁
- 实现可重入,那么可以用唯一 val + version,version 就是一个 count,记录了重入锁的重入次数,当次数为0时,释放锁;
参考:
One comment
OωO