前言
Redis 中都是鍵值對的存儲形式,鍵都是字符串類型的,而值有很多種類型,如 string、list、hash、set、sorted set 等類型。當設置鍵值對時我們還應該為其設置過期時間,通過 expire 以及 pexpire 命令;還可以通過 setnx 命令設置。那么,當設置過期時間之后,到底是怎么將過期的鍵值對刪除的呢?接下來一起看看 Redis 的過期鍵刪除策略。
在說刪除策略之前先了解下是如何確定一個鍵是否過期的:
-
檢查這個鍵是否在過期字典中,如果存在,那么取出這個鍵的過期時間。(過期字典存儲的是每個鍵的過期時間,字典中 key 是 鍵, value 是 long 類型的過期時間)
-
拿到過期時間之后,和當前 UNIX 時間戳比較,如果大於,則鍵過期。
以上就是判斷一個鍵是否過期的方法。接下來說說當鍵過期了怎么去刪除。
三種刪除策略
目前來說有三種刪除策略:
-
定時刪除:在設置鍵的過期時間時,創建一個定時器,當到達鍵過期時間時通過定時器去刪除鍵。
-
惰性刪除:惰性刪除並不是當到達過期時間時去刪除,而是每次獲取鍵時,會判斷是否過期,如果過期則刪除,並返回空;沒過期,就返回鍵值。
-
定期刪除:每隔一段時間,就對數據庫中的鍵進行檢查,如果過期則刪除。至於要刪除多少什么時候刪除,則是通過具體程序決定。
下面來詳細介紹每一種刪除策略。
定時刪除
定時刪除策略
優點是:對內存友好。因為通過定時器,當一個鍵到達過期時間時就會立馬被刪除,直接就釋放了內存。
缺點是:對 CPU 不友好。因為如果過期鍵比較多,那么刪除這些過期鍵會占用相當一部分 CPU 時間,如果 CPU 時間非常緊張的話,還將 CPU 時間用在刪除和當前任務無關的過期鍵上,會對服務器的響應時間以及吞吐量造成影響。
因此,通過 定時刪除 策略來時間過期鍵的刪除不太現實。
惰性刪除
惰性刪除策略優點:對 CPU 時間友好。程序只會在取出鍵時才會判斷是否刪除,並且只作用到當前鍵上,其他過期鍵不會花費 CPU 時間去處理。
惰性刪除策略缺點:對內存不友好。因為只有鍵被使用時才會去檢查是否刪除,如果有大量的鍵一直不被使用,那么這些鍵就算過期了也不會被刪除,會一直占用着內存。這種可以理解為是一種內存泄漏——大量無用的數據一直占用着內存,並且不會被刪除。
定期刪除
相比較定時刪除對 CPU 的不友好,惰性刪除的對內存不友好。定期刪除采用了一種折中的方式:
-
定期刪除策略每隔一段時間執行一次刪除過期鍵操作,並通過限制刪除操作執行的時長和頻率來減少刪除操作對 CPU 時間的影響。
-
並且,通過定期刪除過期鍵,有效的減少了過期鍵帶來的內存浪費。
但刪除的時長和頻率比較難定義,因為:
-
如果頻率太高或者時長太長,那么會占用大量的 CPU 時長。
-
如果過短又會出現內存浪費的情況。
因此。如果采用定期刪除策略的話需要通過具體的業務場景來定義時長和頻率。
Redis 使用的刪除策略
Redis 實際使用的是惰性刪除+定期刪除的策略
通過這兩種方式可以很好的利用 CPU 時間以及避免內存浪費的情況。接下來講講惰性刪除以及定期刪除的實現。
惰性刪除策略的實現
惰性刪除策略由 expireIfNeeded 函數實現,所有讀寫數據庫的 Redis 命令在執行之前都會調用 exipreIfNeeded 函數對輸入鍵進行檢查。
-
如果鍵過期,會將鍵刪除並返回空。
-
如果鍵沒有過期,則不做操作。
定期刪除策略的實現
定期刪除策略由 activeExpireCucle 函數實現,被調用時,它在規定的時間內,分多次遍歷服務器中的各個數據庫,從數據庫的 expires 字典中隨機檢查一部分鍵的過期時間,並刪除其中的過期鍵。
-
函數每次運行時,都是從一定數量的數據庫鍵中隨機取一定數量的鍵進行檢查,並刪除其中的過期鍵。
-
有一個全局變量 current_db 會記錄當前 activeExpireCycle 函數檢查的進度,並且下一次 函數執行時,接着上一次的進度進行處理。如,當前 activeExpireCycle 函數執行到了 10, 講 current_db = 10;然后下一次函數執行時,從 current_db 取到 10 繼續執行。
-
當所有的數據庫鍵都被檢查完時, current_db = 0。
AOF、RDB 和復制功能對過期鍵的處理
生成 RDB 文件
在執行 SAVE 命令或 BGSAVE 命令創建一個新的 RDB 文件時,程序會對數據庫中的鍵進行檢查,已過期的鍵不會被保存到新的 RDB 文件中。
如:redis 中包含 r1、r2、r3 三個鍵,並且 r1 已經過期,那么程序只會講 r2 和 r3 保存到 RDB 文件中。
因此,過期鍵不會對新的 RDB 文件造成影響。
載入 RDB 文件
在啟動 redis 服務器時,如果服務器開啟了 RDB 功能,那么服務器將對 RDB 文件進行載入;
-
如果服務器以主服務器模式運行,那么在載入 RDB 文件時,過期的鍵會被過濾掉,不會被載入到 redis 數據庫中。
-
如果以從服務器模式運行,那么無論鍵是否過期都會被載入到數據庫中。但,因為主從服務器在進行數據同步時,從服務器就會被清空,所以,一般來說,過期鍵對從服務器也不會造成影響。
AOF 文件寫入
當服務器開啟 AOF 的運行模式時,如果某個鍵過期了,但沒有被惰性或定期刪除,那么 AOF 不會理會。如果被惰性或定期刪除了, AOF 會在文件末尾追加一條 DEL 命令,來顯示地記錄該鍵已被刪除。
AOF 重寫
當 AOF 重寫時,過期的鍵不會被載入到 redis 數據庫中。
復制
當服務器在 復制 模式下時,從服務器的過期鍵刪除動作都是由主服務器來進行的。
-
主服務器在刪除一個過期鍵之后,會顯示地向所有從服務器發送一個 DEL 命令,告知從服務器刪除這個過期鍵。
-
從服務器在執行客戶端發送的讀命令時,即使碰到過期的鍵也不會刪除,而是繼續的正常操作。
-
從服務器只有在接到主服務器發來的 DEL 命令之后,才會刪除過期鍵。
既然從服務器不會主動去刪除過期鍵,那么如果查詢從服務器的過期鍵怎么辦?
引用 Redis 官網上的一段話來解釋這個問題
簡單翻譯一下:
1. 從服務器不會去過期 key,它會等待 master 去過期 key,當 master 過期 key (或由於 LRU 算法驅逐),它會生成一個 DEL 命令發送給所有的從服務器。
2. 但是,由於這是 master 驅動的 key 過期行為,master 無法及時的提供 DEL 命令,導致一些從服務器有時內存中存在邏輯上已經過期的 key,為了處理這個問題,slave 使用它的邏輯時鍾以報告只有在不違反數據集的一致性的讀取操作(從主機的新命令到達)中才存在 key(這塊有點拗口,大概意思就是通過邏輯時鍾記錄一下本該過期的或等待 master DEL 命令 的 key)。通過這種方式,從節點避免了返回一個已經過期的鍵。在實際經驗中,一個通過從節點緩存去擴容的 HTML 頁面緩存將可以避免沒有按時過期的問題。
總結
Redis 的過期鍵刪除策略是 惰性刪除 + 定期刪除,這也既可以合理的控制 CPU 使用 還可以 減少內存的浪費。
參考文章:
https://xie.infoq.cn/article/cf66725d99a5832baf9b869e2