什么是分布式锁?当不同的进程必须以互斥地方式访问同一个共享资源时,就要用到分布式锁。当然,我更建议在工程实践时合理设计方案,避免用到锁,除非无法避免。网上也有一些比较简单的设计方案,其可靠性往往得不到很好的保证。
分布式锁的基本要求
一个最小化、可有效使用的分布式锁至少需满足以下三个属性:
安全性:互斥。对于同一资源,在任何时刻,只有一个客户端可以持有锁。
活跃性 A:死锁释放。当持有锁的客户端发生崩溃等异常而不能释放锁时,锁最终也能被其它客户端获取到。
活跃性 B:容错。只要大多数(半数以上)Redis 节点处于启动状态,客户端就可以获取和释放锁。
不能满足以上三个属性,则不是一个合格的分布式锁方案,其可靠性不足以在生产环境使用。在选择分布式锁方案时要牢记这三点。
redlock地址:https://github.com/topics/redlock
首先使用 composer 导入对应包:
composer require rtckit/react-redlock
第一种实现方式:
public function buy()
{
/** @var Factory $factory 初始化一个Redis实例*/
$factory = new \Clue\React\Redis\Factory();
$client = $factory->createLazyClient('127.0.0.1');
/** @var Custodian $custodian 初始化一个锁监听器*/
$custodian = new \RTCKit\React\Redlock\Custodian($client);
$custodian->acquire('resource', 60, 'r4nd0m_token')
->then(function (?Lock $lock) use ($custodian) {
if (is_null($lock)) {
// 获取锁失败
} else {
// 添加一个10s生命周期的锁
// TODO 处理业务逻辑
// 释放锁
$custodian->release($lock);
}
});
}
该方式使用 Redis 的 set + nx 命令实现原子性加锁,然后给当前加的锁设置一个随机的字符串,用来处理释放当前锁时,不能去释放他人的锁。调用了一个 lua 脚本 release 释放锁。保证锁的释放是一个原子性的。
第二种实现方式:
public function buy()
{
/** @var Factory $factory 初始化一个Redis实例*/
$factory = new \Clue\React\Redis\Factory();
$client = $factory->createLazyClient('127.0.0.1');
/** @var Custodian $custodian 初始化一个锁监听器*/
$custodian = new \RTCKit\React\Redlock\Custodian($client);
$custodian->spin(100, 0.5, 'resource', 10, 'r4nd0m_token')
->then(function (?Lock $lock) use ($custodian) : void {
if (is_null($lock)) {
// 将进行100次的场次,每一次间隔0.5秒去获取锁,如果没有获取到锁。则放弃加锁请求。
} else {
// 添加一个10s生命周期的锁
// TODO 处理业务逻辑
// 释放锁
$custodian->release($lock);
}
});
}
该方式加了一个自旋锁。在一定时间内一直获取锁,如果没有获取到则放弃当前的请求。