這篇文章為大家帶來了關於Redis的相關知識,其中主要介紹了LRU快取淘汰演算法實現,包括了Redis的近似LRU演算法實現、近似LRU演算法的實際執行等等,希望對大家有幫助。
推薦學習:Redis學習教學
#1 標準LRU的實作原理
LRU,最近最少使用(Least Recently Used,LRU),經典快取演算法。
LRU會使用一個鍊錶維護快取中每個資料的存取情況,並根據資料的即時訪問,調整資料在鍊錶中的位置,然後透過資料在鍊錶中的位置,表示資料是最近剛訪問的,還是已有一段時間未訪問。
LRU會把鏈頭、尾分別設為MRU端和LRU端:
- MRU,Most Recently Used 縮寫,表示此處資料剛被存取
- #LRU端,此處資料最近最少被存取的資料
LRU可分成以下情況:
- case1:當有新資料插入,LRU會把該數據插入到鏈首,同時把原來鍊錶頭部的數據及其之後的數據,都向尾部移動一位
- case2:當有數據剛被訪問一次後,LRU會把該資料從它在鍊錶中目前位置,並移動到鏈首。把從鍊錶頭部到它目前位置的其他數據,都向尾部移動一位
- case3:當鍊錶長度無法再容納更多數據,再有新數據插入,LRU移除鍊錶尾部的數據,這也相當於將資料從快取中淘汰掉
case2圖解:鍊錶長度為5,從鍊錶頭部到尾部保存的數據分別是5,33,9 ,10,8。假設資料9被存取一次,則9就會被移到鍊錶頭部,同時,資料5和33都要向鍊錶尾部移動一位。
所以若嚴格按LRU實現,假設Redis保存的資料較多,還要在程式碼中實現:
-
為Redis使用最大記憶體時,可容納的所有資料維護一個鍊錶
需額外記憶體空間來保存鍊錶
-
每當有新資料插入或現有資料被再次訪問,需執行多次鍊錶操作
在訪問資料的過程中,讓Redis受到資料移動和鍊錶操作的開銷影響
提供了一個近似LRU演算法實現。
2 Redis的近似LRU演算法實作Redis的記憶體淘汰機制是如何啟用近似LRU演算法的? redis.conf中的如下組態參數:maxmemory,設定Redis server可使用的最大記憶體容量,一旦server使用實際記憶體量超出該閾值, server會根據maxmemory-policy配置策略,執行記憶體淘汰操作
maxmemory-policy,設定Redis server記憶體淘汰策略,包括近似LRU、LFU、按TTL值淘汰和隨機淘汰等
- allkeys-lru是在所有的KV對中篩選將被淘汰的數據
- volatile- lru在設定了TTL的KV對中篩選將被淘汰資料
-
全域LRU時鐘值的計算
#如何計算全域LRU時鐘值的,以用來判斷資料存取的時效性 -
鍵值對LRU時鐘值的初始化與更新
#哪些函數中對每個鍵值對對應的LRU時鐘值,進行初始化與更新 -
近似LRU演算法的實際執行
如何執行近似LRU演算法,即何時觸發資料淘汰,以及實際淘汰的機制實現
#
每個KV對的LRU時鐘值是如何計算的? Redis Server使用實例層級的全域LRU時鐘,每個KV對的LRU time會依照全域LRU時鐘進行設定。
這全域LRU時鐘保存在Redis全域變數server的成員變數lruclock
#當Redis Server啟動後,呼叫initServerConfig初始化各項參數時,會呼叫getLRUClock設定lruclock的值:
#於是,就得注意,**若一個資料前後兩次存取的時間間隔<1s,那這兩次訪問的時間戳記是一樣的! **因為LRU時鐘精度就是1s,它無法區分間隔小於1秒的不同時間戳記!
getLRUClock函數將會得到的UNIX時間戳,除以LRU_CLOCK_RESOLUTION後,就得到了以LRU時脈精確度計算的UNIX時間戳,也就是目前的LRU時脈值。
getLRUClock會把LRU時鐘值和巨集定義LRU_CLOCK_MAX(LRU時脈能表示的最大值)做與運算。
所以預設情況下,全域LRU時鐘值是以1s為精度計算得UNIX時間戳,且是在initServerConfig中進行的初始化。
那Redis Server運作過程中,全域LRU時鐘值是如何更新的?和Redis Server在事件驅動框架中,定期運行的時間事件所對應的serverCron有關。
serverCron作為時間事件的回呼函數,本身會週期性執行,其頻率值由redis.conf的hz配置項目決定,預設值10,即serverCron函數會每100ms( 1s/10 = 100ms)運行一次。在serverCron中,全域LRU時鐘值就會依照該函數執行頻率,定期呼叫getLRUClock進行更新:
這樣,每個KV對就能從全域LRU時鐘取得最新訪問時間戳。
對於每個KV對,它對應的redisObject.lru在哪些函數進行初始化和更新的呢?
2.2 鍵值對LRU時鐘值的初始化與更新
對於一個KV對,其LRU時鐘值最初是在這KV對被創建時,進行初始化設定的,這初始化操作在createObject函數中調用,當Redis要建立一個KV對,就會調用該函數。
createObject除了會為redisObject分配記憶體空間,還會根據maxmemory_policy配置,初始化設定redisObject.lru。
- 若maxmemory_policy=LFU,則lru變數值會被初始化設定為LFU演算法的計算值
- maxmemory_policy≠LFU,則createObject呼叫LRU_CLOCK設定lru值,即KV對對應值的LRU時鐘值。
LRU_CLOCK傳回目前全域LRU時鐘值。因為一個KV對一旦被創建,就相當於有了次訪問,其對應LRU時鐘值就表示了它的訪問時間戳:
lookupKey。
lookupKey會從全域雜湊表中尋找要存取的KV對。若該KV對存在,則lookupKey會根據maxmemory_policy的配置值,來更新鍵值對的LRU時鐘值,也就是它的存取時間戳記。 而當maxmemory_policy沒有配置為LFU策略時,lookupKey函數就會呼叫LRU_CLOCK函數,來取得目前的全域LRU時鐘值,並將其賦值為鍵值對的redisObject結構體中的lru變數2.3.1 何時觸發演算法執行?
近似LRU主要邏輯在performEvictions。 performEvictions被evictionTimeProc調用,而evictionTimeProc函數又是被processCommand調用。 processCommand,Redis處理每個指令時都會呼叫:然後,isSafeToPerformEvictions也會再根據下列條件判斷是否繼續執行performEvictions:
#
一旦performEvictions被調用,且maxmemory-policy被設定為allkeys-lru或volatile-lru,近似LRU就被觸發執行了。
2.3.2 近似LRU具體執行過程
執行可分成下列步驟:
2.3.2.1 判斷目前記憶體使用量
呼叫getMaxmemoryState評估目前記憶體使用情況,判斷目前Redis Server使用記憶體容量是否超過maxmemory配置值。
若未超過maxmemory,則回傳C_OK,performEvictions也會直接回傳。
getMaxmemoryState評估目前記憶體使用量的時候,若發現已使用記憶體超出maxmemory,會計算出需釋放的記憶體量。這個釋放記憶體大小=已使用內存量-maxmemory。
但已使用記憶體量並不包括用於主從複製的複製緩衝區大小,這是getMaxmemoryState透過呼叫freeMemoryGetNotCountedMemory計算的。
而若目前Server所使用的記憶體量超出maxmemory上限,則performEvictions會執行while循環淘汰資料釋放記憶體。
為淘汰數據,Redis定義數組EvictionPoolLRU,保存待淘汰的候選KV對,元素類型是evictionPoolEntry結構體,保存了待淘汰KV對的空閒時間idle、對應K等資訊:
這樣,Redis Server在執行initSever進行初始化時,會呼叫evictionPoolAlloc為EvictionPoolLRU陣列分配記憶體空間,該陣列大小由EVPOOL_SIZE決定,預設可保存16個待淘汰的候選KV對。
performEvictions在淘汰資料的循環流程中,就會更新這個待淘汰的候選KV對集合,也就是EvictionPoolLRU陣列。
2.3.2.2 更新待淘汰的候選KV對集合
performEvictions呼叫evictionPoolPopulate,其會先呼叫dictGetSomeKeys,從待取樣雜湊表隨機取得一定數量K :
- dictGetSomeKeys取樣的雜湊表,由maxmemory_policy配置項目決定:
- 若maxmemory_policy=allkeys_lru,則待取樣雜湊表是Redis Server的全域雜湊表,即在所有KV對中取樣
- 否則,待取樣雜湊表就是儲存設定了TTL的K的雜湊表。
- dictGetSomeKeys取樣的K的數量由配置項maxmemory-samples決定,預設為5:
於是,dictGetSomeKeys傳回取樣的KV對集合。 evictionPoolPopulate根據實際取樣到的KV對數量count,執行循環:呼叫estimateObjectIdleTime計算在取樣集合中的每一個KV對的空閒時間:
#接著,evictionPoolPopulate遍歷待淘汰的候選KV對集合,即EvictionPoolLRU數組,嘗試把採樣的每個KV對插入EvictionPoolLRU數組,取決於如下條件之一:
- 能在數組中找到一個尚未插入KV對的空位
- 能在數組中找到一個KV對的空閒時間<採樣KV對的空閒時間
有一成立,evictionPoolPopulate就能把採樣KV對插入EvictionPoolLRU數組。等所有取樣鍵值對都處理完後,evictionPoolPopulate函數就完成對待淘汰候選鍵值對集合的更新了。
接下來,performEvictions開始選擇最終被淘汰的KV對。
2.3.2.3 選擇被淘汰的KV對並刪除
因evictionPoolPopulate已更新EvictionPoolLRU數組,且該數組裡的K,是按空閒時間從小到大排好序了。所以,performEvictions遍歷一次EvictionPoolLRU數組,從數組的最後一個K開始選擇,若選到的K非空,就把它當作最終淘汰的K。
這個過程執行邏輯:
一旦選到被淘汰的K,performEvictions就會根據Redis server的惰性刪除配置,執行同步刪除或非同步刪除:
至此,performEvictions就淘汰了一個K。若此時釋放的記憶體空間還不夠,即沒有達到待釋放空間,則performEvictions還會重複執行前面所說的更新待淘汰候選KV對集合、選擇最終淘汰K的過程,直到滿足待釋放空間的大小要求。
performEvictions流程:
近似LRU演算法並未使用耗時且耗空間的鍊錶,而使用固定大小的待淘汰資料集合,每次隨機選擇一些K加入待淘汰資料集合。
最後,依待淘汰集合中K的空閒時間長度,刪除空閒時間最長的K。
總結
根據LRU演算法的基本原理,發現若嚴格以基本原理實作LRU演算法,則開發的系統就需要額外記憶體空間保存LRU鍊錶,系統運作時也會受到LRU鍊錶操作的開銷影響。
而Redis的記憶體資源和效能都很重要,所以Redis實作近似LRU演算法:
- 首先是設定了全域LRU時鐘,並且在KV對建立時取得全域LRU時鐘值作為存取時間戳,及每次造訪時取得全域LRU時鐘值,更新存取時間戳
- 然後,當Redis每處理一個指令,都會呼叫performEvictions判斷是否需釋放記憶體。若已使用記憶體超出maxmemory,則隨機選擇一些KV對,組成待淘汰候選集合,並根據它們的存取時間戳,選出最舊資料淘汰
一個演算法的基本原理和演算法的實際執行,在系統開發上會有一定折中,需綜合考慮所開發的系統,在資源和性能方面的要求,以避免嚴格按照演算法實現帶來的資源和性能開銷。
推薦學習:Redis教學
以上是完全掌握Redis的LRU快取淘汰演算法實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Redis的核心功能是高性能的內存數據存儲和處理系統。 1)高速數據訪問:Redis將數據存儲在內存中,提供微秒級別的讀寫速度。 2)豐富的數據結構:支持字符串、列表、集合等,適應多種應用場景。 3)持久化:通過RDB和AOF方式將數據持久化到磁盤。 4)發布訂閱:可用於消息隊列或實時通信系統。

