一:概述php
- LRU 用於管理緩存策略,其自己在 Linux/Redis/Mysql 中均有實現。只是實現方式不盡相同。git
- LRU 算法【Least recently used(最近最少使用)】github
- 根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是 "若是數據最近被訪問過,那麼未來被訪問的概率也更高"。算法
- GITHUBsql
二:單鏈表實現的 Lru 算法緩存
- 思路this
- 單鏈表實現spa
- 流程code
- 1. 新數據插入到鏈表頭部;blog
- 2. 每當緩存命中(即緩存數據被訪問),則將數據移到鏈表頭部;
- 3. 當鏈表滿的時候,將鏈表尾部的數據丟棄。
- 優勢
- 實現簡單。
- 當存在熱點數據時,LRU的效率很好。
- 缺點
- 偶發性的、週期性的批量操做會致使LRU命中率急劇降低,緩存污染狀況比較嚴重。
- 命中時須要遍歷鏈表,找到命中的數據塊索引,而後須要將數據移到頭部。
- 流程圖
-
- 代碼實現(PHP)
/** * 思路 * 單鏈表實現 * 原理 * 單鏈表 * 流程 * 1. 新數據插入到鏈表頭部; * 2. 每當緩存命中(即緩存數據被訪問),則將數據移到鏈表頭部; * 3. 當鏈表滿的時候,將鏈表尾部的數據丟棄。 * 優勢 * 實現簡單。 * 當存在熱點數據時,LRU的效率很好。 * 缺點 * 偶發性的、週期性的批量操做會致使LRU命中率急劇降低,緩存污染狀況比較嚴重。 * 命中時須要遍歷鏈表,找到命中的數據塊索引,而後須要將數據移到頭部。 */ class Lru { public static $lruList = []; // 順序存儲單鏈表結構 public static $maxLen = 5; // 鏈表容許最大長度 public static $nowLen = 0; // 鏈表當前長度 /** * LRU_1 constructor. * 因爲 PHP 不是常駐進程程序,因此鏈表初始化能夠經過 Mysql/Redis 實現 */ public function __construct() { self::$lruList = []; self::$nowLen = count(self::$lruList); } /** * 獲取 key => value * @param $key * @return null */ public function get($key) { $value = null; // lru 隊列爲空,直接返回 if (!self::$lruList) { self::$lruList[] = [$key => $this->getData($key)]; // 根據實際項目狀況獲取數據 self::$nowLen++; return $value; } // 查找 lru 緩存 for ($i = 0; $i < self::$nowLen; $i++) { // 若是存在緩存,則直接返回,並將數據從新插入鏈表頭部 if (isset(self::$lruList[$i][$key])) { $value = self::$lruList[$i][$key]; unset(self::$lruList[$i]); array_unshift(self::$lruList, [$key => $value]); break; } } // 若是沒有找到 lru 緩存 if (!isset($value)) { // 插入頭部 array_unshift(self::$lruList, [$key => $this->getData($key)]); // 根據實際項目狀況獲取數據 self::$nowLen++; if (self::$nowLen > self::$maxLen) { self::$nowLen--; array_pop(self::$lruList); } } return $value; } /** * 輸出 Lru 隊列 */ public function echoLruList() { var_dump(self::$lruList); } /** * 根據真實環境獲取數據 * @param $key * @return string */ public function getData($key) { return 'data'; } }
三:Lru K 算法
- 思路
- 爲了避免 LRU 的 '緩存污染' 問題
- 增長一個隊列來維護緩存出現的次數。其核心思想是將「最近使用過1次」的判斷標準擴展爲「最近使用過K次」。
- 原理
- 相比LRU,LRU-K須要多維護一個隊列,用於記錄全部緩存數據被訪問的歷史。
- 只有當數據的訪問次數達到K次的時候,纔將數據放入緩存。
- 當須要淘汰數據時,LRU-K會淘汰第K次訪問時間距當前時間最大的數據.
- 流程
- 1.數據第一次被訪問,加入到訪問歷史列表;
- 2.若是數據在訪問歷史列表裏後沒有達到K次訪問,則按照必定規則 LRU淘汰;
- 3.當訪問歷史隊列中的數據訪問次數達到K次後,將數據索引從歷史隊列刪除,將數據移到緩存隊列中,並緩存此數據,緩存隊列從新按照時間排序;
- 4.緩存數據隊列中被再次訪問後,從新排序;
- 5.須要淘汰數據時,淘汰緩存隊列中排在末尾的數據,即:淘汰「倒數第K次訪問離如今最久」的數據。
- 優勢
- LRU-K下降了「緩存污染」帶來的問題,命中率比LRU要高。
- 缺點
- LRU-K隊列是一個優先級隊列,算法複雜度和代價比較高。
- 因爲LRU-K還須要維護歷史隊列,因此消耗的內存會更多。
- 流程圖
-
- 代碼實現
/** * 思路 * 爲了不 LRU 的 '緩存污染' 問題 * 增長一個隊列來維護緩存出現的次數。其核心思想是將「最近使用過1次」的判斷標準擴展爲「最近使用過K次」。 * 原理 * 相比LRU,LRU-K須要多維護一個隊列,用於記錄全部緩存數據被訪問的歷史。 * 只有當數據的訪問次數達到K次的時候,纔將數據放入緩存。 * 當須要淘汰數據時,LRU-K會淘汰第K次訪問時間距當前時間最大的數據 * 流程 * 1.數據第一次被訪問,加入到訪問歷史列表; * 2.若是數據在訪問歷史列表裏後沒有達到K次訪問,則按照必定規則 LRU淘汰; * 3.當訪問歷史隊列中的數據訪問次數達到K次後,將數據索引從歷史隊列刪除,將數據移到緩存隊列中,並緩存此數據,緩存隊列從新按照時間排序; * 4.緩存數據隊列中被再次訪問後,從新排序; * 5.須要淘汰數據時,淘汰緩存隊列中排在末尾的數據,即:淘汰「倒數第K次訪問離如今最久」的數據。 * 優勢 * LRU-K下降了「緩存污染」帶來的問題,命中率比LRU要高。 * 缺點 * LRU-K隊列是一個優先級隊列,算法複雜度和代價比較高。 * 因爲LRU-K還須要維護歷史隊列,因此消耗的內存會更多。 */ class Lru_K { public static $historyList = []; // 訪問歷史隊列 public static $lruList = []; // 順序存儲單鏈表結構 public static $maxLen = 5; // 鏈表容許最大長度 public static $nowLen = 0; // 鏈表當前長度 /** * LRU_K constructor. * 因爲 PHP 不是常駐進程程序,因此鏈表初始化能夠經過 Mysql/Redis 實現 */ public function __construct() { self::$lruList = []; self::$historyList = []; self::$nowLen = count(self::$lruList); } /** * 獲取 key => value * @param $key * @return null */ public function get($key) { $value = null; // 查找 lru 緩存 for ($i = 0; $i < self::$nowLen; $i++) { // 若是存在緩存,則直接返回,並將數據從新插入鏈表頭部 if (isset(self::$lruList[$i][$key])) { $value = self::$lruList[$i][$key]; unset(self::$lruList[$i]); array_unshift(self::$lruList, [$key => $value]); break; } } // 若是沒有找到 lru 緩存, 則進入歷史隊列進行計數,當次數大於等於5時候,進入緩存隊列 if (!isset($value)) { self::$historyList[$key]++; $value = $this->getData($key); // 進入緩存隊列 if (self::$historyList[$key] >= 5) { array_unshift(self::$lruList, [$key => $value]); // 根據實際項目狀況獲取數據 self::$nowLen++; if (self::$nowLen > self::$maxLen) { self::$nowLen--; array_pop(self::$lruList); } unset(self::$historyList[$key]); // 歷史隊列推出 } } return $value; } /** * 輸出 Lru 隊列 */ public function echoLruList() { var_dump(self::$lruList); var_dump(self::$historyList); } /** * 根據真實環境獲取數據 * @param $key * @return string */ public function getData($key) { return 'data'; } }