redis——Redis中的LRU算法改進

redis通常使用緩存,是使用一種固定最大內存的使用。當數據達到可使用的最大固定內存時,我們需要通過移除老數據來獲取空間。redis作為緩存是否有效的重要標志是如何尋找一種好的策略:刪除即將需要使用的數據是一種糟糕的策略,而刪除那些很少再次請求的數據則是一種好的策略。
在其他的緩存組件還有個命中率,僅僅表示讀請求的比例。訪問一個緩存中的keys通常不是分布式的。然而訪問經常變化,這意味著不經常訪問,相反,有些keys一旦不流行可能會轉向最經常訪問的keys。 因此,通常一個緩存系統應該盡可能保留那些未來最有可能被訪問的keys。針對keys淘汰的策略是:那些未來極少可能被訪問的數據應該被移除。
但有一個問題:redis和其他緩存系統不能夠預測未來。

LRU算法

緩存系統不能預測未來,原因是:那些很少再次被訪問的key也很有可能最近訪問相當頻繁。如果經常被訪問的模式不會突然改變,那么這是一種很有效的策略。然而,“最近經常被訪問”似乎更隱晦地標明一種 理念。這種算法被稱為LRU算法。最近訪問頻繁的key相比訪問少的key有更高的可能性。
舉個例子,這里有4個不同訪問周期的key,每一個“~”字符代表一秒,結尾的“|”表示當前時刻。

~~~~~A~~~~~A~~~~~A~~~~A~~~~~A~~~~~A~~|
~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~|
~~~~~~~~~~C~~~~~~~~~C~~~~~~~~~C~~~~~~|
~~~~~D~~~~~~~~~~D~~~~~~~~~D~~~~~~~~~D|

A key每5秒請求一次,B周期是2秒,C、D都是10秒。
訪問頻率最高的是B,因為它的空閑時間最短,這意味著B是4個key中未來最有可能被訪問的key。
同樣的A和C目前的空閑時間是2s和6s也能很好地反映它們本身的周期。然而你可以看到不夠嚴謹:D的訪問周期是10秒,但它卻是4個key中最近被訪問的。
當然,在一個很長的運行周期中,LRU算法能工作得很好。通常有一個更高訪問頻率的key當然有一個更低的空閑周期。LRU算法淘汰最少被訪問key,那些有最大空閑周期的key。實現上也相當容易,只需要額外跟蹤最近被訪問的key即可,有時甚至都需要:把所有我們想要淘汰的對象放到一個鏈表中,當一個對象訪問就移除鏈表頭部元素,當我們要淘汰元素是就直接淘汰鏈表尾部開始。

redis中的LRU:起因

最初,redis不支持LRU算法。當內存有效性成為一個必須被解決的問題時,后來才加上了。通過修改redis對象結構,在每個key對象增加24bit的空間。沒有額外的空間使用鏈表把所有對象放到一個鏈表中(大指針),因此需要實現得更加有效,不能因為key淘汰算法而讓整個服務改動太大。
24bits的對象已經足夠去存儲當前的unxi時間戳。這個表現,被稱為“LRU 時鐘”,key元數據經常被更新,所以它是一個有效的算法。
然后,有另一個更加復雜的問題需要解決:如何選擇訪問間隔最長的key,然后淘汰它。
redis內部采用一個龐大的hash table來保存,添加另外一個數據結構存儲時間間隔顯然不是一個好的選擇。然而我們希望能達到一個LRU本身是一個近似的,通過LRU算法本身來實現。

redis原始的淘汰算法簡單實現:**當需要淘汰一個key時,隨機選擇3個key,淘汰其中間隔時間最長的key。**基本上,我們隨機選擇key,淘汰key效果很好。后來隨機3個key改成一個配置項"N隨機key"。但把默認值提高改成5個后效果大大提高。考慮到它的效果,你根本不用修改他。

然而,你可能會想這個算法如何有效執行,你可以看到我們如何搗毀了很多有趣的數據。也許簡單的N key,我們會遇到很多好的決策,但是當我們淘汰最好的,下一個周期又開始抓。

驗證規則第一條:用肉眼觀察你的算法

