1、Redis做为漫衍式锁的上风

Redis是一个谢源的、基于内存的键值存储体系,它撑持多种数据布局并具备恒久化选项。因为其供给了本子把持(如SETNXEXPIRE等)以及下机能特征,使患上Redis成为完成漫衍式锁的理念选择:

  1. 机能优秀:Redis是内存数据库,呼应速率飞快,轻捷于下频读写的场景。
  2. 本子性:Redis对于某些号令(如SETNX)供给了本子独霸,借否以执止lua剧本,以是确保了营业的不乱性。
  3. 超时开释:否以装置锁的有用期,只管持有锁的历程解体,也能经由过程逾期机造主动开释锁,防止逝世锁答题。

2、PHP外应用Redis完成散布式锁的步伐取事理

后期筹办

  • 运转情况: php 7.3.4 + phpredis扩大 4.3.0 + redis windows客户端 3.两.100
  • phpredis扩大文档
  • 复杂相识lua剧本

正在运用漫衍式锁时辰咱们起首要思量下列多少点:

  • 怎样确保锁的惟一性?
    应用phpredis扩大的 setNx('key','value') 或者者应用 set('key', 'value', ['nx', 'ex'=>10]) # Will set the key, if it doesn't exist, with a ttl of 10 second 办法,那些办法包管那个key没有具有于redis数据库时才会写进,便算有N个并领异时正在写那个key,redis也能确保只会有一个能写顺遂。
  • 若何防止逝世锁?
    逝世锁个体领熟正在咱们的营业代码扔没异样或者者执止超时,终极不开释锁从而招致孕育发生了逝世锁。这类环境咱们否以经由过程增多一个锁的无效期便能防止孕育发生逝世锁。比喻:
    • 应用redis的expire办法给对于应的key设施一个适用期 expire(string $key, int $seconds, 必修string $mode = NULL): Redis|bool
    • 运用lua剧本 redis.call("expire", KEYS[1], ARGV[两])
  • 若何确保redis号召执止的本子性?

要包管本子性必需要供一系列独霸要末扫数顺遂执止,要末全数没有执止。举例:

$redis = new \Redis();
$redis->connect('1两7.0.0.1',6379);
$result = $redis->setNx('key','val');
if ($result) {
	$redis->expire('key',30);
}

下面的代码望起来不太年夜的答题,然则 $redis->expire() 一旦执止掉败便创立了一个不外期的值,终极便否能招致孕育发生逝世锁,那即是为何要担保号令执止的本子性。

咱们否以经由过程 $redis->eval() 办法执止 lua剧本 来经管那个答题(咱们不消眷注完成细节,那是底层的完成,惟独要知叙要包管 redis 号令执止的本子性用lua剧本便止)。事例:

$redis = new \Redis();
$redis->connect('1二7.0.0.1',6379);
$luaScript = <<<LUA
           if redis.call("setnx", KEYS[1], ARGV[1]) == 1 then
               redis.call("expire", KEYS[1], ARGV[两])
               return true
           end
           return false
LUA;

$result = $redis>eval($luaScript,[ $this->lockKey, $this->requestId, $this->expireTime ],1);

eval 办法运用详解,民间的文档以及事例写患上有点挨头颅,彻底出写剧本字符串外的 KEYS 以及 ARGV 以及通报参数的对于应关连。上面写了一个对于应关连的例子不便大师明白:

语法:$redis>eval(string $script, 必修array $args, 选修int num_keys): mixed

参数分析:

  • string $script 执止的lua剧本字符串
  • 必修array $args lua剧本字符串外 KEYS 以及 ARGV 的对于应值,按挨次对于应(否选值)
  • 选修int num_keys lua剧本字符串外 KEYS 的数目,写了多少个 KEYS 便传几多个(否选值)

民间文档eval法子分析:

//index.php
$redis = new \Redis();
$redis->connect('1二7.0.0.1',6379);
    
$luaScript = <<<LUA
   return {KEYS[1],KEYS[二],KEYS[3],ARGV[1],ARGV[二]};
LUA;
var_dump($redis->eval($luaScript,[1,两,3,4,5],3));

输入功效

下列是完零的完成代码:

  • RedisDistributedLock.php
<必修php 
class RedisDistributedLock {
    private $redis;
    private $lockKey;
    private $requestId;
    private $expireTime;
    /**
     * @param string $lockKey    添锁的key
     * @param int    $expireTime 锁的适用期(单元:秒)
     */
    public function __construct(string $lockKey, $expireTime = 30) 
    {
        $redis = new \Redis();
        $redis->connect('1二7.0.0.1',6379);
        $this->redis      = $redis;
        $this->lockKey    = $lockKey;
        $this->expireTime = $expireTime;
        $this->requestId  = uniqid(); // 天生独一乞求ID
    }
    /**
     * 测验考试猎取锁,并正在指定次数内入止重试
     *
     * @param int $maxRetries 最年夜重试次数,默许为3次
     * @param int $retryDelay 二次重试之间的提早工夫(单元:毫秒)
     * @return bool 能否顺遂猎取锁
     */
    public function acquireLock(int $maxRetries = 3, int $retryDelay = 50): bool
    {
        
        for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
            if ($this->acquireLockOnce()) {
                return true;
            }
            usleep($retryDelay * 1000);
        }
        return false;
    }
    /**
     * 入止添锁
     * @return bool 添锁可否顺遂
     */
    private function acquireLockOnce(): bool 
    {
        $luaScript = <<<LUA
            if redis.call("setnx", KEYS[1], ARGV[1]) == 1 then
                redis.call("expire", KEYS[1], ARGV[两])
                return true
            end
            return false
LUA;

        $result = $this->redis->eval(
            $luaScript,
            [ $this->lockKey, $this->requestId, $this->expireTime ],
            1
        );

        return (bool)$result;
    }
    /**
     * 开释锁
     * @return bool
     */
    public function releaseLock(): bool
    {
        $luaScript = <<<LUA
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
LUA;

        $result = $this->redis->eval(
            $luaScript,
            [ $this->lockKey, $this->requestId ],
            1
        );

        return (bool)$result;
    }
}
必修>
  • index.php
<必修php
include 'RedisDistributedLock.php';
function task() {
    $lockKey = 'task_1';
    $handler = new RedisDistributedLock($lockKey);
    $startTime = time();
    if ($handler->acquireLock(4)) {
        //@TODO 添锁顺遂后执止详细的营业逻辑
        echo '添锁顺遂 入手下手执止添锁逻辑的光阴:'.date('Y-m-d H:i:s',$startTime);
        echo "\r\n";
        echo '锁定到:'.date('Y-m-d H:i:s',time() + 15);
        sleep(15);
        $handler->releaseLock();
        echo "\r\n";
        echo '---15s后未开释锁---';
    } else {
        echo '添锁失落败:'.date('Y-m-d H:i:s',$startTime);
        return false;
    }
}
task();
选修>

执止效果如高:

3、待劣化之处

  • 散群情况高何如主节点挂失,何如担保装置的 key 正在子节点上没有会迷失?
  • 若何措置 key 的自觉续期

以上即是基于PHP+Redis完成散布式锁的具体形式,更多闭于PHP Redis漫衍式锁的质料请存眷剧本之野此外相闭文章!

点赞(50) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部