Redis 实现分布式锁
- 陈大剩
- 2022-10-04 00:08:05
- 956
分布式锁介绍
分布式锁,主要考察使用者对原子性的理解,原子性可以保证程序从异常中恢复后,redis中的数据是正确的,程序依然正常运行。分布式锁是实现线程同步手段之一。
分布式锁原理
分布式锁其实就是在进程里占一个“坑”,当别的进程来占坑时,发现那里已经有一根“大萝卜”了,就只好放弃或者稍后再试。占“坑”就是把所有的逻辑变成单进程来执行。
Redis 实现分布式锁
实现分布式锁,三个版本进行演变,最终达到完美的分布式锁功能。
基础版本
require_once("../RedisClient.php");
$client = RedisClient::getInstance();
/**
* 基础版,完成锁的请求与释放,但是有严重的问题.
* 如果请求锁完成后,宕机了,就成死锁了.再也不能请求到锁了
* @return null
*/
function base () {
global $client;
//请求锁
$lock = $client->set('lock','true');
if(!$lock){
return null;
}
// do something critical ...
//释放锁
$client->del('lock');
}
存在的问题
如果在释放锁前,服务器宕机了,那么我们永远都无法重新申请锁,就成死锁了。我们可以利用redis 的 set
命令给锁设置一个过期时间,服务器宕机了,锁过一段时间也会重新释放。
过期时间随机数版
很多同学会有疑问为什么需要添加随机数,不妨静下心仔细想想,如果我们给的时间过期了,锁不存在了,刚好另一个进程站用了这个“坑”,那我们是不是删错了呢?
require_once("../RedisClient.php");
$client = RedisClient::getInstance();
/**
* 设置锁和添加过期时间放在一个命令中,要成功一起成功
* 添加随机数,防止过期时间后,删除了其他进程的锁
* @return null
*/
function perfect () {
global $client;
$unique = uniqid();
//请求锁并添加过期时间
$lock = $client->set('lock',$unique,'ex',60,'nx');
if(!$lock){
return null;
}
// do something critical ...
//释放锁
if ($client->get('lock')==$unique){
$client->del('lock');
};
}
存在的问题
由于 Redis
没有提供带条件删除的命令,需我们手动去匹配随机数,在进行了锁匹配中,有可能锁刚匹配完,本进程锁的过期时间到了,系统自动删除。而另一个进程已经申请了锁,我们命令删除另一个进程的锁,造成脏数据。这事我们需要用到 Lua
脚本进行原子性执行。
Lua 脚本版
use Predis\Command\ScriptCommand;
require_once("../RedisClient.php");
$client = RedisClient::getInstance();
class releaseLockScript extends ScriptCommand {
public function getScript()
{
return <<<LUA
if redis.call("get",ARGV[1]) == ARGV[2] then
return redis.call("del",ARGV[1])
else
return 0
end
LUA;
}
}
// 定义锁命令
$client->getProfile()->defineCommand('releaseLock','releaseLockScript');
function lua () {
global $client;
$unique = uniqid();
//请求锁并添加过期时间
$lock = $client->set('lock',$unique,'ex',60,'nx');
if(!$lock){
return null;
}
// do something critical ...
//释放锁
$client->releaseLock('lock',$unique)
}
存在的问题
当然这个也不是一个完美的方案,只是相对安全一点,如果真的超时了。其他逻辑还没有执行完,其他线程也会趁虚而入。
分布式锁的缺点
- 分布式锁一旦加了之后,对同一个商品的下单请求,会导致所有客户端都必须对同一个商品的库存锁key进行加锁。这样会导致对同一个商品的下单请求,就必须串行化,一个接一个的处理。
- 不适合执行较长逻辑的代码的请求。
拓展
分布式锁是一种思想,除了用 Redis
实现分布式锁外,也可以试着用:Mysql、Memcache、Zookeeper 等去实现