Redis实现分布式锁

Posted by Yezhiwei on May 11, 2018

常用的分布式锁实现方式

分布式锁一般有三种实现方式:

  1. 数据库乐观锁;
  2. 基于 Redis 的分布式锁;
  3. 基于 ZooKeeper 的分布式锁。

分布式锁的可靠性

为了确保分布式锁可用,至少要确保锁的实现同时满足以下四个条件

  1. 互斥性:在任意时刻,只有一个客户端能持有锁。
  2. 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁(设置超时)。
  3. 具有容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁(Redis高可用)。
  4. 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能解别人加的锁。

实现

  • 依赖 Jedis 组件,通过 Maven 引入 Jedis
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
  • 加锁
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;
 
    }
 
}

jedis.set(String key, String value, String nx, String expx, int time) 参数解释:

第一个为key,我们使用key来当锁,因为key是唯一的。

第二个为value,在可靠性中,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。

第三个为nx,这个参数设置为NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作。

第四个为expx,这个参数设置为PX,意思是要给这个key加一个过期的设置,防止发生死锁,具体时间由第五个参数决定。

第五个为time,与第四个参数相呼应,代表key的过期时间

  • 解锁
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;
 
    }
 
}

将 Lua 代码传到 jedis.eval() 方法里,并使参数 KEYS[1] 赋值为 lockKey,ARGV[1] 赋值为 requestId。eval() 方法是将 Lua 代码交给 Redis 服务端执行。并且直到eval命令执行完成,Redis才会执行其他命令。

总结:

  1. 要通过 Redis 实现分布式锁,只要保证能满足可靠性里的四个条件。
  2. 如果项目中 Redis 是多机部署的,可以尝试使用 Redisson 实现分布式锁。