基于redis的分布式锁(Java实现)

Github 源码:

https://github.com/z521598/redis-lock

实现原理:

1.setnx

Redis 的 setnx 指令(文档参考),setnx 意为 SET if Not eXists,命令格式:setnx $key $value

如果此 key 不存在,则设置值为 value,返回值为 1;如果此 key 存在,则不设置,返回值为 0。如下图:

127.0.0.1:6379[1]> setnx key v
(integer) 1
127.0.0.1:6379[1]> setnx key v2
(integer) 0

redis 是单线程的,是线程安全的,setnx 指令由于上述的特性能够满足高并发情况下的对于锁的需求。

2.SpringData-redis

springData-redis 是向 Redis 发送命令以及接受数据的高层次抽象的模版方法,简单理解为“使用 java 向 redis 发送命令以及接受数据的客户端”(文档参考

配置文件:https://github.com/z521598/redis-lock/blob/master/src/main/resources/applicationContext.xml

V1

说明:

最简单的最粗糙的锁实现,实现了 2 个方法。

方法 1:获取锁,public UUID acquire(String lockKey, long acquireTimeoutInMillis, long lockExpiryInMillis)

参数说明:lockKey 为锁的 key;acquireTimeoutInMillis 为获取锁的等待时间,如果超过此时间就放弃锁;lockExpiryInMillis 为锁的过期时间。

返回值:锁对应的值,就是命令中“setnx key value”的 value。

思路:

1. 调用 SpringData-Redis 的 setIfAbsent(lockKey, value) 方法 (就是命令行中的 setnx),value 为随机的 UUID。

2. 如果返回 true,则说明已经获取的锁,则继续设置超时时间,返回设置的 UUID。

  如果返回 false,则说明未获取到锁,则休眠 100ms,并记录总休眠的时间,如果 lockExpiryInMillis 大于等于记录总休眠的时间,则说明未获取到锁,返回 null。

方法 2:释放锁,public void release(String lockKey, UUID uuid)

参数说明:lockKey 为锁的 key;uuid 为锁的 value。

思路:检查锁的 value 是否为 uuid,如果相等,则释放,如果不相等,则什么都不做;防止释放了其他线程获取的锁。

明显的缺点:

第一步,获取锁,第二步,然后设置超时时间。这是 2 步操作,不是原子性操作,如果第一步操作之后,程序崩溃了或者掉电了或者 redis 恰巧进行了主从切换等等原因,第二步无法正常执行,这样这个锁就永远得不到释放。

代码:

public UUID acquire(String lockKey, long acquireTimeoutInMillis, long lockExpiryInMillis)
            throws InterruptedException {
        UUID uuid = UUID.randomUUID();
        long timeout = 0L;
        while (timeout < acquireTimeoutInMillis) {
            if (redisTemplate.opsForValue().setIfAbsent(lockKey, uuid.toString())) {
                redisTemplate.expire(lockKey, lockExpiryInMillis, TimeUnit.MILLISECONDS);
                return uuid;
            }
            TimeUnit.MILLISECONDS.sleep(DEFAULT_ACQUIRE_RESOLUTION_MILLIS);
            timeout += DEFAULT_ACQUIRE_RESOLUTION_MILLIS;
        }
        return null;
    }

锁实现:https://github.com/z521598/redis-lock/blob/master/src/main/java/com/redis/lock/sdata/v1/LockService.java

单元测试:https://github.com/z521598/redis-lock/blob/master/src/test/java/com/redis/lock/sdata/v1/LockServiceTest.java

V2

说明:

大体与 V1 相同,但是锁的 value 是 "过期的时间",如果获取锁的时候,发现过期时间小于 now,则视为锁已经过期。

缺点:(极少出现的情况)

当 redis 是主从形式的情况下,获取锁之后,master 宕机,slave 接管,但是这个时候“新 master”还未同步锁的 key。在这个时候,其他线程去获取锁,发现无此 key,则获取了不应该获取的锁,这样就会引起不安全的情况。

代码:

锁实现:https://github.com/z521598/redis-lock/tree/master/src/main/java/com/redis/lock/sdata/v2

单元测试:https://github.com/z521598/redis-lock/blob/master/src/test/java/com/redis/lock/sdata/v2/LockV2ServiceTest.java

 V3

敬请期待