陈大剩博客

Redis 实现分布式锁

  • 陈大剩
  • 2022-10-04 00:08:05
  • 1018

分布式锁介绍

分布式锁,主要考察使用者对原子性的理解,原子性可以保证程序从异常中恢复后,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 等去实现

分享到:
0

说点儿什么吧

头像

表情

本站由陈大剩博客程序搭建 | 湘ICP备2023000975号| Copyright © 2017 - 陈大剩博客 | 本站采用创作共用版权:CC BY-NC 4.0

站长统计| 文章总数[115]| 评论总数[9]| 登录用户[22]| 时间点[119]

logo

登入

社交账号登录