2
<?php
namespace Service;

class Redlock extends Base
{

    private $retryDelay;
    private $retryCount;
    private $clockDriftFactor = 0.01;
    private $quorum;
    private $servers = array();
    private $instances = array();

    public function init(array $servers, $retryDelay = 200, $retryCount = 3)
    {
        $this->servers = $servers;
        $this->retryDelay = $retryDelay;
        $this->retryCount = $retryCount;
        $this->quorum  = min(count($servers), (count($servers) / 2 + 1));
        return $this;
    }

    private function getTime()
    {
        return microtime(true) * 1000;
    }

    public function lock($resource, $ttl)
    {
        $this->initInstances();
        $token = uniqid();
        $retry = $this->retryCount;
        do {
            $n = 0;
            $startTime = $this->getTime();
            foreach($this->instances as $instance){
                if($this->lockInstance($instance, $resource, $token, $ttl)){
                    $n++;
                }
            }
            $drift = ($ttl * $this->clockDriftFactor) + 2;
            $validityTime = $ttl - ($this->getTime() - $startTime) - $drift;
            if($n >= $this->quorum && $validityTime > 0){
                return [
                    'validity' => $validityTime,
                    'resource' => $resource,
                    'token'    => $token
                ];

            }
            else{
                foreach($this->instances as $instance){
                    $this->unlockInstance($instance, $resource, $token);
                }
            }
            $delay = mt_rand(floor($this->retryDelay / 2), $this->retryDelay);
            usleep($delay * 1000);
            $retry--;

        }
        while($retry > 0);
        return false;
    }

    public function unlock(array $lock)
    {
        $this->initInstances();
        $resource = $lock['resource'];
        $token    = $lock['token'];
        foreach($this->instances as $instance){
            $this->unlockInstance($instance, $resource, $token);
        }
    }

    private function initInstances()
    {
        if(empty($this->instances)){
            foreach($this->servers as $server){
                list($host, $port, $timeout) = $server;
                $redis = new \Redis();
                $redis->connect($host, $port, $timeout);
                $this->instances[] = $redis;
            }
        }
    }

    private function lockInstance($instance, $resource, $token, $ttl)
    {
        return $instance->set($resource, $token, ['NX', 'PX' => $ttl]);
    }

    private function unlockInstance($instance, $resource, $token)
    {
        $script = '
            if redis.call("GET", KEYS[1]) == ARGV[1] then
                return redis.call("DEL", KEYS[1])
            else
                return 0
            end
        ';
        return $instance->eval($script, [$resource, $token], 1);
    }

}

使用方法

/*
    /usr/bin/php -d display_error /app/cli.php local Demo.redlock
*/
public function redlock()
{
    $servers = [
        ['192.168.21.16', 6379, 0.01]
    ];
    $redlock = $this->get(SERVICE_REDLOCK)->init($servers);
    $lock = $redlock->lock('resource_name', 1000);
    print_r($lock);
    $redlock->unlock($lock);
}

xxfaxy
1.6k 声望18 粉丝