淺談Redis中LFU算法源碼解析
Redis 的 LFU(Least Frequently Used,最不經(jīng)常使用)淘汰算法主要用于 maxmemory-policy
設(shè)置為 allkeys-lfu
或 volatile-lfu
時(shí),以最少使用頻率的鍵進(jìn)行淘汰。其核心實(shí)現(xiàn)涉及到 訪問頻率計(jì)數(shù) 和 時(shí)間衰減機(jī)制,源碼主要集中在 src/server.c
和 src/evict.c
文件中。
1. LFU 計(jì)數(shù)存儲
Redis 采用 8-bit 的 LRU
字段 來存儲訪問頻率計(jì)數(shù),存儲在 robj
結(jié)構(gòu)體的 lru
字段中:
struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; // 用于 LRU/LFU 計(jì)算 int refcount; void *ptr; };
其中 lru
變量的 8-bit 空間被拆分:
- 前 6-bit(
counter
):用于存儲訪問計(jì)數(shù),最大值 63。 - 后 2-bit(
clock
):用于時(shí)間衰減計(jì)算。
2. 訪問計(jì)數(shù)的計(jì)算
LFU 計(jì)數(shù)在每次訪問鍵時(shí)都會遞增,但遞增方式不是簡單 +1
,而是使用 對數(shù)增長 方式,避免某些鍵因高訪問量而壟斷:
unsigned long LFUDecrAndReturn(robj *o) { unsigned long counter = LFUGetCounter(o); if (counter == 0) return 0; if (rand() % (counter + 1) == 0) counter--; LFUSetCounter(o, counter); return counter; }
計(jì)數(shù)增長時(shí):
int LFUIncrAndReturn(robj *o) { unsigned long counter = LFUGetCounter(o); if (counter < 63) { if (rand() % (counter + 1) == 0) counter++; } LFUSetCounter(o, counter); return counter; }
這意味著:
- 初始時(shí)計(jì)數(shù)增長較快 (
1 → 2 → 3
…) - 計(jì)數(shù)越高,增長概率越低(符合 對數(shù)曲線)
- 這樣可以防止某些高訪問量鍵長期存活。
3. LFU 訪問頻率的衰減
由于有些數(shù)據(jù)可能短期內(nèi)訪問頻繁,但長期不再被訪問,因此 Redis 采用了 時(shí)間衰減機(jī)制:
每 1 分鐘 遞減一次訪問計(jì)數(shù)。
使用 2-bit 記錄最近訪問的時(shí)間 lfu_clock
,每隔 60s 觸發(fā) 衰減:
#define LFU_INIT_VAL 5 // 初始訪問計(jì)數(shù) unsigned long LFUDecrAndReturn(robj *o) { unsigned long counter = LFUGetCounter(o); if (counter == 0) return 0; if (rand() % (counter + 1) == 0) counter--; LFUSetCounter(o, counter); return counter; }
該方法會按照一定概率減少計(jì)數(shù),確保 近期訪問過的鍵不會輕易被淘汰,而 長時(shí)間未訪問的鍵會逐步淘汰。
4. 淘汰策略
當(dāng) maxmemory
超出時(shí),Redis 需要淘汰一部分?jǐn)?shù)據(jù),LFU 主要執(zhí)行:
遍歷數(shù)據(jù),找到訪問計(jì)數(shù)最小的鍵。
采用 volatile-lfu
或 allkeys-lfu
進(jìn)行數(shù)據(jù)刪除:
evictionPoolPopulate(dict *sample_dict) { // 從字典中隨機(jī)采樣 N 個(gè)鍵 for (i = 0; i < EVPOOL_SIZE; i++) { lfu = LFUGetCounter(entry); if (lfu < min_lfu) { min_lfu = lfu; min_entry = entry; } } // 淘汰訪問次數(shù)最少的 dictDelete(sample_dict, min_entry); }
采用 近似隨機(jī)采樣,而不是遍歷所有鍵,提高效率。
5. 關(guān)鍵總結(jié)
- 存儲方式:使用
robj.lru
變量的 8-bit 空間存儲訪問計(jì)數(shù)和時(shí)間信息。 - 訪問計(jì)數(shù)增長:采用對數(shù)增長策略,防止單個(gè)鍵因訪問量過大而占用內(nèi)存。
- 時(shí)間衰減:每分鐘對訪問頻率計(jì)數(shù)進(jìn)行衰減,確保長期未訪問的鍵被淘汰。
- 淘汰策略:采樣多個(gè)鍵,找到訪問計(jì)數(shù)最少的鍵進(jìn)行刪除。
Redis 的 LFU 機(jī)制相比 LRU 更適用于熱點(diǎn)數(shù)據(jù)訪問場景,避免了某些短期流行的鍵占用大量緩存,同時(shí)也能讓真正的 高頻訪問數(shù)據(jù) 存活更久。
到此這篇關(guān)于淺談Redis中LFU算法源碼解析的文章就介紹到這了,更多相關(guān)Redis LFU算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
手把手教你使用redis實(shí)現(xiàn)排行榜功能
使用Redis中有序集合的特性來實(shí)現(xiàn)排行榜是又好又快的選擇,一般排行榜都是有實(shí)效性的,比如“用戶積分榜”,下面這篇文章主要給大家介紹了關(guān)于使用redis實(shí)現(xiàn)排行榜功能的相關(guān)資料,需要的朋友可以參考下2023-04-04Redis中的3種特殊數(shù)據(jù)結(jié)構(gòu)詳解
在本文中,我們對三種特殊的數(shù)據(jù)類型進(jìn)行了介紹,它們分別是geospatial(地理空間數(shù)據(jù)類型)、HyperLogLogs和Bitmaps(位圖),這些數(shù)據(jù)類型在不同的領(lǐng)域和應(yīng)用中發(fā)揮著重要作用,并且具有各自獨(dú)特的特性和用途,對Redis特殊數(shù)據(jù)結(jié)構(gòu)相關(guān)知識感興趣的朋友一起看看吧2024-02-02Redis 使用跳表實(shí)現(xiàn)有序集合的方法
Redis有序集合底層為什么使用跳表而非其他數(shù)據(jù)結(jié)構(gòu)如平衡樹、紅黑樹或B+樹的原因在于其特殊的設(shè)計(jì)和應(yīng)用場景,跳表提供了與平衡樹類似的效率,同時(shí)實(shí)現(xiàn)更簡單,調(diào)試和修改也更加容易,感興趣的朋友一起看看吧2024-09-09redis的list數(shù)據(jù)類型相關(guān)命令介紹及使用
本文主要介紹了redis的list數(shù)據(jù)類型相關(guān)命令介紹及使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01odoo中使用redis實(shí)現(xiàn)緩存的步驟
這篇文章主要介紹了odoo中使用redis實(shí)現(xiàn)緩存的步驟,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04