redis实现分布式锁

Posted by Clear Blog on January 9, 2019

分布式锁的实现方式有多种,我觉得redis实现起来应该是最方便的,后续我会更新下通过zookeeper来实现分布式锁。

首先看获取分布式锁的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 获取分布式锁
 *
 * @param lockKey    锁
 * @param requestId  请求表示
 * @param expireTime 超时时间,单位为毫秒
 * @return
 */
public boolean tryGetLock(String lockKey, String requestId, int expireTime) {
    //setIfAbsent对应的setnx
    if (valueOperations.setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS)) {
        return true;
    }
    return false;
}

对方法和参数一一解释下,

  • setIfAbsent,当key不存在时,我们进行set操作;若key已经存在,则不做任何操作,也就是说当别的进程获取锁的时候key值是已存在的。
  • requestId,在释放锁的时候根据requestId来进行释放,
  • expireTime则是为了防止死锁而设置的超时时间自动释放锁,如果redis突然崩溃,而key没有设置超时时间,则key一直存在,应用一直无法获取到锁

释放锁的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 释放分布式锁
 */
public boolean unLock(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";
    return redisTemplate.execute(
            (RedisConnection connection) -> connection.eval(
                    script.getBytes(),
                    ReturnType.INTEGER,
                    1,
                    lockKey.getBytes(),
                    requestId.getBytes())
    ).equals(1);
}

这里执行的是一段lua代码,逻辑很简单,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。 eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令,就能保证操作的原子性, 如果不能保证原子性的话会存在以下场景: 客户端A加锁,一段时间之后客户端A解锁,在执行delete操作之前,锁过期了, 此时客户端B尝试加锁成功,然后客户端A再执行delete方法,则将客户端B的锁给解除了。

redis官方redlock的分布式锁的实现,但是争议很大,所以不建议用redis来做分布式锁。 以下是redlock算法流程:

  1. 获取当前时间戳,单位是毫秒
  2. 跟上面类似,轮流尝试在每个master节点上创建锁,过期时间较短,一般就几十毫秒
  3. 尝试在大多数节点上建立一个锁,比如5个节点就要求是3个节点(n / 2 +1)
  4. 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了
  5. 要是锁建立失败了,那么就依次删除这个锁
  6. 只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