Redis支持多種數據結構,具體包括:1.字符串(String),適合存儲單一值數據;2.列表(List),適用於隊列和棧;3.集合(Set),用於存儲不重複數據;4.有序集合(SortedSet),適用於排行榜和優先級隊列;5.哈希表(Hash),適合存儲對像或結構化數據。

Redis計數器是一種使用Redis鍵值對存儲來實現計數操作的機制,包含以下步驟:創建計數器鍵、增加計數、減少計數、重置計數和獲取計數。 Redis計數器的優勢包括速度快、高並發、持久性和簡單易用。它可用於用戶訪問計數、實時指標跟踪、遊戲分數和排名以及訂單處理計數等場景。

使用 Redis 命令行工具 (redis-cli) 可通過以下步驟管理和操作 Redis:連接到服務器,指定地址和端口。使用命令名稱和參數向服務器發送命令。使用 HELP 命令查看特定命令的幫助信息。使用 QUIT 命令退出命令行工具。

Redis集群模式通過分片將Redis實例部署到多個服務器,提高可擴展性和可用性。搭建步驟如下:創建奇數個Redis實例,端口不同;創建3個sentinel實例,監控Redis實例並進行故障轉移;配置sentinel配置文件,添加監控Redis實例信息和故障轉移設置;配置Redis實例配置文件,啟用集群模式並指定集群信息文件路徑;創建nodes.conf文件,包含各Redis實例的信息;啟動集群,執行create命令創建集群並指定副本數量;登錄集群執行CLUSTER INFO命令驗證集群狀態;使

