zoukankan      html  css  js  c++  java
  • 利用 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

  • 相关阅读:
    Allegro PCB Design GXL (legacy) 使用slide无法将走线推挤到焊盘的原因
    OrCAD Capture CIS 16.6 导出BOM
    Altium Designer (17.0) 打印输出指定的层
    Allegro PCB Design GXL (legacy) 将指定的层导出为DXF
    Allegro PCB Design GXL (legacy) 设置十字大光标
    Allegro PCB Design GXL (legacy) 手动更改元器件引脚的网络
    magento产品导入时需要注意的事项
    magento url rewrite
    验证台湾同胞身份证信息
    IE8对css文件的限制
  • 原文地址:https://www.cnblogs.com/sxw123/p/13851429.html
Copyright © 2011-2022 走看看