分布式锁的实现方式

分布式锁的实现方式一般分为3种:数据库乐观锁、基于redis的分布式锁、基于zookeeper的分布式锁
为了确保分布式锁可用,至少要保证所得实现满足4种条件:
1.互斥性。在任意时刻,只有一个客户端能持有锁。
2.不会发生死锁。即使有一个客户端在持有锁期间挂掉没有主动释放锁,也要保证后续其他客户端可以加锁。
3.具有容错性。只要大部分的redis节点正常运行,客户端就可以加锁和解锁。
4.加锁和解锁必须保证是同一个客户端,不能把别人的锁解除了

基于redis的分布式锁

加锁

参考网上例子,写出以下代码

redission原子减如何防止减到0 redis 原子锁_分布式锁

redisService.set(key, value, “nx”, “ex”, time) 这个是加锁的代码
key 为锁的唯一标识。
value 为验证客户端唯一性的参数,可以使用 UUID.randomUUID().toString() 获取。
nx 为SET IF NOT EXIST,key 存在时不做任何操作。
ex 为设置过期时间,具体时间由第五个参数决定。
time 为过期时间。

解锁

redission原子减如何防止减到0 redis 原子锁_分布式锁_02

用上面生成的 value 和redis里面获取的 value 进行比较,相同则表示是相同的客户端,之后释放锁即可。
这里的问题是分为两步操作,不是原子性的,当第一步执行完成后锁可能因为过期销毁,就可能会发生另一个客户端刚拿到锁就被释放的问题。

最好的解决方式是通过 redis 的 eval() 方法,这个方法可以执行一段脚本,脚本运行是原子性的,在脚本运行期间没有客户端可以操作,下面为拷贝过来的代码

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;

    }

}

关于 eval() 方法的参数,大致有四个参数
1.script :一段需要执行的脚本
2.numkeys: 用于指定键名参数的个数
3.key [key …]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
4.arg [arg …]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。