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 時間戳。
下圖是一個帶過期字典的資料庫例子:
過期字典是儲存在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
資料庫將更新成如下狀態:
可以從圖中看到,當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使用的過期鍵值刪除策略是:惰性刪除加上定期刪除,兩者配合使用。
轉自:https://www.jianshu.com/p/9352d20fb2e0