要從 Redis 讀取隊列,需要獲取隊列名稱、使用 LPOP 命令讀取元素,並處理空隊列。具體步驟如下:獲取隊列名稱:以 "queue:" 前綴命名,如 "queue:my-queue"。使用 LPOP 命令:從隊列頭部彈出元素並返回其值,如 LPOP queue:my-queue。處理空隊列:如果隊列為空,LPOP 返回 nil,可先檢查隊列是否存在再讀取元素。

Redis 集群中使用 zset:zset 是一種有序集合,將元素與評分關聯。分片策略: a. 哈希分片:根據 zset 鍵的哈希值分佈。 b. 範圍分片:根據元素評分劃分為範圍,並將每個範圍分配給不同的節點。讀寫操作: a. 讀操作:如果 zset 鍵屬於當前節點的分片,則在本地處理;否則,路由到相應的分片。 b. 寫入操作:始終路由到持有 zset 鍵的分片。

如何清空 Redis 數據:使用 FLUSHALL 命令清除所有鍵值。使用 FLUSHDB 命令清除當前選定數據庫的鍵值。使用 SELECT 切換數據庫,再使用 FLUSHDB 清除多個數據庫。使用 DEL 命令刪除特定鍵。使用 redis-cli 工具清空數據。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

記事本++7.3.1
好用且免費的程式碼編輯器

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

SublimeText3 Linux新版
SublimeText3 Linux最新版