php+redis+lua实现分布式锁,转载

以下是我在工作中用到的类,redis加锁两种方式,解锁为了保证原子性所以只用lua+redis的方式

缺陷:虽然死锁问题解决了,但业务执行时间超过锁有效期还是存在多客户端加锁问题。

不过,这个类已经满足了我现在的业务需求

更优的解决方案可以参考以下两篇文章:

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 = <<<EOF

local key = KEYS[1]

local value = ARGV[1]

local ttl = ARGV[2]

if (redis.call(\'setnx\', key, value) == 1) then

return redis.call(\'expire\', key, ttl)

elseif (redis.call(\'ttl\', key) == -1) then

return redis.call(\'expire\', key, ttl)

end

return 0

EOF;

$this->_lockFlag = md5(microtime(true));

return $this->_eval($script, [$key, $this->_lockFlag, $expire]);

}

public function unlock($key)

{

$script = <<<EOF

local key = KEYS[1]

local value = ARGV[1]

if (redis.call(\'exists\', key) == 1 and redis.call(\'get\', key) == value)

then

return redis.call(\'del\', key)

end

return 0

EOF;

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

}