Redis 過(guò)期鍵刪除策略的實(shí)現(xiàn)示例
(一)關(guān)于鍵的過(guò)期時(shí)間或生存時(shí)間
我們知道,Redis數(shù)據(jù)庫(kù)是基于內(nèi)存的,但是如果一些不用的鍵在內(nèi)存中一直存在,那么久而久之,就有可能會(huì)發(fā)生oom的情況。所以,redis數(shù)據(jù)庫(kù)提供了常用的EXPIRE命令或者PEXPIRE命令,用戶可以使用這兩個(gè)命令以秒或者毫秒為精度為數(shù)據(jù)庫(kù)中的某個(gè)鍵設(shè)置生存時(shí)間。在經(jīng)過(guò)指定的時(shí)間后,redis服務(wù)器就會(huì)自動(dòng)刪除生存時(shí)間為0的鍵。
可以設(shè)置鍵的生存時(shí)間的命令如下:
- EXPIRE <key> <ttl>
該命令用于將鍵Key的生存時(shí)間設(shè)置為ttl秒 - PEXPIRE <key> <ttl>
該命令用于將鍵Key的生存時(shí)間設(shè)置為ttl毫秒 - EXPIREAT <key> <timstamp>
該命令用于將鍵Key的生存時(shí)間設(shè)置為timstamp所指定的秒數(shù)時(shí)間戳 - PEXPIREAT <key> <timstamp>
該命令用于將鍵Key的生存時(shí)間設(shè)置為timstamp所指定的毫秒數(shù)時(shí)間戳。
雖然有四種不同的命令用于指定過(guò)期時(shí)間,但是實(shí)際上,無(wú)論使用哪一種命令,最終都會(huì)轉(zhuǎn)換為PEXPIREAT命令來(lái)執(zhí)行
那么,redis是如何存儲(chǔ)過(guò)期時(shí)間的呢?
typedef struct redisDb { dict *dict; /* The keyspace for this DB */ dict *expires; /* Timeout of keys with a timeout set */ dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/ dict *ready_keys; /* Blocked keys that received a PUSH */ dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ int id; /* Database ID */ long long avg_ttl; /* Average TTL, just for stats */ unsigned long expires_cursor; /* Cursor of the active expire cycle. */ list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */ } redisDb;
我們可以通過(guò)以上源碼看出,redisDb結(jié)構(gòu)的expires這個(gè)字典保存了數(shù)據(jù)庫(kù)中所有的過(guò)期時(shí)間,我們叫這個(gè)字典為過(guò)期字典。
每當(dāng)我們?yōu)橐粋€(gè)數(shù)據(jù)庫(kù)的某一個(gè)鍵添加過(guò)期時(shí)間就會(huì)在該字典中添加一個(gè)鍵值對(duì),鍵為這個(gè)需要添加過(guò)期時(shí)間的鍵,值為過(guò)期時(shí)間的時(shí)間戳。相反,如果刪除一個(gè)鍵的過(guò)期時(shí)間,也會(huì)相應(yīng)的操作這個(gè)字典,刪除該鍵對(duì)應(yīng)的過(guò)期時(shí)間鍵值對(duì)。如圖所示:
(二)過(guò)期刪除策略
我們知道了,redis數(shù)據(jù)庫(kù)如何設(shè)置,如何存儲(chǔ)過(guò)期時(shí)間。那么這現(xiàn)在的問(wèn)題是,如果一個(gè)鍵過(guò)期了,那么什么時(shí)候被刪除呢?
關(guān)于這個(gè)問(wèn)題,可以實(shí)現(xiàn)的有一下三種方案(redis只采用了其中兩種):
- 定時(shí)刪除
設(shè)置鍵的過(guò)期時(shí)間的同時(shí),創(chuàng)建一個(gè)定時(shí)器,讓定時(shí)器在鍵過(guò)期時(shí)間來(lái)臨時(shí),立即執(zhí)行對(duì)鍵的刪除操作 - 惰性刪除
放任過(guò)期不管,但是每次從鍵空間中獲取值的時(shí)候,檢查取得的鍵是否過(guò)期,如果過(guò)期的話,就刪除該鍵;如果沒(méi)有過(guò)期,就返回該鍵 - 定期刪除
每隔一段時(shí)間,程序就對(duì)數(shù)據(jù)庫(kù)進(jìn)行一次檢查,刪除里面的過(guò)期鍵,至于要?jiǎng)h除多少個(gè)過(guò)期鍵,以及要檢查多少個(gè)數(shù)據(jù)庫(kù)則由算法決定。
下面我們來(lái)瞅瞅這三種策略的優(yōu)缺點(diǎn):
1.定時(shí)刪除
優(yōu)點(diǎn):
這種刪除策略對(duì)于內(nèi)存來(lái)說(shuō)是友好的,因?yàn)檫@種刪除方式可以保證過(guò)期的鍵盡可能快的被刪除掉,并釋放過(guò)期鍵所占用的內(nèi)存
缺點(diǎn):
1.這種刪除策略對(duì)CPU時(shí)間不友好,在過(guò)期鍵比較多的情況下,刪除過(guò)期鍵這一行為可能會(huì)占用相當(dāng)一部分的CPU時(shí)間,在內(nèi)存不緊張的但是CPU時(shí)間緊張的情況下,這無(wú)疑會(huì)對(duì)服務(wù)器的響應(yīng)時(shí)間和吞吐量造成影響。
2.創(chuàng)建一個(gè)定時(shí)器需要用到redis服務(wù)器中的時(shí)間時(shí)間,而當(dāng)前時(shí)間時(shí)間的實(shí)現(xiàn)方式為無(wú)序鏈表,查找一個(gè)事件的時(shí)間復(fù)雜度為O(N),所以說(shuō),如果采用這種策略,并不能高效的處理大量的時(shí)間事件。
2.惰性刪除
優(yōu)點(diǎn)
這種刪除策略對(duì)于CPU來(lái)說(shuō)是友好的,程序只會(huì)在取出鍵的時(shí)候才會(huì)對(duì)鍵進(jìn)行過(guò)期檢查,這樣可以保證對(duì)鍵的刪除操作僅限于當(dāng)前處理的鍵,這個(gè)策略不會(huì)在刪除其他過(guò)期的鍵上花費(fèi)任何的時(shí)間
缺點(diǎn)
顯而易見(jiàn)的,這種刪除策略對(duì)于內(nèi)存來(lái)說(shuō)是十分不友好的。因?yàn)槿绻罅康倪^(guò)期鍵,長(zhǎng)期不使用的情況下,就會(huì)造成大量的內(nèi)存被無(wú)效的鍵占用。我們甚至可以將這中情況看作是內(nèi)存泄露
3.定期刪除
針對(duì)定期刪除來(lái)說(shuō),這種策略實(shí)際上是定時(shí)刪除和惰性刪除這兩種策略的折中和整合。定期刪除策略每隔一段時(shí)間執(zhí)行一次刪除過(guò)期鍵操作,并通過(guò)限制刪除操作執(zhí)行時(shí)長(zhǎng)和頻率來(lái)減少刪除操作對(duì)CPU時(shí)間的影響。除此之外,通過(guò)定期刪除過(guò)期鍵,定期刪除策略有效的減少了因?yàn)檫^(guò)期鍵而帶來(lái)的內(nèi)存浪費(fèi)。
當(dāng)然,這種策略的難點(diǎn)就在于如何確定刪除的時(shí)長(zhǎng)和頻率。比如,如果設(shè)定的刪除太頻繁或者執(zhí)行刪除的時(shí)間太長(zhǎng),就直接回退化為定時(shí)刪除。如果刪除的頻率過(guò)低或者指定的時(shí)間太短,定期刪除又會(huì)和惰性刪除一樣,造成內(nèi)存浪費(fèi)的情況。
(三)Redis采用的過(guò)期鍵刪除策略
Redis數(shù)據(jù)庫(kù)實(shí)際上采用了兩種刪除策略:定期刪除和惰性刪除。通過(guò)這兩種刪除策略的配合使用,服務(wù)器可以很好的在合理使用CPU時(shí)間和避免內(nèi)存空間浪費(fèi)之間取得平衡。
那么,Redis數(shù)據(jù)庫(kù)是如何實(shí)現(xiàn)這兩種刪除策略的呢?
惰性刪除策略的實(shí)現(xiàn):
過(guò)期鍵的刪除策略由expireIfNeeded函數(shù)實(shí)現(xiàn),所有讀寫數(shù)據(jù)庫(kù)的Redis命令都會(huì)在執(zhí)行錢調(diào)用該函數(shù)進(jìn)行檢查。
int expireIfNeeded(redisDb *db, robj *key) { if (!keyIsExpired(db,key)) return 0; if (server.masterhost != NULL) return 1; /* Delete the key */ server.stat_expiredkeys++; propagateExpire(db,key,server.lazyfree_lazy_expire); notifyKeyspaceEvent(NOTIFY_EXPIRED, "expired",key,db->id); int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) : dbSyncDelete(db,key); if (retval) signalModifiedKey(NULL,db,key); return retval; }
我們可以看出,如果輸入鍵已經(jīng)過(guò)期,那么expireIfNeed函數(shù)將輸入鍵從數(shù)據(jù)庫(kù)刪除。如果輸入鍵沒(méi)有過(guò)期,則不會(huì)做其他動(dòng)作。所以,每個(gè)命令的實(shí)現(xiàn)函數(shù)都必須能同時(shí)處理鍵存在和不存在兩種情況。
定期刪除策略的實(shí)現(xiàn)
該策略由activeExpireCycle函數(shù)實(shí)現(xiàn),每當(dāng)服務(wù)器周期性的操作serverCron函數(shù)執(zhí)行的時(shí)候,activeExpireCycle函數(shù)就會(huì)被調(diào)用,在規(guī)定的時(shí)間內(nèi),分多次遍歷服務(wù)器中的各個(gè)數(shù)據(jù)庫(kù),從數(shù)據(jù)庫(kù)的expires字典中隨你檢查一部分的過(guò)期時(shí)間,并刪除其中的過(guò)期鍵。具體代碼實(shí)現(xiàn)由于太多,有感興趣可以去看一下,redis6在expire.c中,redis3在redis.c中。
(四)關(guān)于AOF、RDB對(duì)過(guò)期鍵的處理
1.生成RDB文件
在執(zhí)行SAVE或者BGSAVE命令創(chuàng)建一個(gè)新的RDB文件的時(shí)候,程序會(huì)對(duì)數(shù)據(jù)庫(kù)中的過(guò)期鍵進(jìn)行檢查,過(guò)期的鍵不會(huì)被保存到新創(chuàng)建的RDB文件中。
2.載入RDB文件
載入的時(shí)候分為兩種情況
(1)服務(wù)器以主服務(wù)器運(yùn)行。
當(dāng)服務(wù)器以主服務(wù)器運(yùn)行的時(shí)候,會(huì)對(duì)文件中保存的鍵進(jìn)行檢查,未過(guò)期的鍵會(huì)被載入到數(shù)據(jù)庫(kù)中,而過(guò)期的鍵則會(huì)被忽略,所以過(guò)期鍵對(duì)載入RDB文件的主服務(wù)器不會(huì)造成影響。
(2)服務(wù)器以從服務(wù)器運(yùn)行。
當(dāng)服務(wù)器以從服務(wù)器運(yùn)行的時(shí)候,會(huì)將文件中保存的所有鍵進(jìn)行保存,不論是否過(guò)期。但是由于主從服務(wù)器進(jìn)行數(shù)據(jù)同步的時(shí)候,從服務(wù)器的數(shù)據(jù)庫(kù)就會(huì)被清空,所以一般來(lái)講,過(guò)期鍵載入RDB文件的從高服務(wù)器也不會(huì)造成影響
3.AOF文件的寫入
當(dāng)數(shù)據(jù)庫(kù)中某個(gè)鍵已經(jīng)過(guò)期,但是它還沒(méi)有被惰性刪除或者定期刪除,那么AOF文件不會(huì)因?yàn)檫@個(gè)過(guò)期鍵而產(chǎn)生任何影響。當(dāng)過(guò)期鍵被惰性刪除或者定期刪除之后,程序會(huì)向AOF文件追加一條DEL命令進(jìn)行顯示的刪除。
4.AOF文件的重寫:
重寫的時(shí)候程序會(huì)對(duì)數(shù)據(jù)庫(kù)中的鍵進(jìn)行檢查,已過(guò)期的鍵不會(huì)被保存到重寫后的AOF文件中
這里有一個(gè)有意思的東西,當(dāng)服務(wù)器運(yùn)行在主從復(fù)制模式下的時(shí)候,從服務(wù)器的過(guò)期鍵刪除動(dòng)作是由主服務(wù)器控制的。
主服務(wù)器在刪除一個(gè)過(guò)期鍵后,會(huì)顯示的向所有從服務(wù)器發(fā)送一個(gè)DEL命令,命令從服務(wù)器刪除這個(gè)鍵;
從服務(wù)器在執(zhí)行客戶端發(fā)送的命令的時(shí)候,即使遇到過(guò)期的鍵也不會(huì)將過(guò)期的鍵進(jìn)行刪除,是繼續(xù)像處理未過(guò)期的鍵一樣來(lái)處理過(guò)期鍵;
從服務(wù)器只有在接到主服務(wù)器發(fā)送來(lái)的DEL命令的時(shí)候才會(huì)刪除過(guò)期鍵。
到此這篇關(guān)于Redis 過(guò)期鍵刪除策略的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Redis 過(guò)期鍵刪除內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis 過(guò)期鍵刪除策略的實(shí)現(xiàn)示例
Redis的過(guò)期數(shù)據(jù)刪除策略主要有三種,包括定時(shí)刪除、惰性刪除和定期刪除,本文主要介紹了Redis 過(guò)期鍵刪除策略的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03深入解析Redis中常見(jiàn)的應(yīng)用場(chǎng)景
這篇文章主要給大家介紹了關(guān)于Redis中常見(jiàn)的應(yīng)用場(chǎng)景的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09Redis3.2.6配置文件詳細(xì)中文說(shuō)明
本文為大家分享了Redis3.2.6配置文件詳細(xì)中文說(shuō)明,非常詳細(xì)收藏起來(lái)以后工作有用2018-10-10window下創(chuàng)建redis出現(xiàn)問(wèn)題小結(jié)
這篇文章主要介紹了window下創(chuàng)建redis出現(xiàn)問(wèn)題總結(jié),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Redis恢復(fù)被移除集群的服務(wù)器實(shí)操步驟
這篇文章主要為大家介紹了Redis恢復(fù)被移除集群的服務(wù)器實(shí)操步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Redis 中的熱點(diǎn)鍵和數(shù)據(jù)傾斜示例詳解
熱點(diǎn)鍵是指在 Redis 中被頻繁訪問(wèn)的特定鍵,這些鍵由于其高訪問(wèn)頻率,可能導(dǎo)致 Redis 服務(wù)器的性能問(wèn)題,尤其是在高并發(fā)場(chǎng)景下,本文給大家介紹Redis 中的熱點(diǎn)鍵和數(shù)據(jù)傾斜,感興趣的朋友一起看看吧2025-03-03