三种常见实现分布式锁的方式: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时,释放锁;

参考:

Last modification:March 29th, 2021 at 11:12 pm
请作者喝杯肥宅快乐水吧!