3

一个 Redis 分布式锁的实现

 3 years ago
source link: http://www.eknown.cn/index.php/default/78.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK