PHP如何正确的使用Redis的SETNX设置一个并发锁

bloger 2021-01-05 AM 2196℃ 0条

最近有朋友问如何使用Redis的SETNX设置一个并发锁(针对的是同一个用户在同一点进行一次行为操作),他发了一段代码,我们看看问题;
未标题-1.png
先来看下php 中setNx这个函数的介绍:

Set the string value in argument as value of the key if the key doesn't already exist in the database.
如果键在数据库中不存在,则将参数中的字符串值设置为键的值

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

如下是朋友写的代码:

QQ图片20210105103833.png

看起来是没什么问题,假设如果在//更新资金日志里面程序中断了,del是不是就执行不到了,是不是会导致死锁,虽然下面加了一个$redis->expire($key, $ttl);但是问题还是没有解决,没有意义;所以这种做法不是特别可靠;

还有的朋友直接这样做:

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

这样显然有一个问题, setNX 成功了 Expire 却失败了是不是又死锁了?

那么朋友说这样呢:

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

为了确保请求的原子性,使用 Multi/Exec确保2次都成功、或者失败;这样还有问题:当多个请求到达时,虽然只有一个请求的 setNX 可以成功,但是任何一个请求的 Expire 却都可以成功,这就意味着即便获取不到锁也可以刷新过期时间,导致锁一直有效,还是解决不了上面的问题。

那么我们如何使用setnx设置一个可靠有效的锁?下面代码实现


/**
     * @param $uid
     * @param int $timeout
     * @return bool
     * 设置锁
     */
    public  function set_mutex($uid,$timeout = 2){
        $cur_time = time();
        $read_news_mutex_key = "redis:mutex:{$uid}";
        $mutex_res = $this->setnx($read_news_mutex_key,$cur_time + $timeout);
        if($mutex_res){
            return true;
        }
        //就算意外退出,下次进来也会检查key,防止死锁
        $time = $this->get($read_news_mutex_key);
        if($cur_time > $time){
            $this->del($read_news_mutex_key);
            return $this->setnx($read_news_mutex_key,$cur_time + $timeout);
        }
        return false;
    }

    /**
     * @param $uid
     * 释放锁
     */
    public  function del_mutex($uid){
        $read_news_mutex_key = "redis:mutex:{$uid}";
        $this->del($read_news_mutex_key);
    }

调用如下:

    if(!$this->set_mutex($uid)){
            $this->error('请求太频繁,请稍后再试',-10001,'','',true);
    }
    //....代码逻辑

    $this->redis->del_mutex($uid);

以上就是正确使用Redis的SETNX设置一个并发锁;

标签: redis

非特殊说明,本博所有文章均为博主原创。

评论啦~