Redis緩存穿透、緩存擊穿、緩存雪崩的原理和解決辦法

在大數據時代,因爲網絡請求的併發,致使的數據庫的I/O開銷巨大,因此爲了緩解數據庫的壓力,緩存技術必不可少,而這其中redis基本是服務器的緩存服務之一,雖然緩存技術很好用,可是也會出現各類各樣的問題,這裏就最多見的三種問題進行原理分析和解決,但願可以給你們帶來幫助。
  • 緩存穿透:key中對應的緩存數據不存在,致使去請求數據庫,形成數據庫的壓力倍增的狀況php

  • 緩存擊穿:redis過時後的一瞬間,有大量用戶請求同一個緩存數據,致使這些請求都去請求數據庫,形成數據庫壓力倍增的情,針對一個key而言html

  • 緩存雪崩:緩存服務器宕機或者大量緩存集中某個時間段失效,致使請求所有去到數據庫,形成數據庫壓力倍增的狀況,這個是針對多個key而言redis

1、緩存穿透的解決方案

  • 經常使用方法能夠採用布隆過濾器方法進行數據攔截,其次能夠還有一種解決思路,就是若是請求的數據爲空,將空值也進行緩存,就不會發生穿透狀況
<?php
class getPrizeList {
    /**
     * redis實例
     * @var \Redis
     */
    private $redis;

    /**
     * @var string
     */
    private $redis_key = '|prize_list';

    /**
     * 過時時間
     * @var int
     */
    private $expire = 30;

    /**
     * getPrizeList constructor.
     * @param $redis
     */
    public function __construct($redis)
    {
        $this->redis = $redis;
    }

    /**
     * @return array|bool|string
     */
    public function fetch()
    {
        $result = $this->redis->get($this->redis_key);
        if(!isset($result)) {
            //此處應該進行數據庫查詢...
            //若是查詢結果不存在,給其默認空數組進行緩存
            $result = [];
            $this->redis->set($this->redis_key, $result, $this->expire);
        }

        return $result;
    }
}

2、緩存擊穿解決辦法

  • 使用互斥鎖(mutex key),就是一個key過時時,多個請求過來容許其中一個請求去操做數據庫,其餘請求等待第一個請求成功返回結果後再請求。
<?php
class getPrizeList {
    /**
     * redis實例
     * @var \Redis
     */
    private $redis;

    /**
     * @var string
     */
    private $redis_key = '|prize_list';

    /**
     * @var string
     */
    private $setnx_key = '|prize_list_setnx';

    /**
     * 過時時間
     * @var int
     */
    private $expire = 30;

    /**
     * getPrizeList constructor.
     * @param $redis
     */
    public function __construct($redis)
    {
        $this->redis = $redis;
    }

    /**
     * @return array|bool|string
     */
    public function fetch()
    {
        $result = $this->redis->get($this->redis_key);
        if(!isset($result)) {
            if($this->redis->setnx($this->setnx_key, 1, $this->expire)) {
                //此處應該進行數據庫查詢...
                //$result = 數據庫查詢結果;
                $this->redis->set($this->redis_key, $result, $this->expire);
                $this->redis->del($this->setnx_key); //刪除互斥鎖
            } else {
                //其餘請求每等待10毫秒從新請求一次
                sleep(10);
                self::fetch();
            }
        }

        return $result;
    }
}

3、緩存雪崩的解決辦法

  • 這種狀況是由於多個key同時過時致使的數據庫壓力,一種方法能夠在key過時時間基礎上增長時間隨機數,讓過時時間分散開,減小緩存時間過時的重複率
  • 另外一種方法就是加鎖排隊,這種有點像上面緩存擊穿的解決方式,可是這種請求量太大,好比5000個請求過來,4999個都須要等待,這必然是指標不治本,不只用戶體驗性差,分佈式環境下就更加複雜,所以在高併發場景下不多使用
  • 最好的解決方法,是使用緩存標記,判斷該標記是否過時,過時則去請求數據庫,而緩存數據的過時時間要設置的比緩存標記的長,這樣當一個請求去操做數據庫的時候,其餘請求拿的是上一次緩存數據
<?php
class getPrizeList {
    /**
     * redis實例
     * @var \Redis
     */
    private $redis;

    /**
     * @var string
     */
    private $redis_key = '|prize_list';

    /**
     * 緩存標記key
     * @var string
     */
    private $cash_key = '|prize_list_cash';

    /**
     * 過時時間
     * @var int
     */
    private $expire = 30;

    /**
     * getPrizeList constructor.
     * @param $redis
     */
    public function __construct($redis)
    {
        $this->redis = $redis;
    }

    /**
     * @return array|bool|string
     */
    public function fetch()
    {
        $cash_result = $this->redis->get($this->cash_key);
        $result = $this->redis->get($this->redis_key);
        if(!$cash_result) {
            $this->redis->set($this->cash_key, 1, $this->expire);
            //此處應該進行數據庫查詢...
            //$result = 數據庫查詢結果, 而且設置的時間要比cash_key長,這裏設置爲2倍;
            $this->redis->set($this->redis_key, $result, $this->expire * 2);
            $this->redis->del($this->cash_key); //刪除互斥鎖
        }

        return $result;
    }
}
相關文章
相關標籤/搜索