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

本文经过阅读 原文 此文进行整理,看原文的同窗们请移步至此

一 缓存穿透

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

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,说明此资源已经被其余请求得到。使用代码实现的话,思路以下:缓存

  1. 给存入缓存的数据增长一个过时时间字段暂时给这个字段起名字叫$data['expire'](这个过时时间要短于实际的缓存过时时间),方便在缓存过时前执行加锁和缓存更新。
  2. 若是$data['expire']达到过时时间,则执行加锁以及缓存更新。
  3. 此时若是有其余请求进入则返回更新以前的数据。

代码以下:并发

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脚本解锁。this

相关文章
相关标签/搜索