• 使用Redis实现分布式锁


    1.setnx
    锁在redis中最简单的数据结构就是string。最早的时候,上锁的操作一般使用setnx,这个命令是当:lock不存在的时候set一个val,或许你还会记得使用expire来增加锁的过期,解锁操作就是使用del命令,伪代码如下:
    if (Redis::setnx("my:lock", 1)) {
      Redis::expire("my:lock", 10);
      // ... do something

      Redis::del("my:lock")
    }
    这里其实是有问题的,问题就在于setnx和expire中间如果遇到crash等行为,可能这个lock就不会被释放了。

    2.set
    现在官方建议直接使用set来实现锁。我们可以使用set命令来替代setnx,就是下面这个样子
    if (Redis::set("my:lock", 1, "nx", "ex", 10)) {
      ... do something

      Redis::del("my:lock")
    }
    上面的代码把my:lock设置为1,当且仅当这个lock不存在的时候,设置完成之后设置过期时间为10。
    获取锁的机制是对了,但是删除锁的机制直接使用del是不对的。因为有可能导致误删别人的锁的情况。比如,这个锁我上了10s,但是我处理的时间比10s更长,到了10s,这个锁自动过期了,被别人取走了,并且对它重新上锁了。那么这个时候,我再调用Redis::del就是删除别人建立的锁了。
    官方对解锁的命令也有建议,建议使用lua脚本,先进行get,再进行del

    主控:
    $token = rand(1, 100000);

    function lock() {
      return Redis::set("my:lock", $token, "nx", "ex", 10);
    }

    function unlock() {
      $script = `
      if redis.call("get",KEYS[1]) == ARGV[1]
      then
        return redis.call("del",KEYS[1])
      else
        return 0
      end 
      `
      return Redis::eval($script, "my:lock", $token)
    }

    if (lock()) {
      // do something

      unlock();
    }

    redis工具类:

    复制代码
    public class RedisTool {
    
        private static final String LOCK_SUCCESS = "OK";
        private static final String SET_IF_NOT_EXIST = "NX";
        private static final String SET_WITH_EXPIRE_TIME = "PX";
    
        private static final Long RELEASE_SUCCESS = 1L;
    
        /**
         * 尝试获取分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @param expireTime 超期时间
         * @return 是否获取成功
         */
        public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    
            String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
    
            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
            return false;
    
        }
    
    
        /**
         * 释放分布式锁
         * @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;
    
        }
    
    
    }
    复制代码

    其他:lua脚本的原子性:
    Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难,因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心,因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。

  • 相关阅读:
    关于时间
    ELK日志平台
    Java web项目
    阳光下的挣扎
    Windows Server 2008 R2无法连接无线网络的解决方法
    像进度条的网页加载Loading JS代码
    JAVASCRIPT网页上下切换的打开特效
    霓虹灯文字代码 JS网页特效
    Js代码动态移动层-拖动浮层并关闭
    JS网页特效:星空飞入效果
  • 原文地址:https://www.cnblogs.com/tinyj/p/10023615.html
Copyright © 2020-2023  润新知