《算法 - Lru算法》

一:概述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';
          }
      }
相關文章
相關標籤/搜索