当前位置: 首页 > news >正文

PHP+redis 优雅实现加锁机制

setNX,是set if not exists 的缩写,也就是只有不存在的时候才设置, 设置成功时返回 1 , 设置失败时返回 0 。可以利用它来实现锁的效果,但是很多人在使用的过程中都有一些问题没有考虑到。

例如某个查询数据库的接口因为请求量比较大所以加了缓存,并设定缓存过期后刷新。当并发量比较大并且缓存过期的瞬间,大量并发请求会直接查询数据库导致雪崩。如果使用锁机制来控制只有一个请求去更新缓存就能避免雪崩的问题。下面是很多人下意识想到的加锁方法

$rs = $redis->setNX($key, $value);
if ($rs) {
    //处理更新缓存逻辑
    // ......
    //删除锁
    $redis->del($key);
}

通过 setNX 获取锁,如果成功了则更新缓存然后删除锁。其实这里有一个严重的问题:如果更新缓存的时候因为某些原因意外退出了,那么这个锁就不会被删除而一直存在,以至于缓存再也得不到更新。为了解决这个问题有人可能会想到给锁设置一个过期时间,如下

$redis->multi();
$redis->setNX($key, $value);
$redis->expire($key, $ttl);
$redis->exec();

因为 setNX 不具备设置过期时间的功能,所以要借助 Expire 来设置,同时需要使用 Multi/Exec 来确保请求的原子性,以免 setNX 成功了 Expire 却失败了。这样还有问题:当多个请求到达时,虽然只有一个请求的 setNX 可以成功,但是任何一个请求的 Expire 却都可以成功,这就意味着即便获取不到锁也可以刷新过期时间,导致锁一直有效,还是解决不了上面的问题。显然 setNX 满足不了需求,Redis从 2.6.12 起,SET 涵盖了 SETEX 的功能, SET 本身又包含了设置过期时间的功能,所以使用 SET 就可以解决上面遇到的问题

$rs = $redis->set($key, $value, array('nx', 'ex' => $ttl));
if ($rs) {
    //处理更新缓存逻辑
    // ......
    //删除锁
    $redis->del($key);
}

到这一步其实还是有问题的,如果一个请求更新缓存的时间比锁的有效期还要长,导致在缓存更新过程中锁就失效了,此时另一个请求就会获取到锁,但前一个请求在缓存更新完毕的时候,直接删除锁的话就会出现误删其它请求创建的锁的情况。所以要避免这种问题,可以在创建锁的时候需要引入一个随机值并在删除锁的时候加以判断

$rs = $redis->set($key, $random, array('nx', 'ex' => $ttl));
if ($rs) {
     //处理更新缓存逻辑
    // ......
    //先判断随机数,是同一个则删除锁
    if ($redis->get($key) == $random) {
        $redis->del($key);
    }
}

redis的setnx可以加锁、实际使用中用不好很容易会发生各种问题造成死锁

分享一下核心的稳定的redis之nx代码、php使用setnx很容易因为不严谨造成死锁,分享一下正确写法
		$redisKey = 'key:member_id:'.$member_id;
        if($redis->setnx($redisKey,time())){//此处使用setnx(key,1)会有原子性问题
            $redis->expire($redisKey,5);//设置过期时间、其实会有原子性问题
            goto unlock;
        }else{
            //防止死锁
            if($nx_lock = $redis->get($redisKey)) {//获取锁的值
                $dif_time = time() - $nx_lock;//判断锁是否过期、防止死锁、因为(设置过期时间、其实会有原子性问题)
                if($dif_time > 5){//防止死锁
                    $redis->del($redisKey);//释放死锁
                }
                return $this->error("系统繁忙,请稍后再试!");
            }
        }
        unlock:
        	#todo解锁代码

 

 public static function setnx(string $key, string $data = '', int $expire = null)
    {
        if (empty($key)) {
            return false;
        }
        self::init();
        if($expire){
            $res = self::$redis->set($key, $data, ['nx', 'ex' => $expire]);
        }else{
            $res = self::$redis->setnx($key,$data);
        }
        // 防止死锁
        if(!$res && $expire){
            $ttl = self::$redis->ttl($key);
            if($ttl === -1 || $ttl > $expire){
                self::$redis->expire($key, $expire);
            }
        }
        return $res;
    }

相关文章:

  • RedHatLinux的第一次作业
  • 单片机OTA升级中Bootloader怎么判断APP有没有问题?
  • java的字符串,数组,集合的长度/大小
  • 通用人工智能(AGI):定义、挑战与未来展望
  • 关于ngx-datatable no data empty message自定义模板解决方案
  • 在虚拟机VMware上安装CENTOS7-图文教程
  • 【iOS逆向与安全】sms短信转发插件与上传服务器开发
  • 解锁 Postman:下载安装与账户注册使用的全攻略,踏上测试新征程
  • 项目管理系统在制造企业 IPD 研发管理中的应用实践
  • 硬件电路 - 推挽(PP)输出与开漏(OD)输出的区别
  • ViM-UNet模型详解及代码复现
  • 如和实现一个服务器端高并发线程池
  • 【C++】滑动窗口算法
  • Kubernetes服务部署 —— Kafka
  • 安卓Android与iOS设备管理对比:企业选择指南
  • SVN 拉取,文件冲突 解决办法
  • ClickHouse 数据倾斜实战:案例分析与优化技巧
  • 基于 Simulink 的超级储能参与电网一次调频仿真研究
  • uniapp+Vue3 组件之间的传值方法
  • simpleGRPO实现分享)
  • 古稀之年的设计家吴国欣:重拾水彩,触摸老上海文脉
  • 上海这场有温度的“人才集市”,为更多人才搭建“暖心桥”
  • 湖北宜化拟斥资超32亿加价回购“弃子”,布局上游煤炭业务
  • 共建医学人工智能高地,上海卫健委与徐汇区将在这些方面合作
  • 刘强东坐镇京东一线:管理层培训1800人次,最注重用户体验
  • 外交部:各方应为俄乌双方恢复直接对话创造条件