redis的過期時間設置和過期刪除機制
一:設置過期時間
redis有四種命令可以用於設置鍵的生存時間和過期時間:
-
EXPIRE <KEY> <TTL> : 將鍵的生存時間設為 ttl 秒
-
PEXPIRE <KEY> <TTL> :將鍵的生存時間設為 ttl 毫秒
-
EXPIREAT <KEY> <timestamp> :將鍵的過期時間設為 timestamp 所指定的秒數時間戳
-
PEXPIREAT <KEY> <timestamp>: 將鍵的過期時間設為 timestamp 所指定的毫秒數時間戳.
二:保存過期時間
那么redis里面對這些key的過期時間和生存時間的信息是怎么保存的呢??
答:在數據庫結構redisDb中的expires字典中保存了數據庫中所有鍵的過期時間,我們稱expire這個字典為過期字典。
(1)過期字典是一個指針,指向鍵空間的某個鍵對象。
(2)過期字典的值是一個longlong類型的整數,這個整數保存了鍵所指向的數據庫鍵的過期時間–一個毫秒級的 UNIX 時間戳。
下圖是一個帶過期字典的數據庫例子:
Paste_Image.png
過期字典是存儲在redisDb這個結構里的:
-
typedef struct redisDb {
-
...
-
-
dict *dict; //數據庫鍵空間,保存着數據庫中所有鍵值對
-
dict *expires // 過期字典,保存着鍵的過期時間
-
...
-
} redisDb;
從以上結構中可以看到expire字典(過期字典)和dict字典(數據庫鍵空間,保存着數據庫中所有鍵值對)是並列的,由此可見expire字典的重要性。
三:移除過期時間
PERSIST 命令可以移除一個鍵的過期時間:
-
127 .0.0.1:6379> set message "hello"
-
OK
-
127 .0.0.1:6379> expire message 60
-
( integer) 1
-
127 .0.0.1:6379> ttl message
-
( integer) 54
-
127 .0.0.1:6379> persist message
-
( integer) 1
-
127 .0.0.1:6379> ttl message
-
( integer) -1
persist命令就是expire命令的反命令,這個函數在過期字典中查找給定的鍵,並從過期字典中移除。
比如在數據庫當前狀態(如上圖所示),當給book這個key移除過期時間:
-
redis> persist book
-
( integer) 1
數據庫將更新成如下狀態:
Paste_Image.png
可以從圖中看到,當PERSIST book命令執行之后,過期字典中的 book 鍵消失了。
四:計算並返回剩余生存時間
ttl命令以秒為單位返回指定鍵的剩余生存時間。pttl以毫秒返回。兩個命令都是通過計算當前時間和過期時間的差值得到剩余生存期的。
-
127 .0.0.1:6379> set minping shuxin
-
OK
-
127 .0.0.1:6379> expire minping 60
-
( integer) 1
-
127 .0.0.1:6379> ttl minping
-
( integer) 57
-
127 .0.0.1:6379> ttl minping
-
( integer) 27
-
127 .0.0.1:6379> pttl minping
-
( integer) 23839
-
127 .0.0.1:6379>
redis源碼為:
-
void ttlCommand(redisClient * c) {
-
ttlGenericCommand( c, 0);
-
}
-
void pttlCommand(redisClient * c) {
-
ttlGenericCommand( c, 1);
-
}
-
void ttlGenericCommand(redisClient * c, int output_ms) {
-
long long expire, ttl = - 1;
-
/* 如果鍵不存在,返回-2 */
-
if (lookupKeyRead(c->db,c->argv[1]) == NULL) {
-
addReplyLongLong( c,-2);
-
return;
-
}
-
-
/* 如果鍵存在*/
-
/*如果沒有設置生存時間,返回 -1, 否則返回實際剩余時間 */
-
expire = getExpire( c->db,c->argv[1]);
-
if (expire != -1) {
-
/* 過期時間減去當前時間,就是鍵的剩余時間*/
-
ttl = expire-mstime();
-
if (ttl < 0) ttl = 0;
-
}
-
if (ttl == -1) {
-
addReplyLongLong( c,-1);
-
} else {
-
/*將毫秒轉化為秒*/
-
addReplyLongLong( c,output_ms ? ttl : ((ttl+500)/1000));
-
}
-
}
五:過期鍵的刪除策略
如果一個鍵是過期的,那它到了過期時間之后是不是馬上就從內存中被被刪除呢??如果不是,那過期后到底什么時候被刪除呢??
其實有三種不同的刪除策略:
(1):立即刪除。在設置鍵的過期時間時,創建一個回調事件,當過期時間達到時,由時間處理器自動執行鍵的刪除操作。
(2):惰性刪除。鍵過期了就過期了,不管。每次從dict字典中按key取值時,先檢查此key是否已經過期,如果過期了就刪除它,並返回nil,如果沒過期,就返回鍵值。
(3):定時刪除。每隔一段時間,對expires字典進行檢查,刪除里面的過期鍵。
可以看到,第二種為被動刪除,第一種和第三種為主動刪除,且第一種實時性更高。下面對這三種刪除策略進行具體分析。
立即刪除
立即刪除能保證內存中數據的最大新鮮度,因為它保證過期鍵值會在過期后馬上被刪除,其所占用的內存也會隨之釋放。但是立即刪除對cpu是最不友好的。因為刪除操作會占用cpu的時間,如果剛好碰上了cpu很忙的時候,比如正在做交集或排序等計算的時候,就會給cpu造成額外的壓力。
而且目前redis事件處理器對時間事件的處理方式--無序鏈表,查找一個key的時間復雜度為O(n),所以並不適合用來處理大量的時間事件。
惰性刪除
惰性刪除是指,某個鍵值過期后,此鍵值不會馬上被刪除,而是等到下次被使用的時候,才會被檢查到過期,此時才能得到刪除。所以惰性刪除的缺點很明顯:浪費內存。dict字典和expires字典都要保存這個鍵值的信息。
舉個例子,對於一些按時間點來更新的數據,比如log日志,過期后在很長的一段時間內可能都得不到訪問,這樣在這段時間內就要拜拜浪費這么多內存來存log。這對於性能非常依賴於內存大小的redis來說,是比較致命的。
定時刪除
從上面分析來看,立即刪除會短時間內占用大量cpu,惰性刪除會在一段時間內浪費內存,所以定時刪除是一個折中的辦法。
定時刪除是:每隔一段時間執行一次刪除操作,並通過限制刪除操作執行的時長和頻率,來減少刪除操作對cpu的影響。另一方面定時刪除也有效的減少了因惰性刪除帶來的內存浪費。
六:redis使用的策略
redis使用的過期鍵值刪除策略是:惰性刪除加上定期刪除,兩者配合使用。