[轉帖]Redis中刪除過期Key的三種策略

济南小老虎發表於2024-06-19
Redis對於過期鍵有三種清除策略
  • 被動刪除:當讀/寫一個已經過期的key時,會觸發惰性刪除策略,直接刪除掉這個過期key
  • 主動刪除:由於惰性刪除策略無法保證冷資料被及時刪掉,所以Redis會定期主動隨機淘汰一批已過期的key
  • 當前已用記憶體超過maxmemory限定時,觸發主動清理策略

被動刪除

只有key被操作時(如GET),REDIS才會被動檢查該key是否過期,如果過期則刪除之並且返回NIL。
1、這種刪除策略對CPU是友好的,刪除操作只有在不得不的情況下才會進行,不會對其他的expire key上浪費無謂的CPU時間。
2、但是這種策略對記憶體不友好,一個key已經過期,但是在它被操作之前不會被刪除,仍然佔據記憶體空間。如果有大量的過期鍵存在但是又很少被訪問到,那會造成大量的記憶體空間浪費。expireIfNeeded(redisDb *db, robj *key)函式位於src/db.c。
但僅是這樣是不夠的,因為可能存在一些key永遠不會被再次訪問到,這些設定了過期時間的key也是需要在過期後被刪除的,我們甚至可以將這種情況看作是一種記憶體洩露—-無用的垃圾資料佔用了大量的記憶體,而伺服器卻不會自己去釋放它們,這對於執行狀態非常依賴於記憶體的Redis伺服器來說,肯定不是一個好訊息。

主動刪除

先說一下時間事件,對於持續執行的伺服器來說, 伺服器需要定期對自身的資源和狀態進行必要的檢查和整理, 從而讓伺服器維持在一個健康穩定的狀態, 這類操作被統稱為常規操作(cron job)

在 Redis 中, 常規操作由 redis.c/serverCron 實現, 它主要執行以下操作

  • 更新伺服器的各類統計資訊,比如時間、記憶體佔用、資料庫佔用情況等。
  • 清理資料庫中的過期鍵值對。
  • 對不合理的資料庫進行大小調整。
  • 關閉和清理連線失效的客戶端。
  • 嘗試進行 AOF 或 RDB 持久化操作。
  • 如果伺服器是主節點的話,對附屬節點進行定期同步。
  • 如果處於叢集模式的話,對叢集進行定期同步和連線測試。

Redis 將 serverCron 作為時間事件來執行, 從而確保它每隔一段時間就會自動執行一次, 又因為 serverCron 需要在 Redis 伺服器執行期間一直定期執行, 所以它是一個迴圈時間事件: serverCron 會一直定期執行,直到伺服器關閉為止。

在 Redis 2.6 版本中, 程式規定 serverCron 每秒執行 10 次, 平均每 100 毫秒執行一次。 從 Redis 2.8 開始, 使用者可以透過修改 hz選項來調整 serverCron 的每秒執行次數, 具體資訊請參考 redis.conf 檔案中關於 hz 選項的說明也叫定時刪除,這裡的“定期”指的是Redis定期觸發的清理策略,由位於src/redis.c的activeExpireCycle(void)函式來完成。

serverCron是由redis的事件框架驅動的定位任務,這個定時任務中會呼叫activeExpireCycle函式,針對每個db在限制的時間REDIS_EXPIRELOOKUPS_TIME_LIMIT內遲可能多的刪除過期key,之所以要限制時間是為了防止過長時間 的阻塞影響redis的正常執行。這種主動刪除策略彌補了被動刪除策略在記憶體上的不友好。

因此,Redis會週期性的隨機測試一批設定了過期時間的key並進行處理。測試到的已過期的key將被刪除。典型的方式為,Redis每秒做10次如下的步驟:

  1. 隨機測試100個設定了過期時間的key
  2. 刪除所有發現的已過期的key
  3. 若刪除的key超過25個則重複步驟1

這是一個基於機率的簡單演算法,基本的假設是抽出的樣本能夠代表整個key空間,redis持續清理過期的資料直至將要過期的key的百分比降到了25%以下。這也意味著在任何給定的時刻已經過期但仍佔據著記憶體空間的key的量最多為每秒的寫操作量除以4.

Redis-3.0.0中的預設值是10,代表每秒鐘呼叫10次後臺任務。

maxmemory
當前已用記憶體超過maxmemory限定時,觸發主動清理策略

  • volatile-lru:只對設定了過期時間的key進行LRU(預設值)
  • allkeys-lru : 刪除lru演算法的key
  • volatile-random:隨機刪除即將過期key
  • allkeys-random:隨機刪除
  • volatile-ttl : 刪除即將過期的
  • noeviction : 永不過期,返回錯誤

當mem_used記憶體已經超過maxmemory的設定,對於所有的讀寫請求,都會觸發redis.c/freeMemoryIfNeeded(void)函式以清理超出的記憶體。注意這個清理過程是阻塞的,直到清理出足夠的記憶體空間。所以如果在達到maxmemory並且呼叫方還在不斷寫入的情況下,可能會反覆觸發主動清理策略,導致請求會有一定的延遲。

清理時會根據使用者配置的maxmemory-policy來做適當的清理(一般是LRU或TTL),這裡的LRU或TTL策略並不是針對redis的所有key,而是以配置檔案中的maxmemory-samples個key作為樣本池進行抽樣清理。

maxmemory-samples在redis-3.0.0中的預設配置為5,如果增加,會提高LRU或TTL的精準度,redis作者測試的結果是當這個配置為10時已經非常接近全量LRU的精準度了,並且增加maxmemory-samples會導致在主動清理時消耗更多的CPU時間,建議:

  • 儘量不要觸發maxmemory,最好在mem_used記憶體佔用達到maxmemory的一定比例後,需要考慮調大hz以加快淘汰,或者進行叢集擴容。
  • 如果能夠控制住記憶體,則可以不用修改maxmemory-samples配置;如果Redis本身就作為LRU cache服務(這種服務一般長時間處於maxmemory狀態,由Redis自動做LRU淘汰),可以適當調大maxmemory-samples。

這裡提一句,實際上redis根本就不會準確的將整個資料庫中最久未被使用的鍵刪除,而是每次從資料庫中隨機取5個鍵並刪除這5個鍵裡最久未被使用的鍵。上面提到的所有的隨機的操作實際上都是這樣的,這個5可以用過redis的配置檔案中的maxmemeory-samples引數配置。

Replication link和AOF檔案中的過期處理

為了獲得正確的行為而不至於導致一致性問題,當一個key過期時DEL操作將被記錄在AOF檔案並傳遞到所有相關的slave。也即過期刪除操作統一在master例項中進行並向下傳遞,而不是各salve各自掌控。這樣一來便不會出現資料不一致的情形。當slave連線到master後並不能立即清理已過期的key(需要等待由master傳遞過來的DEL操作),slave仍需對資料集中的過期狀態進行管理維護以便於在slave被提升為master會能像master一樣獨立的進行過期處理。

</article>

相關文章