• 利用 redis 实现分布式锁服务


    • 支持立即获取锁方式,如果获取到返回true,获取不到则返回false;

    • 支持等待获取锁方式,如果获取到,直接返回true,获取不到在等待一小段时间,在这一小段时间内反复尝试,如果尝试成功,则返回true,等待时间过后还获取不到则返回false;

    • 不能产生死锁的情况;

    • 不能释放非自己加的锁;

    下面我们用实例来演示在 Java 中利用 redis 实现分布式锁服务

    加锁

    通过 redis 来实现分布式锁的加锁逻辑如下所示:

     jedis.select(dbIndex);
    String key = KEY_PRE + key;
    String value = fetchLockValue();
    if(jedis.exists(key)){
      jedis.set(key,value);
      jedis.expire(key,lockExpirseTime);
      return value;
    }

    表面上看这段代码好像没有什么问题,实际上并不能在分布式环境中正确的实现加锁的操作。要能够正确的实现加锁操作,“判断 key 是否存在”、“保存 key-value”、“设置 key 的过期时间”这三步操作必须是原子操作。如果不是原子操作,那么可能会出现下面两种情况:

    • “判断 key 是否存在”得出 key 不存在的结果步骤后,“保存 key-value”步骤前,另一个客户端执行同样的逻辑,并且执行到了“判断 key 是否存在”步骤,同样得出了 key 不存在的结果。这样回导致多个客户端获得了同一把锁;

    • 在客户端执行完“保存 key-value” 步骤后,需要设置一个 key 的过期时间,防止客户端因为代码质量未解锁,在或者进程崩溃未解锁导致的死锁情况。在“保存 key-value”步骤之后,“设置 key 的过期时间”步骤之前,可能进程崩溃,导致“设置 key 的过期时间”步骤失败;

    redis 在2.6.12版本之后,对 set 命令进行了扩充,能够规避上面的两个问题。新版的 redis set 命令的参数如下

    SET key value [EX seconds] [PX milliseconds] [NX|XX]

    新版的 set 命令增加了 EX 、 PX 、 NX|XX 参数选项。他们的含义如下

    • EX seconds – 设置键 key 的过期时间,单位时秒

    • PX milliseconds – 设置键 key 的过期时间,单位时毫秒

    • NX – 只有键 key 不存在的时候才会设置 key 的值

    • XX – 只有键 key 存在的时候才会设置 key 的值

    这样,原来的三步操作就可以在一个 set 的原子操作里面来完成,规避了上面我们提到的两个问题。

    新版的 redis 加锁核心代码修改如下所示:

    jedis = redisConnection.getJedis();
    jedis.select(dbIndex);
    String key = KEY_PRE + key;
    String value = fetchLockValue();
    if ("OK".equals(jedis.set(key, value, "NX", "EX", lockExpirseTime))) {
        return value;
    }

    解锁

    解锁的基本流程如下:

     根据这个逻辑,在 Java 中解锁的核心代码如下所示:

    jedis.select(dbIndex);
    String key = KEY_PRE + key;
    if(jedis.exists(key) && value.equals(jedis.get(key))){
        jedis.del(key);
        return true;
    }
    return false;

    和加锁的时候一样,key 是否存在、判断是否自己持有锁、**删除 key-value **这三步操作需要是原子操作,否则当一个客户端执行完“判断是否自己持有锁”步骤后,得出自己持有锁的结论,此时锁的过期时间到了,自动被 redis 释放了,同时另一个客户端又基于这个 key 加锁成功,如果第一个客户端还继续执行删除 key-value的操作,就将不属于自己的锁给释放了。

    这显然是不运行的。在这里我们利用 redis 执行 Lua 脚本的能力来解决原子操作的问题。修改后的解锁核心代码如下所示:

    jedis.select(dbIndex);
    String key = KEY_PRE + key;
    String command = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
    if (1L.equals(jedis.eval(command, Collections.singletonList(key), Collections.singletonList(value)))) {
        return true;
    }

    另外,判断是否自己持有锁的机制是用加锁的时候的 key-value 来判断当前的 key 的值是否等于自己持有锁时获得的值。所以加锁的时候的 value 必须是一个全局唯一的字符串。

    完整的代码如下所示

    从测试结果来看可以看到,9个线程同时给一个 key 加锁,只有一个能够成功获取到锁,其余的客户端都无法获取到锁。

    文中代码

    https://github.com/gaochao2000/redis_util

  • 相关阅读:
    XmlSerializer .NET 序列化、反序列化
    XmlSerializer .NET 序列化、反序列化
    XmlSerializer .NET 序列化、反序列化
    细数那些你可能不知道的国产数据库
    Python爬虫入门教程 13-100 斗图啦表情包多线程爬取
    基于超大规模集群的本地存储系统优化
    BI分析工具深度测评:FineBI vs Tableau
    Python爬虫入门教程 12-100 半次元COS图爬取
    celery 配置
    Java基础之int和Integer有什么区别
  • 原文地址:https://www.cnblogs.com/sxw123/p/13851429.html
Copyright © 2020-2023  润新知