基于php redis实现分布式锁-kb88凯时官网登录

来自:网络
时间:2024-06-09
阅读:
免费资源网,https://freexyz.cn/

一、redis作为分布式锁的优势

redis是一个开源的、基于内存的键值存储系统,它支持多种数据结构并具备持久化选项。由于其提供了原子操作(如setnxexpire等)和高性能特性,使得redis成为实现分布式锁的理想选择:

  1. 性能优异:redis是内存数据库,响应速度极快,适合于高频读写的场景。
  2. 原子性:redis对某些命令(如setnx)提供了原子操作,还可以执行lua脚本,所以确保了业务的稳定性。
  3. 超时释放:可以设置锁的有效期,即使持有锁的进程崩溃,也能通过过期机制自动释放锁,避免死锁问题。

二、php中使用redis实现分布式锁的步骤与原理

前期准备

  • 运行环境: php 7.3.4   phpredis扩展 4.3.0   redis windows客户端 3.2.100

在使用分布式锁时候我们首先要考虑以下几点:

  • 如何确保锁的唯一性?
    使用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[2])
  • 如何确保redis命令执行的原子性?

要保证原子性必须要求一系列操作要么全部成功执行,要么全部不执行。举例:

$redis = new \redis();
$redis->connect('127.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('127.0.0.1',6379);
$luascript = <<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方法说明:

基于php redis实现分布式锁

//index.php
$redis = new \redis();
$redis->connect('127.0.0.1',6379);
    
$luascript = <<eval($luascript,[1,2,3,4,5],3));

输出结果

基于php redis实现分布式锁

以下是完整的实现代码:

  • redisdistributedlock.php
connect('127.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 = <<redis->eval(
            $luascript,
            [ $this->lockkey, $this->requestid, $this->expiretime ],
            1
        );
        return (bool)$result;
    }
    /**
     * 释放锁
     * @return bool
     */
    public function releaselock(): bool
    {
        $luascript = <<redis->eval(
            $luascript,
            [ $this->lockkey, $this->requestid ],
            1
        );
        return (bool)$result;
    }
}
?>
  • index.php
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();
?>

执行结果如下:

基于php redis实现分布式锁

三、待优化的地方

  • 集群环境下如果主节点挂掉,如何保证设置的 key 在子节点上不会丢失?
  • 如何处理 key 的自动续期

以上就是基于php redis实现分布式锁的详细内容,更多关于php redis分布式锁的资料请关注其它相关文章!

免费资源网,https://freexyz.cn/
返回顶部
顶部
网站地图