Redis 過期鍵刪除策略的實現(xiàn)示例
(一)關于鍵的過期時間或生存時間
我們知道,Redis數(shù)據(jù)庫是基于內(nèi)存的,但是如果一些不用的鍵在內(nèi)存中一直存在,那么久而久之,就有可能會發(fā)生oom的情況。所以,redis數(shù)據(jù)庫提供了常用的EXPIRE命令或者PEXPIRE命令,用戶可以使用這兩個命令以秒或者毫秒為精度為數(shù)據(jù)庫中的某個鍵設置生存時間。在經(jīng)過指定的時間后,redis服務器就會自動刪除生存時間為0的鍵。
可以設置鍵的生存時間的命令如下:
- EXPIRE <key> <ttl>
該命令用于將鍵Key的生存時間設置為ttl秒 - PEXPIRE <key> <ttl>
該命令用于將鍵Key的生存時間設置為ttl毫秒 - EXPIREAT <key> <timstamp>
該命令用于將鍵Key的生存時間設置為timstamp所指定的秒數(shù)時間戳 - PEXPIREAT <key> <timstamp>
該命令用于將鍵Key的生存時間設置為timstamp所指定的毫秒數(shù)時間戳。
雖然有四種不同的命令用于指定過期時間,但是實際上,無論使用哪一種命令,最終都會轉(zhuǎn)換為PEXPIREAT命令來執(zhí)行
那么,redis是如何存儲過期時間的呢?
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;
我們可以通過以上源碼看出,redisDb結(jié)構(gòu)的expires這個字典保存了數(shù)據(jù)庫中所有的過期時間,我們叫這個字典為過期字典。
每當我們?yōu)橐粋€數(shù)據(jù)庫的某一個鍵添加過期時間就會在該字典中添加一個鍵值對,鍵為這個需要添加過期時間的鍵,值為過期時間的時間戳。相反,如果刪除一個鍵的過期時間,也會相應的操作這個字典,刪除該鍵對應的過期時間鍵值對。如圖所示:
(二)過期刪除策略
我們知道了,redis數(shù)據(jù)庫如何設置,如何存儲過期時間。那么這現(xiàn)在的問題是,如果一個鍵過期了,那么什么時候被刪除呢?
關于這個問題,可以實現(xiàn)的有一下三種方案(redis只采用了其中兩種):
- 定時刪除
設置鍵的過期時間的同時,創(chuàng)建一個定時器,讓定時器在鍵過期時間來臨時,立即執(zhí)行對鍵的刪除操作 - 惰性刪除
放任過期不管,但是每次從鍵空間中獲取值的時候,檢查取得的鍵是否過期,如果過期的話,就刪除該鍵;如果沒有過期,就返回該鍵 - 定期刪除
每隔一段時間,程序就對數(shù)據(jù)庫進行一次檢查,刪除里面的過期鍵,至于要刪除多少個過期鍵,以及要檢查多少個數(shù)據(jù)庫則由算法決定。
下面我們來瞅瞅這三種策略的優(yōu)缺點:
1.定時刪除
優(yōu)點:
這種刪除策略對于內(nèi)存來說是友好的,因為這種刪除方式可以保證過期的鍵盡可能快的被刪除掉,并釋放過期鍵所占用的內(nèi)存
缺點:
1.這種刪除策略對CPU時間不友好,在過期鍵比較多的情況下,刪除過期鍵這一行為可能會占用相當一部分的CPU時間,在內(nèi)存不緊張的但是CPU時間緊張的情況下,這無疑會對服務器的響應時間和吞吐量造成影響。
2.創(chuàng)建一個定時器需要用到redis服務器中的時間時間,而當前時間時間的實現(xiàn)方式為無序鏈表,查找一個事件的時間復雜度為O(N),所以說,如果采用這種策略,并不能高效的處理大量的時間事件。
2.惰性刪除
優(yōu)點
這種刪除策略對于CPU來說是友好的,程序只會在取出鍵的時候才會對鍵進行過期檢查,這樣可以保證對鍵的刪除操作僅限于當前處理的鍵,這個策略不會在刪除其他過期的鍵上花費任何的時間
缺點
顯而易見的,這種刪除策略對于內(nèi)存來說是十分不友好的。因為如果大量的過期鍵,長期不使用的情況下,就會造成大量的內(nèi)存被無效的鍵占用。我們甚至可以將這中情況看作是內(nèi)存泄露
3.定期刪除
針對定期刪除來說,這種策略實際上是定時刪除和惰性刪除這兩種策略的折中和整合。定期刪除策略每隔一段時間執(zhí)行一次刪除過期鍵操作,并通過限制刪除操作執(zhí)行時長和頻率來減少刪除操作對CPU時間的影響。除此之外,通過定期刪除過期鍵,定期刪除策略有效的減少了因為過期鍵而帶來的內(nèi)存浪費。
當然,這種策略的難點就在于如何確定刪除的時長和頻率。比如,如果設定的刪除太頻繁或者執(zhí)行刪除的時間太長,就直接回退化為定時刪除。如果刪除的頻率過低或者指定的時間太短,定期刪除又會和惰性刪除一樣,造成內(nèi)存浪費的情況。
(三)Redis采用的過期鍵刪除策略
Redis數(shù)據(jù)庫實際上采用了兩種刪除策略:定期刪除和惰性刪除。通過這兩種刪除策略的配合使用,服務器可以很好的在合理使用CPU時間和避免內(nèi)存空間浪費之間取得平衡。
那么,Redis數(shù)據(jù)庫是如何實現(xiàn)這兩種刪除策略的呢?
惰性刪除策略的實現(xiàn):
過期鍵的刪除策略由expireIfNeeded函數(shù)實現(xiàn),所有讀寫數(shù)據(jù)庫的Redis命令都會在執(zhí)行錢調(diào)用該函數(shù)進行檢查。
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)過期,那么expireIfNeed函數(shù)將輸入鍵從數(shù)據(jù)庫刪除。如果輸入鍵沒有過期,則不會做其他動作。所以,每個命令的實現(xiàn)函數(shù)都必須能同時處理鍵存在和不存在兩種情況。
定期刪除策略的實現(xiàn)
該策略由activeExpireCycle函數(shù)實現(xiàn),每當服務器周期性的操作serverCron函數(shù)執(zhí)行的時候,activeExpireCycle函數(shù)就會被調(diào)用,在規(guī)定的時間內(nèi),分多次遍歷服務器中的各個數(shù)據(jù)庫,從數(shù)據(jù)庫的expires字典中隨你檢查一部分的過期時間,并刪除其中的過期鍵。具體代碼實現(xiàn)由于太多,有感興趣可以去看一下,redis6在expire.c中,redis3在redis.c中。
(四)關于AOF、RDB對過期鍵的處理
1.生成RDB文件
在執(zhí)行SAVE或者BGSAVE命令創(chuàng)建一個新的RDB文件的時候,程序會對數(shù)據(jù)庫中的過期鍵進行檢查,過期的鍵不會被保存到新創(chuàng)建的RDB文件中。
2.載入RDB文件
載入的時候分為兩種情況
(1)服務器以主服務器運行。
當服務器以主服務器運行的時候,會對文件中保存的鍵進行檢查,未過期的鍵會被載入到數(shù)據(jù)庫中,而過期的鍵則會被忽略,所以過期鍵對載入RDB文件的主服務器不會造成影響。
(2)服務器以從服務器運行。
當服務器以從服務器運行的時候,會將文件中保存的所有鍵進行保存,不論是否過期。但是由于主從服務器進行數(shù)據(jù)同步的時候,從服務器的數(shù)據(jù)庫就會被清空,所以一般來講,過期鍵載入RDB文件的從高服務器也不會造成影響
3.AOF文件的寫入
當數(shù)據(jù)庫中某個鍵已經(jīng)過期,但是它還沒有被惰性刪除或者定期刪除,那么AOF文件不會因為這個過期鍵而產(chǎn)生任何影響。當過期鍵被惰性刪除或者定期刪除之后,程序會向AOF文件追加一條DEL命令進行顯示的刪除。
4.AOF文件的重寫:
重寫的時候程序會對數(shù)據(jù)庫中的鍵進行檢查,已過期的鍵不會被保存到重寫后的AOF文件中
這里有一個有意思的東西,當服務器運行在主從復制模式下的時候,從服務器的過期鍵刪除動作是由主服務器控制的。
主服務器在刪除一個過期鍵后,會顯示的向所有從服務器發(fā)送一個DEL命令,命令從服務器刪除這個鍵;
從服務器在執(zhí)行客戶端發(fā)送的命令的時候,即使遇到過期的鍵也不會將過期的鍵進行刪除,是繼續(xù)像處理未過期的鍵一樣來處理過期鍵;
從服務器只有在接到主服務器發(fā)送來的DEL命令的時候才會刪除過期鍵。
到此這篇關于Redis 過期鍵刪除策略的實現(xiàn)示例的文章就介紹到這了,更多相關Redis 過期鍵刪除內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
window下創(chuàng)建redis出現(xiàn)問題小結(jié)
這篇文章主要介紹了window下創(chuàng)建redis出現(xiàn)問題總結(jié),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10