做電影視頻網(wǎng)站賺錢(qián)嘛靠譜的代寫(xiě)平臺(tái)
Redis過(guò)期刪除策略和內(nèi)存淘汰策略有什么區(qū)別?
- 前言
- 過(guò)期刪除策略
- 如何設(shè)置過(guò)期時(shí)間?
- 如何判定 key 已過(guò)期了?
- 過(guò)期刪除策略有哪些?
- Redis 過(guò)期刪除策略是什么?
- 內(nèi)存淘汰策略
- 如何設(shè)置 Redis 最大運(yùn)行內(nèi)存?
- Redis 內(nèi)存淘汰策略有哪些?
- LRU 算法和 LFU 算法有什么區(qū)別?
- 總結(jié)
前言
Redis 的「內(nèi)存淘汰策略」和「過(guò)期刪除策略」,很多小伙伴容易混淆,這兩個(gè)機(jī)制雖然都是做刪除的操作,但是觸發(fā)的條件和使用的策略都是不同的。
今天就跟大家理一理,「內(nèi)存淘汰策略」和「過(guò)期刪除策略」。
話不多說(shuō),發(fā)車!
過(guò)期刪除策略
Redis 是可以對(duì) key 設(shè)置過(guò)期時(shí)間的,因此需要有相應(yīng)的機(jī)制將已過(guò)期的鍵值對(duì)刪除,而做這個(gè)工作的就是過(guò)期鍵值刪除策略。
如何設(shè)置過(guò)期時(shí)間?
先說(shuō)一下對(duì) key 設(shè)置過(guò)期時(shí)間的命令。 設(shè)置 key 過(guò)期時(shí)間的命令一共有 4 個(gè):
- expire :設(shè)置 key 在 n 秒后過(guò)期,比如 expire key 100 表示設(shè)置 key 在 100 秒后過(guò)期;
- pexpire :設(shè)置 key 在 n 毫秒后過(guò)期,比如 pexpire key2 100000 表示設(shè)置 key2 在 100000 毫秒(100 秒)后過(guò)期。
- expireat :設(shè)置 key 在某個(gè)時(shí)間戳(精確到秒)之后過(guò)期,比如 expireat key3 1655654400 表示 key3 在時(shí)間戳 1655654400 后過(guò)期(精確到秒);
- pexpireat :設(shè)置 key 在某個(gè)時(shí)間戳(精確到毫秒)之后過(guò)期,比如 pexpireat key4 1655654400000 表示 key4 在時(shí)間戳 1655654400000 后過(guò)期(精確到毫秒)
當(dāng)然,在設(shè)置字符串時(shí),也可以同時(shí)對(duì) key 設(shè)置過(guò)期時(shí)間,共有 3 種命令:
- set ex :設(shè)置鍵值對(duì)的時(shí)候,同時(shí)指定過(guò)期時(shí)間(精確到秒);
- set px :設(shè)置鍵值對(duì)的時(shí)候,同時(shí)指定過(guò)期時(shí)間(精確到毫秒);
- setex :設(shè)置鍵值對(duì)的時(shí)候,同時(shí)指定過(guò)期時(shí)間(精確到秒)。
如果你想查看某個(gè) key 剩余的存活時(shí)間,可以使用 TTL 命令。
# 設(shè)置鍵值對(duì)的時(shí)候,同時(shí)指定過(guò)期時(shí)間位 60 秒
> setex key1 60 value1
OK# 查看 key1 過(guò)期時(shí)間還剩多少
> ttl key1
(integer) 56
> ttl key1
(integer) 52
如果突然反悔,取消 key 的過(guò)期時(shí)間,則可以使用 PERSIST 命令。
# 取消 key1 的過(guò)期時(shí)間
> persist key1
(integer) 1# 使用完 persist 命令之后,
# 查下 key1 的存活時(shí)間結(jié)果是 -1,表明 key1 永不過(guò)期
> ttl key1
(integer) -1
如何判定 key 已過(guò)期了?
每當(dāng)我們對(duì)一個(gè) key 設(shè)置了過(guò)期時(shí)間時(shí),Redis 會(huì)把該 key 帶上過(guò)期時(shí)間存儲(chǔ)到一個(gè)過(guò)期字典(expires dict)中,也就是說(shuō)「過(guò)期字典」保存了數(shù)據(jù)庫(kù)中所有 key 的過(guò)期時(shí)間。
過(guò)期字典存儲(chǔ)在 redisDb 結(jié)構(gòu)中,如下:
typedef struct redisDb {dict *dict; /* 數(shù)據(jù)庫(kù)鍵空間,存放著所有的鍵值對(duì) */dict *expires; /* 鍵的過(guò)期時(shí)間 */....
} redisDb;
過(guò)期字典數(shù)據(jù)結(jié)構(gòu)結(jié)構(gòu)如下:
- 過(guò)期字典的 key 是一個(gè)指針,指向某個(gè)鍵對(duì)象;
- 過(guò)期字典的 value 是一個(gè) long long 類型的整數(shù),這個(gè)整數(shù)保存了 key 的過(guò)期時(shí)間;
過(guò)期字典的數(shù)據(jù)結(jié)構(gòu)如下圖所示:
字典實(shí)際上是哈希表,哈希表的最大好處就是讓我們可以用 O(1) 的時(shí)間復(fù)雜度來(lái)快速查找。當(dāng)我們查詢一個(gè) key 時(shí),Redis 首先檢查該 key 是否存在于過(guò)期字典中:
- 如果不在,則正常讀取鍵值;
- 如果存在,則會(huì)獲取該 key 的過(guò)期時(shí)間,然后與當(dāng)前系統(tǒng)時(shí)間進(jìn)行比對(duì),如果比系統(tǒng)時(shí)間大,那就沒(méi)有過(guò)期,否則判定該 key 已過(guò)期。
過(guò)期鍵判斷流程如下圖所示:
過(guò)期刪除策略有哪些?
在說(shuō) Redis 過(guò)期刪除策略之前,先跟大家介紹下,常見(jiàn)的三種過(guò)期刪除策略:
- 定時(shí)刪除;
- 惰性刪除;
- 定期刪除;
接下來(lái),分別分析它們的優(yōu)缺點(diǎn)。
- 定時(shí)刪除策略是怎么樣的?
定時(shí)刪除策略的做法是,在設(shè)置 key 的過(guò)期時(shí)間時(shí),同時(shí)創(chuàng)建一個(gè)定時(shí)事件,當(dāng)時(shí)間到達(dá)時(shí),由事件處理器自動(dòng)執(zhí)行 key 的刪除操作。
定時(shí)刪除策略的優(yōu)點(diǎn):
可以保證過(guò)期 key 會(huì)被盡快刪除,也就是內(nèi)存可以被盡快地釋放。因此,定時(shí)刪除對(duì)內(nèi)存是最友好的。
定時(shí)刪除策略的缺點(diǎn):
在過(guò)期 key 比較多的情況下,刪除過(guò)期 key 可能會(huì)占用相當(dāng)一部分 CPU 時(shí)間,在內(nèi)存不緊張但 CPU 時(shí)間緊張的情況下,將 CPU 時(shí)間用于刪除和當(dāng)前任務(wù)無(wú)關(guān)的過(guò)期鍵上,無(wú)疑會(huì)對(duì)服務(wù)器的響應(yīng)時(shí)間和吞吐量造成影響。所以,定時(shí)刪除策略對(duì) CPU 不友好。
- 惰性刪除策略是怎么樣的?
惰性刪除策略的做法是,不主動(dòng)刪除過(guò)期鍵,每次從數(shù)據(jù)庫(kù)訪問(wèn) key 時(shí),都檢測(cè) key 是否過(guò)期,如果過(guò)期則刪除該 key。
惰性刪除策略的優(yōu)點(diǎn):
因?yàn)槊看卧L問(wèn)時(shí),才會(huì)檢查 key 是否過(guò)期,所以此策略只會(huì)使用很少的系統(tǒng)資源,因此,惰性刪除策略對(duì) CPU 時(shí)間最友好。
惰性刪除策略的缺點(diǎn):
如果一個(gè) key 已經(jīng)過(guò)期,而這個(gè) key 又仍然保留在數(shù)據(jù)庫(kù)中,那么只要這個(gè)過(guò)期 key 一直沒(méi)有被訪問(wèn),它所占用的內(nèi)存就不會(huì)釋放,造成了一定的內(nèi)存空間浪費(fèi)。所以,惰性刪除策略對(duì)內(nèi)存不友好。
- 定期刪除策略是怎么樣的?
定期刪除策略的做法是,每隔一段時(shí)間「隨機(jī)」從數(shù)據(jù)庫(kù)中取出一定數(shù)量的 key 進(jìn)行檢查,并刪除其中的過(guò)期key。
定期刪除策略的優(yōu)點(diǎn):
通過(guò)限制刪除操作執(zhí)行的時(shí)長(zhǎng)和頻率,來(lái)減少刪除操作對(duì) CPU 的影響,同時(shí)也能刪除一部分過(guò)期的數(shù)據(jù)減少了過(guò)期鍵對(duì)空間的無(wú)效占用。
定期刪除策略的缺點(diǎn):
內(nèi)存清理方面沒(méi)有定時(shí)刪除效果好,同時(shí)沒(méi)有惰性刪除使用的系統(tǒng)資源少。
難以確定刪除操作執(zhí)行的時(shí)長(zhǎng)和頻率。如果執(zhí)行的太頻繁,定期刪除策略變得和定時(shí)刪除策略一樣,對(duì)CPU不友好;如果執(zhí)行的太少,那又和惰性刪除一樣了,過(guò)期 key 占用的內(nèi)存不會(huì)及時(shí)得到釋放。
Redis 過(guò)期刪除策略是什么?
前面介紹了三種過(guò)期刪除策略,每一種都有優(yōu)缺點(diǎn),僅使用某一個(gè)策略都不能滿足實(shí)際需求。
所以, Redis 選擇「惰性刪除+定期刪除」這兩種策略配和使用,以求在合理使用 CPU 時(shí)間和避免內(nèi)存浪費(fèi)之間取得平衡。
- Redis 是怎么實(shí)現(xiàn)惰性刪除的?
Redis 的惰性刪除策略由 db.c 文件中的 expireIfNeeded 函數(shù)實(shí)現(xiàn),代碼如下:
int expireIfNeeded(redisDb *db, robj *key) {// 判斷 key 是否過(guò)期if (!keyIsExpired(db,key)) return 0;..../* 刪除過(guò)期鍵 */....// 如果 server.lazyfree_lazy_expire 為 1 表示異步刪除,反之同步刪除;return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :dbSyncDelete(db,key);
}
Redis 在訪問(wèn)或者修改 key 之前,都會(huì)調(diào)用 expireIfNeeded 函數(shù)對(duì)其進(jìn)行檢查,檢查 key 是否過(guò)期:
如果過(guò)期,則刪除該 key,至于選擇異步刪除,還是選擇同步刪除,根據(jù) lazyfree_lazy_expire 參數(shù)配置決定(Redis 4.0版本開(kāi)始提供參數(shù)),然后返回 null 客戶端;
如果沒(méi)有過(guò)期,不做任何處理,然后返回正常的鍵值對(duì)給客戶端;
惰性刪除的流程圖如下:
- Redis 是怎么實(shí)現(xiàn)定期刪除的?
再回憶一下,定期刪除策略的做法:每隔一段時(shí)間「隨機(jī)」從數(shù)據(jù)庫(kù)中取出一定數(shù)量的 key 進(jìn)行檢查,并刪除其中的過(guò)期key。
- 這個(gè)間隔檢查的時(shí)間是多長(zhǎng)呢?
在 Redis 中,默認(rèn)每秒進(jìn)行 10 次過(guò)期檢查一次數(shù)據(jù)庫(kù),此配置可通過(guò) Redis 的配置文件 redis.conf 進(jìn)行配置,配置鍵為 hz 它的默認(rèn)值是 hz 10。
特別強(qiáng)調(diào)下,每次檢查數(shù)據(jù)庫(kù)并不是遍歷過(guò)期字典中的所有 key,而是從數(shù)據(jù)庫(kù)中隨機(jī)抽取一定數(shù)量的 key 進(jìn)行過(guò)期檢查。
- 隨機(jī)抽查的數(shù)量是多少呢?
我查了下源碼,定期刪除的實(shí)現(xiàn)在 expire.c 文件下的 activeExpireCycle 函數(shù)中,其中隨機(jī)抽查的數(shù)量由 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 定義的,它是寫(xiě)死在代碼中的,數(shù)值是 20。
也就是說(shuō),數(shù)據(jù)庫(kù)每輪抽查時(shí),會(huì)隨機(jī)選擇 20 個(gè) key 判斷是否過(guò)期。
接下來(lái),詳細(xì)說(shuō)說(shuō) Redis 的定期刪除的流程:
- 從過(guò)期字典中隨機(jī)抽取 20 個(gè) key;
- 檢查這 20 個(gè) key 是否過(guò)期,并刪除已過(guò)期的 key;
- 如果本輪檢查的已過(guò)期 key 的數(shù)量,超過(guò) 5 個(gè)(20/4),也就是「已過(guò)期 key 的數(shù)量」占比「隨機(jī)抽取 key 的數(shù)量」大于 25%,則繼續(xù)重復(fù)步驟 1;如果已過(guò)期的 key 比例小于 25%,則停止繼續(xù)刪除過(guò)期 key,然后等待下一輪再檢查。
可以看到,定期刪除是一個(gè)循環(huán)的流程。
那 Redis 為了保證定期刪除不會(huì)出現(xiàn)循環(huán)過(guò)度,導(dǎo)致線程卡死現(xiàn)象,為此增加了定期刪除循環(huán)流程的時(shí)間上限,默認(rèn)不會(huì)超過(guò) 25ms。
針對(duì)定期刪除的流程,我寫(xiě)了個(gè)偽代碼:
do {//已過(guò)期的數(shù)量expired = 0;//隨機(jī)抽取的數(shù)量num = 20;while (num--) {//1. 從過(guò)期字典中隨機(jī)抽取 1 個(gè) key//2. 判斷該 key 是否過(guò)期,如果已過(guò)期則進(jìn)行刪除,同時(shí)對(duì) expired++}// 超過(guò)時(shí)間限制則退出if (timelimit_exit) return;/* 如果本輪檢查的已過(guò)期 key 的數(shù)量,超過(guò) 25%,則繼續(xù)隨機(jī)抽查,否則退出本輪檢查 */
} while (expired > 20/4);
定期刪除的流程如下:
內(nèi)存淘汰策略
前面說(shuō)的過(guò)期刪除策略,是刪除已過(guò)期的 key,而當(dāng) Redis 的運(yùn)行內(nèi)存已經(jīng)超過(guò) Redis 設(shè)置的最大內(nèi)存之后,則會(huì)使用內(nèi)存淘汰策略刪除符合條件的 key,以此來(lái)保障 Redis 高效的運(yùn)行。
如何設(shè)置 Redis 最大運(yùn)行內(nèi)存?
在配置文件 redis.conf 中,可以通過(guò)參數(shù) maxmemory 來(lái)設(shè)定最大運(yùn)行內(nèi)存,只有在 Redis 的運(yùn)行內(nèi)存達(dá)到了我們?cè)O(shè)置的最大運(yùn)行內(nèi)存,才會(huì)觸發(fā)內(nèi)存淘汰策略。 不同位數(shù)的操作系統(tǒng),maxmemory 的默認(rèn)值是不同的:
在 64 位操作系統(tǒng)中,maxmemory 的默認(rèn)值是 0,表示沒(méi)有內(nèi)存大小限制,那么不管用戶存放多少數(shù)據(jù)到 Redis 中,Redis 也不會(huì)對(duì)可用內(nèi)存進(jìn)行檢查,直到 Redis 實(shí)例因內(nèi)存不足而崩潰也無(wú)作為。
在 32 位操作系統(tǒng)中,maxmemory 的默認(rèn)值是 3G,因?yàn)?32 位的機(jī)器最大只支持 4GB 的內(nèi)存,而系統(tǒng)本身就需要一定的內(nèi)存資源來(lái)支持運(yùn)行,所以 32 位操作系統(tǒng)限制最大 3 GB 的可用內(nèi)存是非常合理的,這樣可以避免因?yàn)閮?nèi)存不足而導(dǎo)致 Redis 實(shí)例崩潰。
Redis 內(nèi)存淘汰策略有哪些?
Redis 內(nèi)存淘汰策略共有八種,這八種策略大體分為「不進(jìn)行數(shù)據(jù)淘汰」和「進(jìn)行數(shù)據(jù)淘汰」兩類策略。
- 不進(jìn)行數(shù)據(jù)淘汰的策略
noeviction(Redis3.0之后,默認(rèn)的內(nèi)存淘汰策略) :它表示當(dāng)運(yùn)行內(nèi)存超過(guò)最大設(shè)置內(nèi)存時(shí),不淘汰任何數(shù)據(jù),這時(shí)如果有新的數(shù)據(jù)寫(xiě)入,會(huì)報(bào)錯(cuò)通知禁止寫(xiě)入,不淘汰任何數(shù)據(jù),但是如果沒(méi)用數(shù)據(jù)寫(xiě)入的話,只是單純的查詢或者刪除操作的話,還是可以正常工作。
- 進(jìn)行數(shù)據(jù)淘汰的策略
針對(duì)「進(jìn)行數(shù)據(jù)淘汰」這一類策略,又可以細(xì)分為「在設(shè)置了過(guò)期時(shí)間的數(shù)據(jù)中進(jìn)行淘汰」和「在所有數(shù)據(jù)范圍內(nèi)進(jìn)行淘汰」這兩類策略。
在設(shè)置了過(guò)期時(shí)間的數(shù)據(jù)中進(jìn)行淘汰:
- volatile-random:隨機(jī)淘汰設(shè)置了過(guò)期時(shí)間的任意鍵值;
- volatile-ttl:優(yōu)先淘汰更早過(guò)期的鍵值。
- volatile-lru(Redis3.0 之前,默認(rèn)的內(nèi)存淘汰策略):淘汰所有設(shè)置了過(guò)期時(shí)間的鍵值中,最久未使用的鍵值;
- volatile-lfu(Redis 4.0 后新增的內(nèi)存淘汰策略):淘汰所有設(shè)置了過(guò)期時(shí)間的鍵值中,最少使用的鍵值;
在所有數(shù)據(jù)范圍內(nèi)進(jìn)行淘汰:
- allkeys-random:隨機(jī)淘汰任意鍵值;
- allkeys-lru:淘汰整個(gè)鍵值中最久未使用的鍵值;
- allkeys-lfu(Redis 4.0 后新增的內(nèi)存淘汰策略):淘汰整個(gè)鍵值中最少使用的鍵值。
如何查看當(dāng)前 Redis 使用的內(nèi)存淘汰策略?
可以使用 config get maxmemory-policy 命令,來(lái)查看當(dāng)前 Redis 的內(nèi)存淘汰策略,命令如下:
127.0.0.1:6379> config get maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
可以看出,當(dāng)前 Redis 使用的是 noeviction 類型的內(nèi)存淘汰策略,它是 Redis 3.0 之后默認(rèn)使用的內(nèi)存淘汰策略,表示當(dāng)運(yùn)行內(nèi)存超過(guò)最大設(shè)置內(nèi)存時(shí),不淘汰任何數(shù)據(jù),但新增操作會(huì)報(bào)錯(cuò)。
如何修改 Redis 內(nèi)存淘汰策略?
設(shè)置內(nèi)存淘汰策略有兩種方法:
- 方式一:通過(guò)“config set maxmemory-policy <策略>”命令設(shè)置。它的優(yōu)點(diǎn)是設(shè)置之后立即生效,不需要重啟 Redis 服務(wù),缺點(diǎn)是重啟 Redis 之后,設(shè)置就會(huì)失效。
- 方式二:通過(guò)修改 Redis 配置文件修改,設(shè)置“maxmemory-policy <策略>”,它的優(yōu)點(diǎn)是重啟 Redis 服務(wù)后配置不會(huì)丟失,缺點(diǎn)是必須重啟 Redis 服務(wù),設(shè)置才能生效。
LRU 算法和 LFU 算法有什么區(qū)別?
LFU 內(nèi)存淘汰算法是 Redis 4.0 之后新增內(nèi)存淘汰策略,那為什么要新增這個(gè)算法?那肯定是為了解決 LRU 算法的問(wèn)題。
接下來(lái),就看看這兩個(gè)算法有什么區(qū)別?Redis 又是如何實(shí)現(xiàn)這兩個(gè)算法的?
什么是 LRU 算法?
LRU 全稱是 Least Recently Used 翻譯為最近最少使用,會(huì)選擇淘汰最近最少使用的數(shù)據(jù)。
傳統(tǒng) LRU 算法的實(shí)現(xiàn)是基于「鏈表」結(jié)構(gòu),鏈表中的元素按照操作順序從前往后排列,最新操作的鍵會(huì)被移動(dòng)到表頭,當(dāng)需要內(nèi)存淘汰時(shí),只需要?jiǎng)h除鏈表尾部的元素即可,因?yàn)殒湵砦膊康脑鼐痛碜罹梦幢皇褂玫脑亍?/p>
Redis 并沒(méi)有使用這樣的方式實(shí)現(xiàn) LRU 算法,因?yàn)閭鹘y(tǒng)的 LRU 算法存在兩個(gè)問(wèn)題:
需要用鏈表管理所有的緩存數(shù)據(jù),這會(huì)帶來(lái)額外的空間開(kāi)銷;
當(dāng)有數(shù)據(jù)被訪問(wèn)時(shí),需要在鏈表上把該數(shù)據(jù)移動(dòng)到頭端,如果有大量數(shù)據(jù)被訪問(wèn),就會(huì)帶來(lái)很多鏈表移動(dòng)操作,會(huì)很耗時(shí),進(jìn)而會(huì)降低 Redis 緩存性能。
Redis 是如何實(shí)現(xiàn) LRU 算法的?
Redis 實(shí)現(xiàn)的是一種近似 LRU 算法,目的是為了更好的節(jié)約內(nèi)存,它的實(shí)現(xiàn)方式是在 Redis 的對(duì)象結(jié)構(gòu)體中添加一個(gè)額外的字段,用于記錄此數(shù)據(jù)的最后一次訪問(wèn)時(shí)間。
當(dāng) Redis 進(jìn)行內(nèi)存淘汰時(shí),會(huì)使用隨機(jī)采樣的方式來(lái)淘汰數(shù)據(jù),它是隨機(jī)取 5 個(gè)值(此值可配置),然后淘汰最久沒(méi)有使用的那個(gè)。
Redis 實(shí)現(xiàn)的 LRU 算法的優(yōu)點(diǎn):
不用為所有的數(shù)據(jù)維護(hù)一個(gè)大鏈表,節(jié)省了空間占用;
不用在每次數(shù)據(jù)訪問(wèn)時(shí)都移動(dòng)鏈表項(xiàng),提升了緩存的性能;
但是 LRU 算法有一個(gè)問(wèn)題,無(wú)法解決緩存污染問(wèn)題,比如應(yīng)用一次讀取了大量的數(shù)據(jù),而這些數(shù)據(jù)只會(huì)被讀取這一次,那么這些數(shù)據(jù)會(huì)留存在 Redis 緩存中很長(zhǎng)一段時(shí)間,造成緩存污染。
因此,在 Redis 4.0 之后引入了 LFU 算法來(lái)解決這個(gè)問(wèn)題。
什么是 LFU 算法?
LFU 全稱是 Least Frequently Used 翻譯為最近最不常用,LFU 算法是根據(jù)數(shù)據(jù)訪問(wèn)次數(shù)來(lái)淘汰數(shù)據(jù)的,它的核心思想是“如果數(shù)據(jù)過(guò)去被訪問(wèn)多次,那么將來(lái)被訪問(wèn)的頻率也更高”。
所以, LFU 算法會(huì)記錄每個(gè)數(shù)據(jù)的訪問(wèn)次數(shù)。當(dāng)一個(gè)數(shù)據(jù)被再次訪問(wèn)時(shí),就會(huì)增加該數(shù)據(jù)的訪問(wèn)次數(shù)。這樣就解決了偶爾被訪問(wèn)一次之后,數(shù)據(jù)留存在緩存中很長(zhǎng)一段時(shí)間的問(wèn)題,相比于 LRU 算法也更合理一些。
Redis 是如何實(shí)現(xiàn) LFU 算法的?
LFU 算法相比于 LRU 算法的實(shí)現(xiàn),多記錄了「數(shù)據(jù)的訪問(wèn)頻次」的信息。Redis 對(duì)象的結(jié)構(gòu)如下:
typedef struct redisObject {...// 24 bits,用于記錄對(duì)象的訪問(wèn)信息unsigned lru:24; ...
} robj;
Redis 對(duì)象頭中的 lru 字段,在 LRU 算法下和 LFU 算法下使用方式并不相同。
在 LRU 算法中,Redis 對(duì)象頭的 24 bits 的 lru 字段是用來(lái)記錄 key 的訪問(wèn)時(shí)間戳,因此在 LRU 模式下,Redis可以根據(jù)對(duì)象頭中的 lru 字段記錄的值,來(lái)比較最后一次 key 的訪問(wèn)時(shí)間長(zhǎng),從而淘汰最久未被使用的 key。
在 LFU 算法中,Redis對(duì)象頭的 24 bits 的 lru 字段被分成兩段來(lái)存儲(chǔ),高 16bit 存儲(chǔ) ldt(Last Decrement Time),低 8bit 存儲(chǔ) logc(Logistic Counter)。
- ldt 是用來(lái)記錄 key 的訪問(wèn)時(shí)間戳;
- logc 是用來(lái)記錄 key 的訪問(wèn)頻次,它的值越小表示使用頻率越低,越容易淘汰,每個(gè)新加入的 key 的logc 初始值為 5。
注意,logc 并不是單純的訪問(wèn)次數(shù),而是訪問(wèn)頻次(訪問(wèn)頻率),因?yàn)?logc 會(huì)隨時(shí)間推移而衰減的。
在每次 key 被訪問(wèn)時(shí),會(huì)先對(duì) logc 做一個(gè)衰減操作,衰減的值跟前后訪問(wèn)時(shí)間的差距有關(guān)系,如果上一次訪問(wèn)的時(shí)間與這一次訪問(wèn)的時(shí)間差距很大,那么衰減的值就越大,這樣實(shí)現(xiàn)的 LFU 算法是根據(jù)訪問(wèn)頻率來(lái)淘汰數(shù)據(jù)的,而不只是訪問(wèn)次數(shù)。訪問(wèn)頻率需要考慮 key 的訪問(wèn)是多長(zhǎng)時(shí)間段內(nèi)發(fā)生的。key 的先前訪問(wèn)距離當(dāng)前時(shí)間越長(zhǎng),那么這個(gè) key 的訪問(wèn)頻率相應(yīng)地也就會(huì)降低,這樣被淘汰的概率也會(huì)更大。
對(duì) logc 做完衰減操作后,就開(kāi)始對(duì) logc 進(jìn)行增加操作,增加操作并不是單純的 + 1,而是根據(jù)概率增加,如果 logc 越大的 key,它的 logc 就越難再增加。
所以,Redis 在訪問(wèn) key 時(shí),對(duì)于 logc 是這樣變化的:
- 先按照上次訪問(wèn)距離當(dāng)前的時(shí)長(zhǎng),來(lái)對(duì) logc 進(jìn)行衰減;
- 然后,再按照一定概率增加 logc 的值。
redis.conf 提供了兩個(gè)配置項(xiàng),用于調(diào)整 LFU 算法從而控制 logc 的增長(zhǎng)和衰減:
- lfu-decay-time 用于調(diào)整 logc 的衰減速度,它是一個(gè)以分鐘為單位的數(shù)值,默認(rèn)值為1,lfu-decay-time 值越大,衰減越慢;
- lfu-log-factor 用于調(diào)整 logc 的增長(zhǎng)速度,lfu-log-factor 值越大,logc 增長(zhǎng)越慢。
總結(jié)
Redis 使用的過(guò)期刪除策略是「惰性刪除+定期刪除」,刪除的對(duì)象是已過(guò)期的 key。
內(nèi)存淘汰策略是解決內(nèi)存過(guò)大的問(wèn)題,當(dāng) Redis 的運(yùn)行內(nèi)存超過(guò)最大運(yùn)行內(nèi)存時(shí),就會(huì)觸發(fā)內(nèi)存淘汰策略,Redis 4.0 之后共實(shí)現(xiàn)了 8 種內(nèi)存淘汰策略,我也對(duì)這 8 種的策略進(jìn)行分類,如下:
完結(jié),撒花🎉🎉
答應(yīng)我,下次別再搞混了!