首先,所謂的緩存過時引發的「驚羣」現象是指,在大併發狀況下,咱們一般會用緩存來給數據庫分壓,可是會有這麼一種狀況發生,那就是當一個緩存數據失效以後會致使同時有多個併發線程去向後端數據庫發起請求去獲取同一個數據,這樣若是在一段時間內同時生成了大量的緩存,而後在另一段時間內又有大量的緩存失效,這樣就會致使後端數據庫的壓力忽然增大,這種現象就能夠稱爲「緩存過時產生的驚羣現象」!
php
如下代碼的思路,就是利用「鎖機制」來防止驚羣現象。先看代碼:
redis
class KomaRedis{ private $redis; //redis對象 private static $_instance = null; private function __construct($config = array()) { if (empty($config)) { return false; } $this->redis = new Redis(); $this->redis->connect($config['server'], $config['port']); return $this->redis; } /** * @param array $config * @return redis操做類對象 */ public static function getInstance($config = array()) { if (!(self::$_instance instanceof self)) { self::$_instance = new self ($config); } return self::$_instance; } /** * 獲取緩存 * @param $key string $name * @return array,object,number,string,boolean * @desc 此方法使用了鎖機制來防止防止緩存過時時所產生的驚羣現象,保證只有一個進程不獲取數據,能夠更新,其餘進程仍然獲取過時數據 */ public function getByLock($key) { $sth = $this->redis->get($key); if ($sth === false) { return $sth; } else { $sth = json_decode($sth, TRUE); if (intval($sth['expire']) <= time()) { $lock = $this->redis->incr($key . ".lock"); if ($lock === 1) { return false; } else { return $sth['data']; } } else { return $sth['data']; } } } /** * 設置緩存 * @param $key string $name 緩存鍵 * @param $value $string ,array,object,number,boolean $value 緩存值 * @param null $ttl $string ,number $ttl 過時時間,若是不設置,則使用默認時間,若是爲 infinity 則爲永久保存 * @return bool * @desc 此方法存儲的數據會自動加入一些其餘數據來避免驚羣現象,如需保存原始數據,請使用 set */ public function setByLock($key, $value, $ttl = null) { if (is_numeric($ttl) && intval($ttl) > 0) { $ttl = intval($ttl); $exp = time() + $ttl; $arg = array("data" => $value, "expire" => $exp); } else { $ttl = 300; $exp = time() + $ttl; } empty($ttl) OR $ttl += 300; //增長redis緩存時間,使程序有足夠的時間生成緩存 $arg = array("data" => $value, "expire" => $exp); $rs = $this->redis->setex($key, $ttl, json_encode($arg, TRUE)); $this->redis->del($key . ".lock"); return $rs; } /** * 返回redis對象 * redis有很是多的操做方法,咱們只封裝了一部分 * 拿着這個對象就能夠直接調用redis自身方法 */ public function redis() { return $this->redis; } }
原理就是:數據庫
首先,在存儲數據的時候,設置數據的過時時間比實際設置的過時時間多300秒,而後存儲的數據中,經過一個數組來存儲數據,數組中一個鍵用來存放真實的數據,另一個鍵用來存放數據的真實過時時間,這個留到後期獲取數據的時候作校驗,而後把對應這個數據的「鎖」刪除掉。json
這裏這麼作的緣由和讀取數據的作法相關!後端
而後,在讀取數據的時候,依然像平時同樣直接讀取,若是數據已經超過了有效期(注意:這裏的有效期並不是設置的有效期,而是更該以後的有效期),那麼就只能去讀後端數據庫。若是數據依然有效,則須要去判斷,判斷數據「在真正的有效期內是否失效」,若是沒有失效,則直接返回數據!數組
重點是,假如數據「在僞造的有效期內沒有失效,而在真正的有效期內已經失效」,那麼這時就須要去判斷「數據的鎖」!緩存
經過代碼「$lock = $this->redis->incr($key . ".lock");」能夠獲取數據的鎖,「$lock === 1」表示數據沒有鎖,那麼這一次請求須要發送到後端數據庫去讀取最新的數據,不然的話表示該數據已經加了鎖,也就是已經有一個線程去後端讀取數據了,那麼後來的線程也就沒有權限再去後端取數據,須要等到前面的那個線程執行結束,可是此次讀取就只能讀取「舊的數據」了!併發
經過上面的解釋也就明白,爲何在存儲數據的時候須要「刪除數據的鎖」!由於一旦數據被從新存儲,那麼說明已經有一個線程去後端獲得了最新的數據,那麼該數據的鎖就能夠釋放,而後下一個線程在獲取數據的時候若是有須要就能夠獲得這個鎖,而後纔有權限進入到後端去讀取新數據!this