php使用redis分布式锁,php 实现Redis分布式锁

本文介绍了在多线程环境下如何利用Redis实现分布式锁,详细阐述了使用setnx命令的局限性,并提出了通过结合set命令的nx和ex选项来改进。同时,文章还讲解了Redlock的概念,包括其加锁、解锁过程,以及解决锁提前释放可能导致的问题。最后,文章提及了引入版本号作为进一步优化分布式锁的方法。
摘要由CSDN通过智能技术生成

简介

多线程情况下访问一些共享资源需要加锁,否则就会导致数据错乱的问题

分布式锁可以通过DB,Redis,Zk等方式实现,本节主要介绍php使用Redis实现分布式锁

set命令

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代表加锁失败

Redlock

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分布式锁还有没有问题?

913d618bcc1cedd2e509b640de8fb1dd.png

解决方法:引入版本号

24510323229d478752286d25d7fad01a.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值