php+redis+lua分散ロックを実現

7053 ワード

以下は私が仕事で使っているクラス、redisロックの2つの方法で、ロックを解除するのは原子性を保証するためにlua+redisだけの方法です.
欠陥:デッドロックの問題は解決しましたが、ビジネスの実行時間がロックの有効期間を超えても、マルチクライアントのロックの問題があります.しかし、このクラスは私の現在のビジネスニーズを満たしています.
より優れたソリューションについては、次の2つの記事を参照してください.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 = <<

        $this->_lockFlag = md5(microtime(true));
        return $this->_eval($script, [$key, $this->_lockFlag, $expire]);
    }

    public function unlock($key)
    {
        $script = <<

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