page contents

PHP 缓存穿透/使用Redis进行缓存加锁

以下内容希望帮助到大家!

attachments-2020-06-jnH5DB7Z5edeec50c4818.png

一 缓存穿透


缓存穿透指的是,当我们访问某个缓存KEY想取得对应的数据时,若此KEY不存在于缓存中,则会去查库。如何解决呢?将每次查询的结果都放入缓存不管是不是空。

public function getArticles($key)
{
    $expire = 60 * 3;
    $data = Cache::get($key);
    //注意:此处使用is_null来判断而不是直接使用 (!$data)来判断。
    //使用 (!$data)来判断的弊端是:如果$data的值为空字符串或者空数组,此处也是不成立的,会继续执行查询DB的语句,造成缓存穿透
    if (!is_null($data)) {
        return $data;
    }
    $data = $this->searchDB();
    Cache::put($key, $data, $expire);
    return $data;
}

这样处理的原因是,即使当前查询的key为空字符串,或者空数组,结果也会被缓存起来。

当下一次访问时会直接返回,不会造成缓存穿透。


二 缓存加锁(Redis)


若系统的并发很高,当缓存过期时,则大量的请求会穿透缓存,同时到DB中查询,那我们可以设置缓存当缓存过期时,只去DB中请求一次并缓存吗?

可以,我们可以使用redis的setNx()setNx($key) 的作用类似于set($key) ,setNx的意思为 set Not Exists 如果$key不存在则设置,存在则不进行任何操作。

设置成功设置返回1,说明当前的请求获得了当前的操作权限,设置失败返回0,说明此资源已经被其他请求获得。

使用代码实现的话,思路如下:

①给存入缓存的数据增加一个过期时间字段暂时给这个字段起名字叫$data['expire'](这个过期时间要短于实际的缓存过期时间),方便在缓存过期前执行加锁和缓存更新。

②如果$data['expire']达到过期时间,则执行加锁以及缓存更新。

③此时如果有其他请求进入则返回更新之前的数据。


代码如下:

public function getArticlesLock($key)
{
    $time = time();
    $expire = 10 * 2;
    $lockKey = 'lock:k';
    $data = Cache::get($key);

    if (!is_null($data)) {
        //缓存未过期
        if ($data['expire'] > time()){
            return $data['data'];
        }
        //加锁失败说明已经有请求执行加锁,返回之前的缓存数据
        if (!Redis::setnx($lockKey,1)) {
            return $data['data'];
        }
    }
    sleep(3);
    $datat = $this->searchDB();
    $data = [
        'data' => $datat,
        'expire' => $time + $expire - 10
    ];
    $r = Cache::put($key, $data, $expire);
    //解锁
    Redis::del($lockKey);
    return $data['data'];
}

当然此处也可以使用set()来代替setnx()加锁,以及使用lua脚本解锁。


attachments-2020-06-Ouc5NqrB5edeec1ca1f48.jpg

  • 发表于 2020-06-09 09:56
  • 阅读 ( 608 )
  • 分类:PHP开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1135 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1474 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章