Redis之利用鎖機制來防止緩存過期產生的驚群現象

jopen 11年前發布 | 69K 次閱讀 Redis NoSQL數據庫

緩存驚群現象,在各種緩存中都會存在這種現象,這里以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;     } }</pre>

 

原理就是:

首先,在存儲數據的時候,設置數據的過期時間比實際設置的過期時間多300秒,然后存儲的數據中,通過一個數組來存儲數據,數組中一個鍵用來存放真實的數據,另外一個鍵用來存放數據的真實過期時間,這個留到后期獲取數據的時候做校驗,然后把對應這個數據的“鎖”刪除掉。

這里這么做的原因和讀取數據的做法相關!

然后,在讀取數據的時候,依然像平時一樣直接讀取,如果數據已經超過了有效期(注意:這里的有效期并非設置的有效期,而是更該之后的有效期),那么就只能去讀后端數據庫。如果數據依然有效,則需要去判斷,判斷數據“在真正的有效期內是否失效”,如果沒有失效,則直接返回數據!

重點是,假如數據“在偽造的有效期內沒有失效,而在真正的有效期內已經失效”,那么這時就需要去判斷“數據的鎖”!

通過代碼“$lock = $this->redis->incr($key . ".lock");”可以獲取數據的鎖,“$lock === 1”表示數據沒有鎖,那么這一次請求需要發送到后端數據庫去讀取最新的數據,否則的話表示該數據已經加了鎖,也就是已經有一個線程去后端讀取數據了,那么后來的線程也就沒有權限再去后端取數據,需要等到前面的那個線程執行結束,但是這次讀取就只能讀取“舊的數據”了!

通過上面的解釋也就明白,為什么在存儲數據的時候需要“刪除數據的鎖”!因為一旦數據被重新存儲,那么說明已經有一個線程去后端得到了最新的數據,那么該數據的鎖就可以釋放,然后下一個線程在獲取數據的時候如果有需要就可以得到這個鎖,然后才有權限進入到后端去讀取新數據!

來自:http://my.oschina.net/u/1156660/blog/360552

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!