其中有一個觀點已經應用到Redis 3.0正式版中了。在redis2.8中一個LRU緩存經常被使用在多個環境,用戶關于淘汰的沒有抱怨太多,但是很明顯我可以提高它,通過不僅僅是增加額外的空間,還有額外的CPU時間。
然而為了提高某項功能,你必須觀察它。有多個不同的方式去觀察LRU算法。你可以通過寫工具觀察,例如模擬不同的工作負載、校驗命中率和失誤率。
程序非常簡單:增加一些指定的keys,然后頻繁地訪問這些keys以至于每一個key都有一個下降的空閑時間。最終超過50%的keys被增加,一半的老key需要被淘汰。
一個完美理想的LRU實現,應該是沒有最新加的key被淘汰,而是淘汰最初的50%的老key。

規則二:不要丟棄重要信息

借助最新的可視化工具,我可以在嘗試新的方法觀察和測試幾分鐘。使用redis最明顯有效的提高算法就是,積累對立的垃圾信息在一個淘汰池中。
基本上,當N keys算法被采用時,通常會分配一個很大的線程pool(默認為16key),這個池按照空閑時間排序,所以只有當有一個大于池中的一個或者池為空的時候,最新的key只會進入到這個池中。
同時,一個新的redis-cli模式去測量LRU算法也增加了(看這個-lru-test選項)。
還有另外一個方式去檢驗LRU算法的好壞,通過一個冪等訪問模式。這個工具通常校驗用一個不同的測試,新算法工作工作效果好于真實世界負載。它也同樣使用流水線和每秒打印訪問日志,因此可以被使用不用為了基準不同的思想,至少可以校驗和觀察明顯的速度回歸。

規則三、最少使用原則(LFU算法)


一切源于一個開放性問題:但你有多個redis 3.2數據庫時,而淘汰算法只能在本機選擇。因此,假如你全部空閑小的key都是DB0號機器,空閑時間長的key都是1號機器,redis每臺機器都會淘汰各自的key。一個更好的選擇當然是先淘汰DB1,最后再淘汰DB0。
當redis被當作緩存使用時很少有情況被分成不同的db上,這不是一個好的處理方式。然而這也是我為什么我再一次修改淘汰代碼的原因。最終,我能夠修改緩存池包括數據庫id,使用單緩存池為多個db,代替多緩存池。這種實現很麻煩,但是通過優化和修改代碼,最終它比普通實現要快到20%。
然而這時候,我對這個redis緩存淘汰算法的好奇心又被點燃。我想要提升它。我花費了幾天想要提高LRU算法實現:或許可以使用更大的緩存池?通過歷史時間選擇最合適被淘汰的key?
經過一段時間,通過優化我的工具,我理解到:LRU算法受限于數據庫中的數據樣本,有時可能相反的場景效果非常好,因此要想提高非常非常難。實際上,能通過展示不同算法的圖片上看這有點非常明顯:每個周期10個keys幾乎和理論的LRU算法表現一致。
當原始算法很難提高時,我開始測試新的算法。 如果我們倒回到博客開始,我們說過LRU實際上有點嚴格。哪些key需要我們真正想要保留:將來有最大可能被訪問,最頻繁被訪問,而不是最近被訪問的key。
淘汰最少被訪問的key算法成為:LFU(Least Frequently Used),將來要被淘汰騰出新空間給新key。
理論上LFU的思想相當簡單,只需要給每個key加一個訪問計數器。每次訪問就自增1,所以也就很容易知道哪些key被訪問更頻繁。
當然,LFU也會帶起其他問題,不單單是針對redis,對于LFU實現:
1、不能使用“移除頂部元素”的方式,keys必須要根據訪問計數器進行排序。每訪問一次就得遍歷所有key找出訪問次數最少的key。
2、LFU不能僅僅是只增加每一訪問的計數器。正如我們所講的,訪問模式改變隨時變化,因此一個有高訪問次數的key,后面很可能沒有人繼續訪問它,因此我們的算法必須要適應超時的情況。
在redis中,第一個問題很好解決:我們可以在LRU的方式一樣:隨機在緩存池中選舉,淘汰其中某項。第二個問題redis還是存在,因此一般對于LFU的思想必須使用一些方式進行減少,或者定期把訪問計數器減半。

24位的LFU實現

