Redis 過期鍵刪除和內存淘汰策略【Redis 系列之四】
〇、前言
對于 Redis 服務器來說,內存資源非常寶貴,如果一些過期鍵一直不被刪除,就會造成資源浪費。
那么,本文將結合博主收集的資料,簡單介紹下過期鍵刪除、內存淘汰兩個策略,僅供參考。
博主 Redis 相關文章都在這里了:http://www.rzrgm.cn/hnzhengfy/category/2229717.html
一、Redis 的過期時間配置
1.1 設置過期時間的方法
首先是在設置鍵值對的同時指定過期時間:
# 語法
SET key value [EX seconds] [PX milliseconds]
# 示例
SET mykey "hello" EX 60 # 設置 mykey 的值為 "hello",并在 60 秒后過期
SET mykey "hello" PX 60000 # 設置 mykey 的值為 "hello",并在 60000 毫秒(即 60 秒)后過期
然后是給已存在的鍵添加過期時間,共四種命令:
# 【expire 命令】設置以【秒】為單位的過期時間
# 語法:
EXPIRE key seconds
# 示例:
SET mykey "hello"
EXPIRE mykey 60 # 設置 mykey 在 60 秒后過期
# 【pexpire 命令】設置以【毫秒】為單位的過期時間
# 語法:
PEXPIRE key milliseconds
# 示例:
SET mykey "hello"
PEXPIRE mykey 60000 # 設置 mykey 在 60000 毫秒(即 60 秒)后過期
# 【expireat 命令】設置一個具體的 Unix 時間戳作為過期時間(以【秒】為單位)
# 語法:
EXPIREAT key timestamp
# 示例:
SET mykey "hello"
EXPIREAT mykey 1743283200 # 設置 mykey 在 Unix 時間戳 1743283200 時過期
# 【pexpireat 命令】設置一個具體的 Unix 時間戳作為過期時間(以【毫秒】為單位)
# 語法:
PEXPIREAT key timestamp_ms
# 示例:
SET mykey "hello"
PEXPIREAT mykey 1743283200000 # 設置 mykey 在 Unix 時間戳 1743283200000 時過期
1.2 如何判斷一個鍵是否已經過期?
- 首先看下一個帶過期時間的鍵如何保存?
Redis 在內存中存儲鍵值對時,會為每個鍵維護一個獨立的數據結構來記錄其元信息(如過期時間)。
鍵值對存儲在全局哈希表(dict)中。每個鍵對應一個 redisObject,該對象包含了鍵的值以及與該鍵相關的元信息(例如類型、編碼方式等)。
過期時間存儲在一個專門的字典(expires 字典)中,鍵是普通鍵名,值是該鍵的過期時間(以 Unix 時間戳的形式存儲)。
# 示例
SET mykey "hello" EX 60
# mykey 被存儲在主字典中,指向值 "hello"。
# 同時,mykey 也會被添加到 expires 字典中,值為當前時間 + 60 秒的時間戳。
如果一個鍵沒有設置過期時間,則它不會出現在 expires 字典中。
- 讀取鍵時會先進行過期檢查
當訪問一個鍵時(如 GET 命令),Redis 會先檢查該鍵是否存在于 expires 字典中。如果存在,Redis 會將當前時間與 expires 字典中存儲的過期時間進行比較:
如果,當前時間 >= 過期時間:認為該鍵已過期,返回 nil。
如果,當前時間 < 過期時間:認為該鍵仍然有效。
二、過期鍵刪除策略
2.1 惰性刪除(Lazy Deletion)(被動刪除)
惰性刪除是 Redis 核心工作機制的一部分,因此默認啟用,并且無法通過配置項來手動關閉。
針對惰性刪除有三個要點:
被動觸發:當客戶端訪問一個鍵時(如 GET、DEL 等命令),Redis 會首先檢查該鍵是否已設置過期時間。
過期檢查:如果鍵已過期(當前時間 ≥ 過期時間),Redis 會立即刪除該鍵,并返回 nil(表示鍵不存在)。
不主動清理:未被訪問的過期鍵不會被刪除,直到下次被訪問時才會觸發檢查。
- 優勢
高效性:僅在鍵被訪問時檢查過期,避免了不必要的 CPU 資源消耗。
低延遲:不會因過期鍵的清理操作導致系統性能波動。
- 劣勢
內存浪費:長時間未被訪問的過期鍵會持續占用內存,可能導致內存泄漏。
不可控性:無法主動清理未被訪問的過期鍵。
2.2 定期刪除(Periodic Deletion)(主動刪除)
定期刪除就是以固定的時間間隔,來掃描 Redis 數據庫中的鍵,發現過期的鍵立即刪掉,可以通過配置來設置具體頻率。
要點:
主動觸發:Redis 內部通過一個后臺任務(serverCron)周期性地掃描過期鍵,并主動刪除它們。
隨機采樣:每次掃描時,Redis 會從數據庫的 expires 字典中隨機選取一批鍵(默認 20 個),檢查是否過期,并刪除已過期的鍵。
動態調整:通過參數 hz(默認值 10)控制掃描頻率,即每秒執行 hz 次掃描任務。掃描時長受 CPU 時間限制(默認不超過 25% 的 CPU 時間)。
簡要的步驟:
- 遍歷所有數據庫:依次檢查每個數據庫(DB)。
- 隨機采樣:從每個數據庫的 expires 字典中隨機選取 20 個鍵。
- 刪除過期鍵:將過期的鍵從主字典和 expires 字典中刪除。
- 重復掃描:如果發現 25% 以上的采樣鍵已過期,則重復掃描,直到過期鍵比例低于閾值或達到 CPU 時間限制。
- 退出條件:若達到預設的 CPU 時間限制(如每秒 25ms),則停止掃描。
- 優勢:
內存友好:主動清理過期鍵,減少內存浪費。
可控性:通過調整 hz 和掃描參數,平衡性能與內存占用。
- 劣勢:
延遲性:過期鍵可能在掃描間隔內仍存在,無法立即刪除。
資源占用:頻繁掃描可能增加 CPU 負載(但通過時間限制避免了極端情況)。
- 如何配置?
hz:配置 serverCron 的執行頻率(默認 10,即每秒 10 次)。
ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC:該參數決定了 Redis 在單次過期鍵清理周期中,最多可以占用的 CPU 時間比例(默認 25%)。
# 計算公式:
最大允許 CPU 時間(微秒) = (1000000 * ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC) / (server.hz * 100)
# 例如:當 hz=10
最大允許時間 = (1000000 * 25) / (10 * 100) = 25,000 微秒(即 25 毫秒) # 單次清理,只運行 25 毫秒
# 例如:當 hz=1
最大允許時間 = (1000000 * 25) / (1 * 100) = 250,000 微秒(即 250 毫秒) # 單次清理,只運行 250 毫秒
若某次清理發現大量過期鍵(如 20 個采樣鍵中有 6 個過期),Redis 會繼續掃描直到過期鍵比例下降或時間耗盡。
2.3 綜合策略:惰性刪除+定期刪除
Redis 的過期鍵刪除策略結合了惰性刪除(Lazy Deletion)和定期刪除(Periodic Deletion),通過兩者的互補性實現性能與內存占用的平衡。
兩者結合帶來的好處:
高效性:惰性刪除僅在鍵被訪問時觸發,避免了不必要的 CPU 開銷。定期刪除通過隨機采樣和動態調整,平衡了 CPU 負載與內存清理效率。
內存友好:惰性刪除確保被訪問的過期鍵立即釋放內存。定期刪除主動清理未被訪問的過期鍵,減少內存泄漏風險。
典型應用場景:
針對高頻訪問場景,惰性刪除更有優勢,能夠快速清理過期鍵,減少內存占用。當然,不可避免的有部分鍵是沒有被訪問的,這時就需要定期刪除來及時清理。
針對內存敏感場景,定期刪除可以避免過期鍵長期占用空間,但要仔細考量配置項的值,避免 CPU 過載。惰性刪除則盡快清除失效鍵。
針對低資源環境,更加需要兩者的有機配合。惰性刪除減少 CPU 開銷,定期刪除通過參數控制資源消耗。也可能需犧牲部分內存利用率以換取 CPU 節省。
三、內存淘汰策略
3.1 觸發條件
Redis 的內存淘汰策略(Memory Eviction Policy)是在 Redis 達到最大內存限制(由 maxmemory 參數配置)時,用于決定如何淘汰(刪除)現有數據以騰出空間的機制。
兩個條件全部滿足才觸發:
- 達到最大內存限制:當 Redis 使用的內存超過 maxmemory 配置的閾值時。
- 需要分配新內存:當嘗試寫入新數據或更新現有數據時,如果內存不足,觸發淘汰策略。
3.2 八種內存淘汰策略
八種策略實際上可以分為三類。
1)不淘汰任何數據(默認策略)
- maxmemory-policy noeviction
當內存不足時,拒絕所有寫操作(如SET、ADD等),并返回錯誤(OOM command not allowed when used memory > 'maxmemory')。
適用于對數據完整性要求高的場景,避免因內存不足導致數據丟失。
2)在所有鍵(allkeys)中選擇淘汰
這類策略會從所有鍵(無論是否設置了過期時間)中選擇淘汰對象。
- maxmemory-policy allkeys-lru
淘汰最近最少使用的(Least Recently Used)鍵。根據鍵的最近訪問時間(上次讀或寫的時間),選擇最久未使用的鍵淘汰。
適合訪問模式不固定、需要保留近期活躍數據的場景。
- maxmemory-policy allkeys-lfu
淘汰最近最少使用的(Least Frequently Used)鍵。根據鍵的訪問頻率(讀寫次數),選擇使用頻率最低的鍵淘汰。
適合頻繁訪問少數熱點數據的場景(如熱門商品緩存)。
- maxmemory-policy allkeys-random
隨機選擇一個鍵淘汰。
適用于對數據淘汰的公平性要求不高,追求簡單快速的淘汰方式。
3)僅在設置了過期時間的鍵(volatile)中選擇淘汰
這類策略僅從設置了過期時間的鍵中選擇淘汰對象。
- maxmemory-policy volatile-lru
在設置了過期時間的鍵中,淘汰最近最少使用的(Least Recently Used)鍵。
適用于需要淘汰過期可能性高的鍵,同時保留近期活躍數據。
- maxmemory-policy volatile-lfu
在設置了過期時間的鍵中,淘汰最近最少使用的(Least Frequently Used)鍵。
適合訪問頻率低但即將過期的鍵。
- maxmemory-policy volatile-random
在設置了過期時間的鍵中隨機選擇一個淘汰。
適用于對過期鍵的淘汰公平性要求不高的場景。
- maxmemory-policy volatile-ttl
優先淘汰剩余生存時間(TTL:Time To Live)最短的鍵。如果多個鍵的 TTL 相近,則可能隨機選擇。
適用于希望盡快清理即將過期的鍵,避免內存浪費的場景。
注意:volatile-ttl 策略可能因時間因素導致某些鍵被提前淘汰,即使它們仍被頻繁訪問。
| 業務場景 | 推薦使用的策略 |
| 數據完整性要求高 | 使用 noeviction,避免寫入失敗 |
| 訪問模式不固定 | 使用 allkeys-lru 或 volatile-lru |
| 存在熱點數據 | 使用 allkeys-lfu 或 volatile-lfu |
| 希望優先清理即將過期的數據 | 使用 volatile-ttl |
| 簡單快速淘汰 | 使用 allkeys-random 或 volatile-random |
注意:Redis 的 LRU 和 LFU 并非精確實現,而是通過采樣和統計來近似計算,以減少性能開銷。例如,maxmemory-policy allkeys-lru 會定期采樣部分鍵來估算最近最少使用情況。
另外,Redis 的內存淘汰策略與鍵的過期機制(惰性刪除+定期刪除)是獨立的。即使設置了過期時間,未被訪問的鍵仍可能因內存不足被提前淘汰。
3.3 配置和查看
# 配置格式
maxmemory <bytes> # 設置最大內存限制(如:maxmemory 1gb)
maxmemory-policy <policy> # 設置淘汰策略(如:maxmemory-policy allkeys-lru)
# 動態配置(需重啟后生效)
CONFIG SET maxmemory 1gb
CONFIG SET maxmemory-policy allkeys-lru
# 查看當前配置項
CONFIG GET maxmemory
CONFIG GET maxmemory-policy
Redis 的內存淘汰策略提供了靈活的選擇,需根據業務需求權衡數據保留策略、性能開銷和公平性。合理配置 maxmemory 和 maxmemory-policy 是優化 Redis 性能和可靠性的關鍵。
本文來自博客園,作者:橙子家,歡迎微信掃碼關注博主【橙子家czzj】,有任何疑問歡迎溝通,共同成長!
轉載本文請注明原文鏈接:http://www.rzrgm.cn/hnzhengfy/p/18784865/redis4

浙公網安備 33010602011771號