• Laravel Redis分布式锁实现源码分析


    首先是锁的抽象类,定义了继承的类必须实现加锁、释放锁、返回锁拥有者的方法。

    namespace IlluminateCache;
    
    abstract class Lock implements LockContract
    {
        use InteractsWithTime;
    
        // 锁的名称
        protected $name;
    
        // 锁的时长
        protected $seconds;
    
        // 当前操作锁的拥有者
        protected $owner;
    
        // 获取锁失败时,重新获取锁需要等待的毫秒数
        protected $sleepMilliseconds = 250;
    
        // 构造函数
        public function __construct($name, $seconds, $owner = null)
        {
            if (is_null($owner)) {
                $owner = Str::random();
            }
    
            $this->name = $name;
            $this->owner = $owner;
            $this->seconds = $seconds;
        }
    
        // 加锁
        abstract public function acquire();
    
        // 释放锁
        abstract public function release();
    
        // 获取锁中保存的拥有者信息
        abstract protected function getCurrentOwner();
    
        // 1. 尝试获取锁,并返回获取结果
        // 2. 尝试获取锁,获取成功后执行一个回调函数,执行完成后自动释放锁
        public function get($callback = null)
        {
            $result = $this->acquire();
    
            if ($result && is_callable($callback)) {
                try {
                    return $callback();
                } finally {
                    $this->release();
                }
            }
    
            return $result;
        }
    
        // 尝试在指定的时间内获取锁,超时则失败抛出异常
        public function block($seconds, $callback = null)
        {
            $starting = $this->currentTime();
    
            while (! $this->acquire()) {
                usleep($this->sleepMilliseconds * 1000);
    
                if ($this->currentTime() - $seconds >= $starting) {
                    throw new LockTimeoutException;
                }
            }
    
            if (is_callable($callback)) {
                try {
                    return $callback();
                } finally {
                    $this->release();
                }
            }
    
            return true;
        }
    
        // 返回当前操作锁的拥有者
        public function owner()
        {
            return $this->owner;
        }
    
        // 判断当前操作的拥有者是否为锁中保存的拥有者
        protected function isOwnedByCurrentProcess()
        {
            return $this->getCurrentOwner() === $this->owner;
        }
    
        // 设置重试获取锁需要等待的毫秒数
        public function betweenBlockedAttemptsSleepFor($milliseconds)
        {
            $this->sleepMilliseconds = $milliseconds;
            return $this;
        }
    }
    

    Redis 锁实现类,增加了强制删除锁的方法。

    class RedisLock extends Lock
    {
        // Redis对象
        protected $redis;
    
        // 构造函数
        public function __construct($redis, $name, $seconds, $owner = null)
        {
            parent::__construct($name, $seconds, $owner);
            $this->redis = $redis;
        }
    
        // 加锁逻辑代码
        public function acquire()
        {
            if ($this->seconds > 0) {
                return $this->redis->set($this->name, $this->owner, 'EX', $this->seconds, 'NX') == true;
            } else {
                return $this->redis->setnx($this->name, $this->owner) === 1;
            }
        }
    
        // 使用 Lua 脚本释放锁逻辑代码
        public function release()
        {
            return (bool) $this->redis->eval(LuaScripts::releaseLock(), 1, $this->name, $this->owner);
        }
    
        // 无视锁的拥有者强制删除锁
        public function forceRelease()
        {
            $this->redis->del($this->name);
        }
    
        // 返回锁中保存的拥有者信息
        protected function getCurrentOwner()
        {
            return $this->redis->get($this->name);
        }
    }
    

    原子性释放锁的 Lua 脚本。

    class LuaScripts
    {
        /**
         * 使用 Lua 脚本原子性地释放锁.
         *
         * KEYS[1] - 锁的名称
         * ARGV[1] - 锁的拥有者,只有是该锁的拥有者才允许释放
         *
         * @return string
         */
        public static function releaseLock()
        {
            return <<<'LUA'
    if redis.call("get",KEYS[1]) == ARGV[1] then
        return redis.call("del",KEYS[1])
    else
        return 0
    end
    LUA;
        }
    }
    

    总结:

    1. 可以通过get()方法直接获取锁并传入回调函数在成功时执行。
    2. 可以通过block()方法在指定时间内不断获取锁,知道成功或超时为止,成功时会执行传入的回调函数。
    3. Redis 通过 set() 命令设置一个值为“拥有者”的字符串来作为锁。
    4. set() 通过 NX 参数来实现排他锁(只在键不存在时,才对键进行设置)。
    5. set() 通过 EX 参数来控制锁的生存时间(防止程序意外终止发生死锁)。
    6. 不能使用 set()+expire() 来代替set(),防止网络延迟或其他故障导致死锁。
    7. Redis 通过 Lua 脚本来达到原子性删除锁。
    8. Lua 脚本中会判断字符串的内容是否与参数中的拥有者一致,一致才执行删除操作。防止当前锁被其他进程误删除,或者误删除了其他进程的锁。
  • 相关阅读:
    ES6扩展
    javascript当中prototype用法
    三列浮动中间宽度自适应
    介绍axios和promise
    JS高级---实例对象和构造函数之间的关系
    JS-数组常用方法整理
    原生JS轮播图
    JavaScript 开胃菜
    JS高级---遍历DOM树
    JS高级---正则表达式练习身份证号码
  • 原文地址:https://www.cnblogs.com/danhuang/p/13232056.html
Copyright © 2020-2023  润新知