LFU有它本身的實現,在redis中我們使用自己的24bit來記錄LRU。
為了實現LFU僅僅需要在每個對象額外新增24bit:
1、一部分用于保存訪問計數器;
2、足夠用于決定什么時候將計數器減半的信息;

我的解決方法是把24bit分成兩列:

16bits8bitslast decr timeLOG_C

16位記錄最后一次減半時間,那樣redis知道上一次減半時間,另外8bit作為訪問計數器。
你可能會想8位的計數器很快就會溢出,是的,相對于簡單計數器,我采用邏輯計數器。邏輯計數器的實現:

uint8_t LFULogIncr(uint8_t counter) {if (counter == 255) return 255;double r = (double)rand()/RAND_MAX;double baseval = counter - LFU_INIT_VAL;if (baseval < 0) baseval = 0;double p = 1.0/(baseval*server.lfu_log_factor+1);if (r < p) counter++;return counter;}

基本上計數器的較大者,更小的可能計數器會增加:上面的代碼計算p位于0~1之間,但計數器增長時會越來越小,位于0-1的隨機數r,只會但滿足r<p時計數器才會加一。
你可以配置計數器增長的速率,如果使用默認配置,會發生:

  • 100次訪問后,計數器=10;
  • 1000次訪問是是18;
  • 10萬次訪問是142;
  • 100萬次訪問后達到255,并不在繼續增長;

下面,讓我們看看計數器如果進行衰減。16位的被儲存為unix時間戳保留到分鐘級別,redis會隨機掃描key填充到緩存池中,如果最后一個下降的時間大于N分鐘前(可配置化),如果計數器的值很大就減半,或者對于值小的就直接簡單減半。
這里又衍生出另外一個問題,就是新進來的key是需要有機會被保留的。由于LFU新增是得分都是0,非常容易被選舉替換掉。在redis中,開始默認值為5。這個初始值是根據增長數據和減半算法來估算的。模擬顯示得分小于5的key是首選。

代碼和性能

上面描述的算法已經提交到一個非穩定版的redis分支上。我最初的測試顯示:它在冪等模式下優于LRU算法,測試情況是每個key使用用相同數量的內存,然而真實世界的訪問可能會有很大不同。時間和空間都可能改變得很不同,所以我會很開心去學習觀察現實世界中LFU的性能如何,兩種方式在redis實現中對性能的改變。
因此,新增了一個OBJECT FREQ子命令,用于報告給定key的訪問計數器,不僅僅能有效提觀察一個計數器,而且還能調試LFU實現中的bug。
注意運行中切換LRU和LFU,剛開始會隨機淘汰一些key,隨著24bit不能匹配上,然而慢慢會適應。 還有幾種改進實現的可能。Ben Manes發給我這篇感興趣的文章,描述了一種叫TinyLRU算法。鏈接

這篇文章包含一個非常厲害的觀點:相比于記錄當前對象的訪問頻率,讓我們(概率性地)記錄全部對象的訪問頻率,看到了,這種方式我們甚至可以拒絕新key,同樣,我們相信這些key很可能得到很少的訪問,所以一點也不需要淘汰,如果淘汰一個key意味著降低命中/未命中率。
我的感覺這種技術雖然很感興趣GET/SET LFU緩存,但不適用與redis性質的數據服務器:用戶期望keys被創建后至少存在幾毫秒。拒絕key的創建似乎在redis上就是一種錯誤。
然而,redis保留了LFU信息,當一個key被覆蓋時,舉個例子:

SET oldkey some_new_value

24位的LFU計數器會從老的key復制到新對象中。

新的redis淘汰算法不穩定版本還有以下幾個好消息:
1、跨DB策略。在過去的redis只是基于本地的選舉,現在修復為所有策略,不僅僅是LRU。
2、易變ttl策略。基于key預期淘汰存活時間,如今就像其他策略中的使用緩存池。
3、在緩存池中重用了sds對象,性能更好。

這篇博客比我預期要長,但是我希望它反映出一個見解:在創新和對于已經存在的事物實現上,一種解決方案去解決一個特定問題,一個基礎工具。由開發人員以正確的方式使用它。許多redis的用戶把redis作為一個緩存的解決方案,因此提高淘汰策略這一塊經常一次又一次被拿出來探討。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/445275.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445275.shtml
英文地址,請注明出處:http://en.pswp.cn/news/445275.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

