多線程狀況下訪問一些共享資源須要加鎖,不然就會致使數據錯亂的問題
分佈式鎖能夠經過DB,Redis,Zk等方式實現,本節主要介紹php使用Redis實現分佈式鎖
setnx key value 設置一個值,當key已經存在時,返回flase,表明失敗
使用setnx實現分佈鎖有個缺陷,setnx操做沒法設置key的ttl,須要配合exprie key ttl 一塊兒使用
好在set命令就集成了nx和ex操做set key name NX PX 10000
$redis = new Redis(); $redis->connect('127.0.0.1', 6380); $rs = $redis->set('testnx', 123, ['nx', 'ex' => 10]); var_dump($rs);//返回true表明加鎖成功,返回false表明加鎖失敗
set命令還有一個問題,當你要提早釋放這個鎖的時候,使用expire key 0或者使用del key
若是expire或者del命令發送了阻塞,鎖自動失效,這時候B獲取了鎖,expire/del命令到達,致使B獲取的鎖失效
Redlock在加鎖的時候value值要保證惟一性,在釋放鎖的時候要驗證value是否和申請鎖時value是否一致
class RedLock { private $retryDelay; private $retryCount; private $clockDriftFactor = 0.01; private $quorum; private $servers = array(); private $instances = array(); 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); } } $servers = [ ['127.0.0.1', 6379, 0.01], ]; $redLock = new RedLock($servers); while (true) { $lock = $redLock->lock('test', 10000); if ($lock) { print_r($lock); $redLock->unlock(['resource' => 'test', 'token' => '5d1c123121538']); } else { print "Lock not acquired\n"; } }
Redis分佈式鎖還有沒有問題?
解決方法:引入版本號php
參考:https://time.geekbang.org/col...redis