Share the PHP implementation of distributed lock RedLock based on Redis, deepen the understanding of distributed lock RedLock principle.


      

class RedLock
{
    private $retryDelay;
    private $retryCount;
    private $clockDriftFactor = 0.01;

    private $quorum;

    private $servers = array(a);private $instances = array(a);function __construct(array $servers.$retryDelay = 200.$retryCount = 3)
    {
        $this->servers = $servers;

        $this->retryDelay = $retryDelay;
        $this->retryCount = $retryCount;

        $this->quorum  = min(count($servers), (count($servers) / 2 + 1));
    }

    public function lock($resource.$ttl)
    {
        $this->initInstances();

        $token = uniqid();
        $retry = $this->retryCount;

        do {
            $n = 0;

            $startTime = microtime(true) * 1000;

            foreach ($this->instances as $instance) {
                if ($this->lockInstance($instance.$resource.$token.$ttl)) {
                    $n++;
                }
            }

            # Add 2 milliseconds to the drift to account for Redis expires
            # precision, which is 1 millisecond, plus 1 millisecond min drift
            # for small TTLs.
            $drift = ($ttl * $this->clockDriftFactor) + 2;

            $validityTime = $ttl - (microtime(true) * 1000 - $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); }}// Wait a random delay before to retry
            $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); }}Copy the code

Redlock algorithm is a high availability mode introduced by Antirez on the basis of single Redis node. In the distributed environment of Redis, we assume that there are N completely independent Redis nodes, and locks are acquired and released on N Instances of Redis using the same method as in the single instance of Redis. Now assume that there are 5 primary Redis nodes (an odd number greater than 3), so that it is almost guaranteed that they will not all go down at the same time. During lock acquisition and lock release, the client will do the following:

  • 1. Obtain the current Unix time, in milliseconds
  • When requesting a lock from Redis, the client should set a timeout time for network connection and response. The timeout time should be shorter than the expiration time of the lock, so as to avoid client death
  • 3. The client obtains the lock usage time by subtracting the start time from the current time. The lock is successful if and only if the lock is fetched from more than half of the Redis nodes and is used for less than the lock expiration time
  • 4. If a lock is obtained, it is important that the key’s actual lifetime is equal to the lifetime minus the time used to acquire the lock
  • 5. If for some reason, failed to get the lock (no more than half in the instance to lock or lock time was more than effective time), the client should be conducted on all instances of Redis unlock, whether Redis instance lock is successful, because may be the server response message lost but actually succeeded, released, after all, there will be no problem at a time

Author: Back-end technical compass Link: juejin.cn/post/684490… The copyright belongs to the author. Commercial reprint please contact the author for authorization, non-commercial reprint please indicate the source.