redis 锁 – 高并发抢购防止商品超卖与重复虚拟商品密钥


<?php

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

    private $quorum;

    private $servers = array();
    private $instances = array();

    function __construct(array $servers = array(), $retryDelay = 200, $retryCount = 3)
    {
        $redis_conf = $servers&&$servers!=array()?$servers:C('MY_REDIS');
        $ip = $redis_conf['ip'];
        $port = $redis_conf['port'];
        $auth = $redis_conf['auth'];
        $ip = $ip?$ip:'127.0.0.1';
        $port = $port?$port:6379;
        $auth = $auth?$auth:false;
        $servers = [
            [$ip, $port, 0.01,$auth],
        ];

        $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++;
                }
            }


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

            $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,$auth) = $server;
                $redis = new \Redis();
                $redis->connect($host, $port, $timeout);
                if($auth){
                    $redis->auth($auth);
                }

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

}

调用

$redLock = new RedLock();
$lock = $redLock->lock('key', 10000);
if ($lock) {
 抢购操作
}else{}
,

《“redis 锁 – 高并发抢购防止商品超卖与重复虚拟商品密钥”》 有 3 条评论

  1. This is a very good tip especially to those fresh to the blogosphere. Simple but very accurate information?Thanks for sharing this one. A must read post! Gretta Cammy Holt

  2. Wonderful story, reckoned we could combine several unrelated information, nevertheless definitely really worth taking a appear, whoa did 1 discover about Mid East has got a lot more problerms as well Edi Mattheus Wenonah

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注