Redis 過期刪除策略和內存淘汰機制


 
 
 

Redis 設置過期時間

Redis 有四個不同的命令可以用於設置鍵的生存時間(鍵可以存在多久)或過期時間(鍵什么時候會被刪除):
  • EXPIRE <key> <ttl> ——將鍵 key 的生存時間設置為 ttl 秒。
  • PEXPIRE <key> <ttl>——將鍵 key 的生存時間設置為 ttl 毫秒。
  • EXPIREAT <key> <timestamp>——將鍵 key 的過期時間設置為 timestamp 所指定的秒數時間戳。
  • PEXPIREAT <key> <timestamp>——將鍵 key 的過期時間設置為 timestamp 所指定的毫秒數時間戳。
雖然有多種不同單位和不同形式的設置命令,但實際上 EXPIRE 、PEXPIRE 、EXPIREAT 三個命令都是使用 PEXPIREAT 命令來實現的:無論客戶端執行的是四個命令中的哪一個,經過轉換之后,最終的執行效果都和執行 PEXPIREAT 命令一樣。
 

 

Redis 計算並返回剩余時間

Redis 提供了兩個命令,其中 TTL 命令以秒為單位返回鍵的剩余生存時間;PTTL 命令則以毫秒為單位返回鍵的剩余生存時間。TTL 和 PTTL 兩個命令都是通過計算鍵的過期時間和當前時間之間的差來實現的。
 
 

 

Redis 過期字典

redisDb 結構的 expires 字典保存了數據庫中所有鍵的過期時間,我們稱這個字典為過期字典:
  • 過期字典的鍵是一個指針,這個指針指向鍵空間的某個鍵對象(也即是某個數據庫鍵)。
  • 過期字典的值是一個 long long 類型的整數,這個整數保存了鍵所指向的數據庫鍵的過期時間——一個毫秒精度的 UNIX 時間戳。
 
 

 

Redis 過期鍵的判定

通過過期字典,程序可以用以下步驟檢查一個給定鍵是否過期:
  1. 檢查給定鍵是否存在於過期字典,如果存在,那么取得鍵的過期時間。
  2. 檢查當前 UNIX 時間戳是否大於鍵的過期時間,如果是的話,那么鍵已經過期,否則的話,鍵未過期。
 
 

 

三種過期鍵刪除策略

定時刪除

在設置鍵的過期時間的同時,創建一個定時器(timer),讓定時器在鍵的過期時間來臨時,立即執行對鍵的刪除操作。

優點:

  • 對內存是最友好的。通過使用定時器,定時刪除策略可以保證過期鍵會盡可能快地被刪除,並釋放過期鍵所占用地內存。

缺點:

  • 對 CPU 時間是最不友好的。在過期鍵比較多的情況下,刪除過期鍵這一行為可能會占用相當一部分 CPU 時間,在內存不緊張但是 CPU 時間非常緊張的情況下,將 CPU 時間用在刪除和當前任務無關的過期鍵上,無疑會對服務器的響應時間和吞吐量造成影響。
  • 創建一個定時器需要用到 Redis 服務器中的時間事件,而當前時間事件的實現方式是無序鏈表,查找一個事件的時間復雜度為 O(N),這並不能高效地處理大量時間事件。
 

惰性刪除

放任鍵過期不管,但是每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過期,如果過期的話,就刪除該鍵;如果沒有過期,就返回該鍵。

優點:

  • 對 CPU 時間來說是最友好的。程序只會在取出鍵時才對鍵進行過期檢查,這可以保證刪除過期鍵的操作只會在非做不可的情況下進行,並且刪除的目標僅限於當前處理的鍵,這個策略不會在刪除其他無關的過期鍵上花費任何 CPU 時間。

缺點:

  • 對內存是最不友好的。如果一個鍵已經過期,而這個鍵又仍然保留在數據庫中,那么只要這個過期鍵不被刪除,它所占用的內存就不會釋放。如果數據庫中有非常多的過期鍵,而這些過期鍵又恰好沒有被訪問到的話,那么它們也許永遠也不會被刪除(除非用戶手動執行 FLUSHDB),我們甚至可以將這種情況看作是一種內存泄露,無用的垃圾數據占用了大量內存,而服務器卻不會自己去釋放它們。
 

定期刪除

每隔一段時間,程序就對數據庫進行一次檢查,刪除里面的過期鍵。至於要刪除多少過期鍵,以及要檢查多少個數據庫,則由算法決定。

優點:

  • 之前討論的兩種刪除策略都有明顯的缺陷,定期刪除策略是前兩種策略的一種整合和折中。
  • 定期刪除策略每隔一段時間執行一次刪除過期鍵操作,並通過限制刪除操作執行的時長和頻率來減少刪除操作對 CPU 時間的影響。
  • 除此以外,通過定期刪除過期鍵,定期刪除策略有效地減少了因為過期鍵而帶來的內存浪費。

缺點:

  • 定期刪除的難點是確定刪除操作執行的時長和頻率。
  • 如果刪除操作執行得太頻繁,或者執行的時間太長,定期刪除策略就會退化成定時刪除策略,以至於將 CPU 時間過多地消耗在刪除過期鍵上面。
  • 如果刪除操作執行得太少,或者執行的時間太短,定期刪除策略又會和惰性刪除策略一樣,出現浪費內存的情況。
 
 

 

