談談Redis針對鍵生存時間以及過期的處理
一直以來,我對redis中對鍵的生存時間以及過期鍵的處理甚感興趣,以前從沒有去深入瞭解,在看了黃健巨集寫的《Redis設計與實現》後,突然醍醐灌頂,恍然大悟,並不得不感嘆作者設計的巧妙。僅以此文章,記錄一下讀後的收穫。
關於鍵的生存時間或過期時間設定
通過命令EXPIRE或PEXPIRE,客戶端可以以秒或者毫秒精度為資料庫中的某個鍵設定生存時間,那麼,redis是如何儲存這些“特殊”的鍵值對呢?又是如何實現的呢?
redis中提供了4中命令用於設定鍵的生存時間或過期時間(鍵在哪個時間被刪除),雖然提供了多種形式的設定命令,但最終都是轉化成一個PEXPIREAT命令(將鍵設定為指定的毫秒數時間戳),見下圖:
操作如下:
redis在底層有個expire字典,該字典儲存了資料庫中所有鍵的過期時間,其中
- 過期字典的鍵是一個指標,該指標指向鍵空間的某個鍵物件
- 過期字典的值是long long型別的整數,這個整數儲存了鍵所指向的資料庫過期時間---- 一個毫秒精度的unix時間戳
關於過期鍵的處理
按照現實的場景,假如一個鍵過期了,那麼我們怎麼刪除它呢?不難想到,我們可能有如下的三種可能的方案:
- 定時刪除:建立一個定時器,高頻地對鍵進行掃描,對於已過期的鍵執行刪除操作
- 惰性刪除:每當從鍵空間取鍵時,檢查取得鍵是否過期,若過期,則執行刪除,如果沒過期,則正常返回
- 定期刪除:定期對整個資料庫做一個check, 刪除裡面的過期鍵
思考,以上三種策略中,第一與第三種為主動刪除策略,第二種為被動刪除策略,那麼它們各自有什麼特點呢?
定時刪除
定時刪除策略很明顯對記憶體是最友好的,通過使用定時器,定時刪除策略可以保證過期鍵會被儘快地幹掉,並釋放過期鍵佔用的記憶體。當然缺點也很明顯,對CPU相當不友好。伺服器中過期鍵比較多時,刪除過期鍵的操作本身就會消耗很多cpu時間。當伺服器中過期鍵比較少時,不斷地檢索未過期的key也會佔用相當一部分cpu時間
惰性刪除
惰性刪除就簡單了,程式只會在被取到時才對鍵進行檢查,這個策略保證了刪除的目標僅限於當前處理的鍵,不會在刪除其他過期鍵上花費任何cpu時間。雖然對cpu友好,但對記憶體來說,這是個災難。如果伺服器中恰巧有很多過期的鍵,且一直未被訪問到,且這種key會隨時間一直遞增,這些資料在某種程度上算是垃圾資料,理應刪除的,但由於一直存在,會消耗大量的伺服器記憶體,最終伺服器會不堪重負,記憶體耗盡而掛掉。
定期刪除
從上面的兩種策略來看,這兩種中的任意一種在單一使用時,都會存在非常明顯的缺點
- 定時刪除佔用太多cpu時間,影響伺服器的響應時間和吞吐量
- 惰性刪除浪費太多記憶體,有記憶體洩露的風險
定期刪除是前兩種策略的一種整合和折中:
- 定期刪除策略每隔一段時間執行一次刪除過期鍵的操作,並通過限制刪除操作執行時長和頻率來減少操作對cpu時間的影響
- 除此之外,通過定期刪除過期鍵,有效地減少了因過期鍵而帶來的記憶體浪費
定期刪除的難點在於確定刪除操作執行的時長和頻率:
- 如果刪除操作執行得很頻繁,或者執行的時間過長,則定期刪除策略就會退化為定時刪除策略
- 如果刪除操作執行得太少,或者執行時間比較短,則又退化為惰性刪除,出現浪費記憶體的現象
Redis 的過期鍵刪除策略
redis實際使用的惰性刪除+定期刪除兩種策略,通過配合使用這兩種刪除策略可以很好地在合理使用cpu時間和避免浪費記憶體之間取得平衡。惰性刪除和定期刪除的策略實現,可見原文第九章。
總結:
- 鍵生存時間的設定是依靠過期字典,過期字典存的是key 和對應失效時間的unix毫秒時間戳,判斷是否過期以及ttl均通過計算實現
- redis採取惰性刪除+定期刪除的方式實現對過期鍵的處理(原文對serverCron函式的設定和依據沒有做過多闡述,也就是定期刪除執行的時長和頻率這塊沒有做更細的闡述),如何合理設定定期刪除的週期有待繼續探究