redis——HyperLogLog

HyperLogLog 是一種概率數據結構&#xff0c;用來估算數據的基數。數據集可以是網站訪客的 IP 地址&#xff0c;E-mail 郵箱或者用戶 ID。 基數就是指一個集合中不同值的數目&#xff0c;比如 a, b, c, d 的基數就是 4&#xff0c;a, b, c, d, a 的基數還是 4。雖然 a 出現兩次…

機器學習知識總結系列-機器學習中的優化算法總結(1-4)

文章目錄1.梯度下降1.1批量梯度下降(BGD)1.2隨機梯度下降&#xff08;SGD&#xff09;1.3 小批量隨機梯度下降&#xff08;MSGD&#xff09;1.4 比較&#xff1a;1.5 動量算法&#xff08;momentum&#xff09;1.6 Nestrov Momentum2. 自適應方法2.1 自適應學習率算法&#xff…

Python(19)-字符串、Unicode字符串

高級數據類型--字符串、Unicode字符串1.字符串的定義2.字符串的長度、計數、Index3.字符串常用方法3.1判斷類型3.2查找和替換3.3文本對齊3.4去除空白字符.strip()4.字符串的拆分和拼接5.字符串的切片6.跨行字符串7.包含轉義字符r8.字符串的分割與連接9.Unicode字符串字符串-不變…

機器學習中的距離和損失函數

文章目錄13.1 距離度量13.2 損失函數13.1 距離度量 距離函數種類&#xff1a;歐式距離、曼哈頓距離、明式距離&#xff08;閔可夫斯基距離&#xff09;、馬氏距離、切比雪夫距離、標準化歐式距離、漢明距離、夾角余弦等常用距離函數&#xff1a;歐式距離、馬氏距離、曼哈頓距離…

Python(20)-高級數據類型的公共方法

高級數據類型的公共方法1內置函數2高級數據類型切片3運算符&#xff0c;*&#xff0c;in4完整的for循環公共方法是列表&#xff0c;元組&#xff0c;字典&#xff0c;字符串都能使用的方法1內置函數 內置函數&#xff1a;不需要import導入模塊&#xff0c;就可以直接使用的函數…

redis——為什么選擇了跳表而不是紅黑樹?

跳表是個啥東西請看這個文章。 我們知道&#xff0c;節點插入時隨機出一個層數&#xff0c;僅僅依靠一個簡單的隨機數操作而構建出來的多層鏈表結構&#xff0c;能保證它有一個良好的查找性能嗎&#xff1f;為了回答這個疑問&#xff0c;我們需要分析skiplist的統計性能。 在…

機器學習公式推導

文章目錄線性回歸邏輯回歸線性判別分析PCAk-means決策樹svm隨機深林GBDTxgboost強化學習MapReduce線性回歸 邏輯回歸 對于分類問題&#xff1a;輸出0/1&#xff0c;超過[0,1]沒有意義&#xff0c;使用sigmoid函數 **代價函數&#xff1a;**使用L2平方差&#xff0c;由于模型函…

Python綜合應用(1)--名片管理系統開發

第一個綜合應用-名片管理系統1框架搭建2完善功能綜合應用&#xff0c;名片管理系統 歡迎界面&#xff0c;不同選項&#xff0c;1.新建名片&#xff0c;2.顯示全部&#xff0c;3 查詢名片&#xff08;查到之后可以修改名片信息&#xff09;&#xff0c;0 退出系統 程序開發流程…

springboot1——spring相關入門

spring 隨著我們開發&#xff0c;發現了一個問題&#xff1a; A---->B---->C---->D 在A中創建B的對象調用B的資源 在B中創建C的對象調用C的資源 在C中創建D的對象調用…

大數據學習(06)-- 云數據庫

文章目錄目錄1.什么是云數據庫&#xff1f;1.1 云計算和云數據庫的關系1.2 云數據庫的概念1.3 云數據庫的特性1.4 云數據庫應用場景1.5 云數據庫和其他數據的關系2.云數據庫產品有哪些&#xff1f;2.1 云數據庫廠商概述2.2 亞馬遜云數據庫產品2.3 Google云數據庫產品2.4 微軟云…