Redis 的過期刪除策略

前面討論了定時刪除、惰性刪除和定期刪除三種過期鍵刪除策略, Redis 服務器實際使用的是惰性刪除和定期刪除兩種策略。通過配合使用這兩種策略,服務器可以很好地在合理使用 CPU 時間和避免浪費內存空間之間取得平衡。
 

惰性刪除策略的實現

過期鍵的惰性刪除策略由 db.c/expireIfNeeded 函數實現,所有讀寫數據庫的 Redis 命令在執行之前都會調用 expireIfNeeded 函數對輸入鍵進行檢查:
  1. 如果輸入鍵已經過期,那么 expireIfNeeded 函數將輸入鍵從數據庫中刪除。
  2. 如果輸入鍵未過期,那么 expireIfNeeded 函數不做動作。
 

定期刪除策略的實現

過期鍵的定期刪除策略由 redis.c/activeExpireCycle 函數實現,每當 Redis 的服務器周期性操作 redis.c/serverCron 函數執行時,activeExpireCycle 函數就會調用,它在規定的時間內,分多次遍歷服務器中的各個數據庫,從數據庫的 expires 字典中隨機檢查一部分鍵的過期時間,並刪除其中的過期鍵。
Redis 默認每秒進行 10 次過期掃描(Redis 的配置文件里面的 hz 參數配置),過期掃描不會遍歷過期字典中所有的 key,而是采用了一種簡單的貪心策略,步驟如下:
  1. 從過期字典中隨機選出 20 個 key。
  2. 刪除這 20 個 key 中已經過期的 key。
  3. 如果過期的 key 的比例超過 1/4,那就重復步驟(1)。
同時,為了保證過期掃描不會出現循環過度,導致線程卡死的現象,算法還增加了掃描時間的上限,默認不會超過 25ms。
 
 
 

 

Redis 的內存淘汰機制

為了限制最大使用內存,Redis 提供了配置參數 maxmemory 來限制內存超出期望大小。
當實際內存超出 maxmemory 時,Redis 提供了幾種可選策略( maxmemory-policy)來讓用戶自己決定該如何騰出新的空間以繼續提供讀寫服務。
  • noeviction:不會繼續服務寫請求(del 請求可以繼續服務),讀請求可以繼續進行。這樣可以保證不會丟失數據,但是會讓線上的業務不能持續進行。這是默認的淘汰策略。
  • volatile-lru:嘗試淘汰設置了過期時間的 key,最少使用的 key 優先被淘汰。沒有設置過期時間的 key 不會被淘汰,這樣可以保證需要持久化的數據不會突然丟失。
  • volatile-ttl:跟上面幾乎一樣,不過淘汰的策略不是 LRU,而是比較 key 的剩余壽命 ttl 的值,ttl 越小越優先被淘汰。
  • volatile-random:跟上面幾乎一樣,不過淘汰的 key 是過期 key 集合中隨機的 key。
  • alllkeys-lru:區別於 volatille-lru,這個策略要淘汰的 key 對象是全體的 key 集合,而不只是過期的 key 集合,這意味着一些沒有設置過期時間的 key 也會被淘汰。
  • alllkeys-random:跟上面幾乎一樣,不過淘汰的 key 是隨機的 key。
 
volatile-xxx 策略只會針對帶過期時間的 key 進行淘汰,allkeys-xxx 策略會對所有的 key 進行淘汰。如果你只是拿 Redis 做緩存,那么應該使用 allkeys-xxx 策略,客戶端寫緩存時不必攜帶過期時間。如果你還想同時使用 Redis 的持久化功能,那就使用 volatile-xxx 策略,這樣可以保留沒有設置過期時間的 key,它們是永久的 key,不會被 LRU 算法淘汰。
 
 
 

 

Redis 的 LRU 算法

  Redis 使用的是一種近似 LRU 算法。之所以不使用 LRU 算法,是因為其需要消耗大量的額外內存,需要對現有的數據結構進行較大的改造。近似 LRU 算法很簡單,在現有數據結構的基礎上使用隨機采樣法來淘汰元素,能達到和 LRU 算法非常近似的效果。
  當 Redis 執行寫操作時,發現內存超出 maxmemory,就會執行一次 LRU 淘汰算法。這個算法也很簡單,就是隨機采樣出 5 個 key(數量可以配置,maxmemory_samples),然后淘汰掉最舊的 key,如果淘汰后內存還是超出 maxmemory,那就繼續隨機采樣淘汰,直到內存低於 maxmemory 為止。
如何采樣要看 maxmemory-policy 的設置,如果是 allkeys,就從所有的 key 字典中隨機采樣,如果是 volatile,就從帶過期時間的 key 字典中隨機采樣。每次采樣多少個 key 取決於 maxmemory_samples 的設置,默認為 5。
  Redis 3.0 在算法中增加了淘汰池,進一步提升了近似 LRU 算法的效果。淘汰池是一個數組,它的大小是 maxmemory_samples,在每一次淘汰循環中,新的隨機得出的 key 列表會和淘汰池中的 key 列表進行融合,淘汰掉最舊的一個 key 之后,保留剩余較舊的 key 列表放入淘汰池中留待下一個循環。
 
 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM