前言
最近在需求開發(fā)中又用到了我們熟知的Redis字符串操作SET命令偷崩,可以設置指定key的值value及該key的生存時間(Time To Live,TTL)腐宋。相關命令的語法如下:
set key value [EX seconds] [PX milliseconds] [NX|XX] //TTL由EX指定秒或PX指定毫秒
expire key seconds //expire指定TTL养交,單位為秒
pexpire key milliseconds //pexpire指定TTL枚抵,單位為毫秒
expireat key timestamp //expireat指定過期時間戳,單位為秒
pexpireat key milliseconds-timestamp //pexpireat指定過期時間戳造烁,單位為毫秒
TTL key //key的剩余生存時間否过,單位是秒
PTTL key //key的剩余生存時間,單位是毫秒
PERSIST key //移除key的過期時間
這些命令用起來挺熟練惭蟋,可轉(zhuǎn)念一想苗桂,Redis中鍵的自動過期是如何實現(xiàn)的呢?在翻閱資料及源碼的基礎上告组,本文主要從過期時間處理煤伟、自動刪除過期鍵策略等方面簡要介紹該功能的實現(xiàn)。
鍵的過期時間處理
設置過期時間
前言中提到木缝,Redis有四個不同命令可以用于設置鍵的生存時間或過期時間便锨。
可以通過EXPIRE或PEXPIRE命令設置該key的生存時間(Time To Live,TTL)我碟,在經(jīng)過指定秒或毫秒后放案,Redis服務器就會自動刪除生存時間為0的鍵key。
同時可以使用EXPIREAT或PEXPIREAT命令給鍵key設置過期時間(expire time)矫俺。
雖然命令形式多樣卿叽,但實際上EXPIRE、PEXPIRE恳守、EXPIREAT三個命令都是使用PEXPIREAT命令來實現(xiàn)的考婴,轉(zhuǎn)換方法很簡單,就是將過期時間換算成時間戳催烘,并保持時間單位統(tǒng)一沥阱。
保存過期時間
redisDb結(jié)構(gòu)的expires字典保存了數(shù)據(jù)庫中所有鍵的過期時間,被稱為“過期字典”伊群。
- 過期字典是一個指針考杉,指向鍵空間中的某個對象(也即時某個數(shù)據(jù)庫鍵)。
- 過期字典的值是一個long long類型的整數(shù)舰始,保存了鍵所指向的數(shù)據(jù)庫鍵的過期時間(一個毫秒精度的UNIX時間戳)崇棠。
移除過期時間
PERSIST命令可以移除一個鍵的過期時間,實際就是PEXPIREAT命令的反操作:PERSIST命令在過期字典中查找給定的鍵丸卷,并解除鍵和值(過期時間)在過期字典中的關聯(lián)枕稀。
計算并返回剩余生存時間
可以使用TTL或PTTL命令查找給定鍵key的剩余生存時間(key距離被服務器刪除還剩多少秒/毫秒),兩個命令都是通過計算鍵的過期時間和當前時間的差值實現(xiàn)的。
過期鍵的判定
Redis通過查詢過期字典的方式檢查一個給定鍵是否過期:
- 檢查給定鍵是否存在于過期字典:如果存在萎坷,那么取得鍵的過期時間凹联;
- 檢查當前UNIX時間戳是否大于鍵的過期時間:如果是的話,name鍵已過期哆档;否則鍵未過期蔽挠。
過期鍵刪除策略
常見的三種刪除策略對比
刪除策略 | 實現(xiàn) | 優(yōu)點 | 缺點 |
---|---|---|---|
定時刪除 | 設置鍵的過期時間的同時,創(chuàng)建一個定時器(Timer)瓜浸,讓定時器在鍵的過期時間來臨時澳淑,立即執(zhí)行對鍵的刪除操作。 | 內(nèi)存占用率低插佛,通過使用定時器杠巡,可以保證過期鍵會盡可能快地被刪除,并釋放過期鍵所占的內(nèi)存朗涩。 | 占用較多cpu時間忽孽,影響服務器的響應時間和吞吐量。 |
惰性刪除 | 放任鍵過期不管谢床,但是每次獲取鍵時兄一,都檢查鍵是否已過期,如果過期則刪除該鍵识腿;否則返回該鍵出革。 | cpu占用率低,只會在取出鍵時才進行過期檢查渡讼,可以保證刪除的目標僅限于當前的鍵骂束,不會在其它過期鍵上花費任何cpu時間。 | 浪費內(nèi)存成箫,有內(nèi)存泄漏的風險展箱。 |
定期刪除 | 每隔一段時間就對數(shù)據(jù)庫做一次過期鍵的刪除。但每次要刪除多少過期鍵蹬昌、要檢查多少個db混驰,則由算法決定。 | 是前兩種策略的整合和折中皂贩,減少了內(nèi)存和cpu的無謂占用栖榨。 | 難以確定刪除操作執(zhí)行的時長和頻率。 |
Redis的過期鍵刪除策略
Redis服務器實際使用的是惰性刪除和定期刪除兩種策略:通過配合使用這兩種策略明刷,服務器可以很好地在合理使用cpu和避免浪費內(nèi)存空間之間取得平衡婴栽。
惰性刪除策略的實現(xiàn)
過期鍵的惰性刪除策略由db.c/expireIfNeeded函數(shù)實現(xiàn),所有讀寫數(shù)據(jù)庫的Redis命令在執(zhí)行之前都會調(diào)用expireIfNeeded函數(shù)對輸入鍵進行檢查:
- 如果輸入鍵已經(jīng)過期辈末,那么expireIfNeeded函數(shù)將輸入鍵從數(shù)據(jù)庫中刪除愚争。
- 如果輸入鍵未過期映皆,那么expireIfNeeded函數(shù)不做動作。
expireIfNeeded函數(shù)就像一個過濾器准脂,它可以在命令真正執(zhí)行之前劫扒,過濾掉過期的輸入鍵檬洞,從而避免命令接觸到過期鍵狸膏。另外,因為每個被訪問的鍵都可能因為過期而被expireIfNeeded函數(shù)刪除添怔,所以每個命令的實現(xiàn)函數(shù)都必須能同時處理鍵存在和不存在的情況:
- 當鍵存在時湾戳,命令按照鍵存在的情況執(zhí)行。
- 當鍵不存在或者鍵因為過期而被expireIfNeeded函數(shù)刪除時广料,命令按照鍵不存在的情況執(zhí)行砾脑。
定期刪除策略的實現(xiàn)
過期鍵的定期刪除策略由redis.c/activeExpireCycle函數(shù)實現(xiàn),每當Redis的服務器周期性操作redis.c/serverCron函數(shù)執(zhí)行時艾杏,activeExpireCycle函數(shù)就會被調(diào)用韧衣,它在規(guī)定的時間內(nèi),分多次遍歷服務器中的各個數(shù)據(jù)庫购桑,從數(shù)據(jù)庫的expires字典中隨機檢查一部分鍵的過期時間畅铭,并刪除其中的過期鍵。
activeExpireCycle函數(shù)的工作模式可以總結(jié)如下:
- 函數(shù)每次運行時勃蜘,都從一定數(shù)量的數(shù)據(jù)庫(取min(默認16硕噩,實際數(shù)量))中取出一定數(shù)量的隨機鍵(默認20)進行檢查,并刪除其中的過期鍵缭贡。
- 全局變量current_db會記錄當前activeExpireCycle函數(shù)檢查的進度炉擅,并在下一次activeExpireCycle函數(shù)調(diào)用時,接著上一次的進度進行處理阳惹。
- 隨著activeExpireCycle函數(shù)的不斷執(zhí)行谍失,服務器中的所有db都會被檢查一遍,這時函數(shù)將current_db變量重置為0莹汤,然后進行新一輪的定期刪除快鱼。
小結(jié)
本文對Redis中鍵過期功能的實現(xiàn)做了一個簡要介紹,相信讀者看完之后會對大致的實現(xiàn)方案有所了解体啰,但更多細節(jié)推薦閱讀《Redis涉及與實現(xiàn)》攒巍,當然想自己去研究源碼更好啦。