Python(21)--變量進階

變量的進階使用1變量引用2可變、不可變數據類型3局部變量和全局變量4.Tips本系列博文來自學習《Python基礎視頻教程》筆記整理&#xff0c;視屏教程連接地址&#xff1a;http://yun.itheima.com/course/273.html在博文&#xff1a;https://blog.csdn.net/sinat_40624829/articl…

HTTP 響應代碼全集

HTTP 響應狀態代碼指示特定 http 請求是否已成功完成。響應分為五類&#xff1a;信息響應(100–199)&#xff0c;成功響應(200–299)&#xff0c;重定向(300–399)&#xff0c;客戶端錯誤(400–499)和服務器錯誤 (500–599)。狀態代碼由 section 10 of RFC 2616定義 信息響應 …

機器學習知識總結系列-機器學習中的數學-矩陣(1-3-2)

矩陣 SVD 矩陣的乘法狀態轉移矩陣狀態轉移矩陣特征值和特征向量 對稱陣 正交陣 正定陣數據白化矩陣求導 向量對向量求導 標量對向量求導 標量對矩陣求導一.矩陣1.1 SVD奇異值分解&#xff08;Singular Value Decomposition&#xff09;&#xff0c;假設A是一個mn階矩陣&#xf…

阿里Java編程規約(注釋)提煉

【強制】類、類屬性、類方法的注釋必須使用 Javadoc 規范&#xff0c;使用/**內容*/格式&#xff0c;不得使用 // xxx 方式。 說明&#xff1a;在 IDE 編輯窗口中&#xff0c;Javadoc 方式會提示相關注釋&#xff0c;生成 Javadoc 可以正確輸出相應注釋&#xff1b;在 IDE 中…

Python面試題-交換兩個數字的三種方法

Python實現兩個數字交換解法1解法2解法3a6 b100 解法1 使用其他變量&#xff0c;最通用的方法 ca ab bc 解法2 不使用其他變量,利算法節省內存空間 aab ba-b aa-b 解法3 python 專有 a,b(b,a) #等號右邊是一個元組 或者可以寫為&#xff1a; a,bb,a print(a,b)

面試中海量數據處理總結

教你如何迅速秒殺掉&#xff1a;99%的海量數據處理面試題 前言 一般而言&#xff0c;標題含有“秒殺”&#xff0c;“99%”&#xff0c;“史上最全/最強”等詞匯的往往都脫不了嘩眾取寵之嫌&#xff0c;但進一步來講&#xff0c;如果讀者讀罷此文&#xff0c;卻無任何收獲&…

redis——舊版復制

Redis 的復制功能分為同步&#xff08;sync&#xff09;和命令傳播&#xff08;command propagate&#xff09;兩個操作&#xff1a; 同步操作用于將從服務器的數據庫狀態更新至主服務器當前所處的數據庫狀態。命令傳播操作用于在主服務器的數據庫狀態被修改&#xff0c; 導致…

Linux(3)-網-ifconfig,ping,ssh

終端命令網-ping,ssh1. ifconfig -a2. ping3. ssh3.1安裝3.2 連接3.3 配置登入別名防火墻端口號,todo1. ifconfig -a 查看IP地址&#xff0c; 還可以用于配置網口。 ifconfig -a 2. ping ping命令&#xff1a; 檢測到IP地址的連接是否正常。命令開始后由本機發送數據包a&…

redis——相關問題匯總

什么是redis&#xff1f; Redis 本質上是一個 Key-Value 類型的內存數據庫&#xff0c; 整個數據庫加載在內存當中進行操作&#xff0c; 定期通過異步操作把數據庫數據 flush 到硬盤上進行保存。 因為是純內存操作&#xff0c; Redis 的性能非常出色&#xff0c; 每秒可以處理…

一文搞定面試中的二叉樹問題

一文搞定面試中的二叉樹問題 版權所有&#xff0c;轉載請注明出處&#xff0c;謝謝&#xff01; http://blog.csdn.net/walkinginthewind/article/details/7518888 樹是一種比較重要的數據結構&#xff0c;尤其是二叉樹。二叉樹是一種特殊的樹&#xff0c;在二叉樹中每個節點…