php+redis+lua实现分布式锁

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

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

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

代码实现:

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);
}

 

php+redis+lua实现分布式锁

上一篇:如何在 Node.js 中流式处理大 JSON 文件


下一篇:jQuery从入门到放弃(三)