Redis給我們提供了設(shè)置鍵過期的方式:
- EXPIRE <key> <ttl> 命令用于將鍵key的生存時間設(shè)置為ttl秒
- PEXPIRE <key> <ttl> 命令用于將鍵key的生存時間設(shè)置為ttl毫秒
- EXPIREAT <key> <timestamp> 命令用于將鍵key的過期時間設(shè)置為timestamp
- PEXPIREAT <key> <timestamp>命令用于將鍵key的過期時間設(shè)置為timestamp所指定的秒數(shù)時間戳
其實上述命令經(jīng)過轉(zhuǎn)換后的執(zhí)行效果都和PEXPIREAT命令效果一樣:
def EXPIRE(key, ttl_in_sec) :
#將TTL從秒轉(zhuǎn)換成毫秒
ttl_in_ms = sec_to_ms(ttl_in_sec)
PEXPIRE(key, ttl_in_ms)
def PEXPIRE(key, ttl_in_ms):
now_ms = get_current_unix_timestamp_in_ms()
PEXPIREAT(key,now_ms+ttl_in_ms)
def EXPIREAT(key, expire_time_in_sec):
expire_time_in_ms = sec_to_ms(expire_time_in_sec)
PEXPIREAT(key, expire_time_in_ms)

過期時間的底層存儲

從圖可知,在redis的數(shù)據(jù)庫中,redisDb結(jié)構(gòu)中的expires字典中保存了數(shù)據(jù)庫中所有鍵的過期時間,所以叫過期字典。
- 過期字典的key是一個指針,指向鍵空間的某個鍵對象(就是數(shù)據(jù)庫鍵)
- 過期字典的value是一個long類型的整數(shù),這個整數(shù)保存了鍵所指向的數(shù)據(jù)庫鍵的過期時間,一個毫秒精度的UNIX時間戳
過期鍵判定
通過過期字典,我們可以得到一個key是否過期:
- 判斷key是否存在于過期字典中
- 通過過期字典拿到key的過期時間,判斷當前UNIX時間戳是否大于key時間
重點:過期鍵的刪除策略
其實刪除策略也比較常見,以下三種:
定時刪除:設(shè)置過期鍵的同時,設(shè)定設(shè)定定時器,通過定時器來主動刪除過期鍵。
這種方式對內(nèi)存友好,但是對cpu最不友好;定時器的設(shè)定需要使用redis服務(wù)器的時間事件(無序鏈表),查找的事件復(fù)雜度為O(n);故在過期鍵過多時,cpu的大部分占用是用來查找過期鍵和刪除過期鍵的。惰性刪除:每次對key進行操作時,判斷當前key是否過期,再進行操作。
這種方式很明顯對內(nèi)存是不友好的,key過期的話仍然會一直存在數(shù)據(jù)庫中,直到下次有對這個key的操作。定期刪除:每隔一段時間,對redis數(shù)據(jù)庫中的過期字典進行掃描,對于過期的key進行刪除。每次刪除多少過期鍵,以及檢查多少個數(shù)據(jù)庫,需要由我們的算法來決定。
定期刪除算是上述兩種方式折中的方式,定期刪除的難點在于如何選取定期的時間:
如果刪除操作太過于頻繁,那么將會退化成定時刪除;
如果刪除操作執(zhí)行次數(shù)太少或者執(zhí)行的時間太短,就會退化為惰性刪除。
redis實際上選用的是惰性刪除和定期刪除兩種策略。
過期鍵的惰性刪除策略是由db.c/expireIfNeeded函數(shù)實現(xiàn)的,所有讀寫數(shù)據(jù)庫Redis命令在執(zhí)行之前都會先調(diào)用該函數(shù)對輸入鍵進行檢查

過期鍵的定期刪除策略是由redis.c/activeExpireCycle函數(shù)實現(xiàn)的。Redis服務(wù)器周期性操作redis.c/serverCron函數(shù),activeExpireCycle函數(shù)就會被調(diào)用,在規(guī)定的事件內(nèi),分多次遍歷服務(wù)器中的各個數(shù)據(jù)庫,從數(shù)據(jù)庫的expire字典中隨機檢查一部分鍵的過期時間,并刪除其中過期的鍵。