• php+redis+lua实现分布式锁


    以下是我在工作中用到的类,redis加锁两种方式,解锁为了保证原子性所以只用lua+redis的方式

    缺陷:虽然死锁问题解决了,但业务执行时间超过锁有效期还是存在多客户端加锁问题。
    不过,这个类已经满足了我现在的业务需求

    更优的解决方案可以参考以下两篇文章:
    https://redis.io/topics/distlock (Redlock的算法描述)
    https://mp.weixin.qq.com/s/1bPLk_VZhZ0QYNZS8LkviA

    相关文章  redis 分布式锁的 5个坑,真是又大又深

    Redlock-php 实现了分布式锁

    主从复制 用红锁解决

    代码实现:

    class RedisLock
    {
        /**
         * @var 当前锁标识,用于解锁
         */
        private $_lockFlag;
    
        private $_redis;
    
        public function __construct($host = '127.0.0.1', $port = '6379', $passwd = '')
        {
            $this->_redis = new Redis();
            $this->_redis->connect($host, $port);
            if ($passwd) {
                $this->_redis->auth($passwd);
            }
        }
    
        public function lock($key, $expire = 5)
        {
            $now= time();
            $expireTime = $expire + $now;
            if ($this->_redis->setnx($key, $expireTime)) {
                $this->_lockFlag = $expireTime;
                return true;
            }
    
            // 获取上一个锁的到期时间
            $currentLockTime = $this->_redis->get($key);
            if ($currentLockTime < $now) {
                /* 用于解决
                C0超时了,还持有锁,加入C1/C2/...同时请求进入了方法里面
                C1/C2都执行了getset方法(由于getset方法的原子性,
                所以两个请求返回的值必定不相等保证了C1/C2只有一个获取了锁) */
                $oldLockTime = $this->_redis->getset($key, $expireTime);
                if ($currentLockTime == $oldLockTime) {
                    $this->_lockFlag = $expireTime;
                    return true;
                }
            }
    
            return false;
        }
    
        public function lockByLua($key, $expire = 5)
        {
            $script = <<<EOF
    
                local key = KEYS[1]
                local value = ARGV[1]
                local ttl = ARGV[2]
    
                if (redis.call('setnx', key, value) == 1) then
                    return redis.call('expire', key, ttl)
                elseif (redis.call('ttl', key) == -1) then
                    return redis.call('expire', key, ttl)
                end
    
                return 0
    EOF;
    
            $this->_lockFlag = md5(microtime(true));
            return $this->_eval($script, [$key, $this->_lockFlag, $expire]);
        }
    
        public function unlock($key)
        {
            $script = <<<EOF
    
                local key = KEYS[1]
                local value = ARGV[1]
    
                if (redis.call('exists', key) == 1 and redis.call('get', key) == value) 
                then
                    return redis.call('del', key)
                end
    
                return 0
    
    EOF;
    
            if ($this->_lockFlag) {
                return $this->_eval($script, [$key, $this->_lockFlag]);
            }
        }
    
        private function _eval($script, array $params, $keyNum = 1)
        {
            $hash = $this->_redis->script('load', $script);
            return $this->_redis->evalSha($hash, $params, $keyNum);
        }
    
    }
    
    $redisLock = new RedisLock();
    
    $key = 'lock';
    if ($redisLock->lockByLua($key)) {
        // to do...
        $redisLock->unlock($key);
    }
  • 相关阅读:
    .Net Core AOP之IResultFilter
    .Net Core AOP之IExceptionFilter
    Linux部署.Net Core应用
    .Net Core Aop之IActionFilter
    计算虚拟化技术
    正则表达式通配符 ? 和 * 的区别
    linux中rar文件的压缩、解压
    linux 系统中 双引号“”和单引号‘’转义的区别
    linux 中find命令的用法
    ubuntu中出现:passwd: unrecognized option 'stdin'
  • 原文地址:https://www.cnblogs.com/xiangshihua/p/15008013.html
Copyright © 2020-2023  润新知