13期 談談 Redis 的過期策略

2022-09-19 03:30:13 字數 4145 閱讀 3301

在日常開發中,我們使用 redis 儲存 key 時通常會設定乙個過期時間,但是 redis 是怎麼刪除過期的 key,而且 redis 是單執行緒的,刪除 key 會不會造成阻塞。要搞清楚這些,就要了解 redis 的過期策略和記憶體淘汰機制。

redis採用的是定期刪除 + 懶惰刪除策略。

定期刪除策略

redis 會將每個設定了過期時間的 key 放入到乙個獨立的字典中,預設每 100ms 進行一次過期掃瞄:

隨機抽取 20 個 key

刪除這 20 個key中過期的key

如果過期的 key 比例超過 1/4,就重複步驟 1,繼續刪除。

為什麼不掃瞄所有的 key?

redis 是單執行緒,全部掃瞄豈不是卡死了。而且為了防止每次掃瞄過期的 key 比例都超過 1/4,導致不停迴圈卡死執行緒,redis 為每次掃瞄新增了上限時間,預設是 25ms。

如果客戶端將超時時間設定的比較短,比如 10ms,那麼就會出現大量的連線因為超時而關閉,業務端就會出現很多異常。而且這時你還無法從 redis 的 slowlog 中看到慢查詢記錄,因為慢查詢指的是邏輯處理過程慢,不包含等待時間。

如果在同一時間出現大面積 key 過期,redis 迴圈多次掃瞄過期詞典,直到過期的 key 比例小於 1/4。這會導致卡頓,而且在高併發的情況下,可能會導致快取雪崩。

為什麼 redis 為每次掃瞄添的上限時間是 25ms,還會出現上面的情況?

因為 redis 是單執行緒,每個請求處理都需要排隊,而且由於 redis 每次掃瞄都是 25ms,也就是每個請求最多 25ms,100 個請求就是 2500ms。

如果有大批量的 key 過期,要給過期時間設定乙個隨機範圍,而不宜全部在同一時間過期,分散過期處理的壓力。

從庫的過期策略

從庫不會進行過期掃瞄,從庫對過期的處理是被動的。主庫在 key 到期時,會在 aof 檔案裡增加一條 del 指令,同步到所有的從庫,從庫通過執行這條 del 指令來刪除過期的 key。

因為指令同步是非同步進行的,所以主庫過期的 key 的 del 指令沒有及時同步到從庫的話,會出現主從資料的不一致,主庫沒有的資料在從庫里還存在。

懶惰刪除策略

redis 為什麼要懶惰刪除(lazy free)?

刪除指令 del 會直接釋放物件的記憶體,大部分情況下,這個指令非常快,沒有明顯延遲。不過如果刪除的 key 是乙個非常大的物件,比如乙個包含了千萬元素的 hash,又或者在使用 flushdb 和 flushall 刪除包含大量鍵的資料庫時,那麼刪除操作就會導致單執行緒卡頓。

redis 4.0 引入了 lazyfree 的機制,它可以將刪除鍵或資料庫的操作放在後台執行緒裡執行, 從而盡可能地避免伺服器阻塞。

unlink

unlink 指令,它能對刪除操作進行懶處理,丟給後台執行緒來非同步**記憶體。

> unlink key

ok

flush

flushdb 和 flushall 指令,用來清空資料庫,這也是極其緩慢的操作。redis 4.0 同樣給這兩個指令帶來了非同步化,在指令後面增加 async 引數就可以將整棵大樹連根拔起,扔給後台執行緒慢慢焚燒。

> flushall async

ok

非同步佇列

主線程將物件的引用從「大樹」中摘除後,會將這個 key 的記憶體**操作包裝成乙個任務,塞進非同步任務佇列,後台執行緒會從這個非同步佇列中取任務。任務佇列被主線程和非同步執行緒同時操作,所以必須是乙個執行緒安全的佇列。

不是所有的 unlink 操作都會延後處理,如果對應 key 所占用的記憶體很小,延後處理就沒有必要了,這時候 redis 會將對應的 key 記憶體立即**,跟 del 指令一樣。

更多非同步刪除點

redis **記憶體除了 del 指令和 flush 之外,還會存在於在 key 的過期、lru 淘汰、rename 指令以及從庫全量同步時接受完 rdb 檔案後會立即進行的 flush 操作。

redis4.0 為這些刪除點也帶來了非同步刪除機制,開啟這些點需要額外的配置選項。

記憶體淘汰機制

redis 的記憶體占用會越來越高。redis 為了限制最大使用記憶體,提供了 redis.conf 中的

配置引數 maxmemory。當記憶體超出 maxmemory,redis 提供了幾種記憶體淘汰機制讓使用者選擇,配置 maxmemory-policy:

lru 演算法(最近最少使用)

實現 lru 演算法除了需要 key/value 字典外,還需要附加乙個鍊錶,鍊錶中的元素按照一定的順序進行排列。當空間滿的時候,會踢掉鍊錶尾部的元素。當字典的某個元素被訪問時,它在鍊錶中的位置會被移動到表頭。所以鍊錶的元素排列順序就是元素最近被訪問的時間順序。

近似 lru 演算法

redis 使用的並不是完全 lru 演算法。不使用 lru 演算法,是為了節省記憶體,redis 採用的是隨機lru演算法,redis 為每乙個 key 增加了乙個24 bit的字段,用來記錄這個 key 最後一次被訪問的時間戳。

注意 redis 的 lru 淘汰策略是懶惰處理,也就是不會主動執行淘汰策略,當 redis 執行寫操作時,發現記憶體超出 maxmemory,就會執行 lru 淘汰演算法。這個演算法就是隨機取樣出5(預設值)個 key,然後移除最舊的 key,如果移除後記憶體還是超出 maxmemory,那就繼續隨機取樣淘汰,直到記憶體低於 maxmemory 為止。

如何取樣就是看 maxmemory-policy 的配置,如果是 allkeys 就是從所有的 key 字典中隨機,如果是 volatile 就從帶過期時間的 key 字典中隨機。每次取樣多少個 key 看的是 maxmemory_samples 的配置,預設為 5。

lfu(最不經常使用)

redis 4.0 裡引入了乙個新的淘汰策略 —— lfu(least frequently used) 模式,作者認為它比 lru 更加優秀。

lfu 表示按最近的訪問頻率進行淘汰,它比 lru 更加精準地表示了乙個 key 被訪問的熱度。

如果乙個 key 長時間不被訪問,只是剛剛偶然被使用者訪問了一下,那麼在使用 lru 演算法下它是不容易被淘汰的,因為 lru 演算法認為當前這個 key 是很熱的。而 lfu 是需要追蹤最近一段時間的訪問頻率,如果某個 key 只是偶然被訪問一次是不足以變得很熱的,它需要在近期一段時間內被訪問很多次才有機會被認為很熱。

redis物件的熱度

redis 的所有物件結構頭中都有乙個 24bit 的字段,這個字段用來記錄物件的熱度。

// redis 的物件頭

typedef struct redisobject robj;

lru模式

在 lru 模式下,lru 字段儲存的是 redis 時鐘 server.lruclock,redis 時鐘是乙個 24bit 的整數,預設是 unix 時間戳對 2^24 取模的結果,大約 97 天清零一次。當某個 key 被訪問一次,它的物件頭的 lru 字段值就會被更新為 server.lruclock。

lfu模式

在 lfu 模式下,lru 字段 24 個 bit 用來儲存兩個值,分別是 ldt(last decrement time) 和 logc(logistic counter)。

logc 是 8 個 bit,用來儲存訪問頻次,因為 8 個 bit 能表示的最大整數值為 255,儲存頻次肯定遠遠不夠,所以這 8 個 bit 儲存的是頻次的對數值,並且這個值還會隨時間衰減。如果它的值比較小,那麼就很容易被**。為了確保新建立的物件不被**,新物件的這 8 個 bit 會初始化為乙個大於零的值,預設是 lfu_init_val=5。

ldt 是 16 個位,用來儲存上一次 logc 的更新時間,因為只有 16 位,所以精度不可能很高。它取的是分鐘時間戳對 2^16 進行取模,大約每隔 45 天就會折返。

同 lru 模式一樣,我們也可以使用這個邏輯計算出物件的空閒時間,只不過精度是分鐘級別的。圖中的 server.unixtime 是當前 redis 記錄的系統時間戳,和 server.lruclock 一樣,它也是每毫秒更新一次。

吃水不忘挖井人:

redis過期策略

1 noeviction 一旦記憶體滿則返回錯誤 2 allkeys lru 對所有的key進行lru 3 volatile lru 只對設定了過期的key進行lru 預設的方式 4 allkeys random 隨機剔除乙個key 5 volatile random 對設定過期的key進行隨機剔除...

Redis過期策略

1,設定過期時間 expire key time 單位為秒 setex string key,int seconds,string value 字串獨有的方式 注意 過期key的判定 檢查給定key是否存在於過期字典,如果存在,那麼取得key的過期時間。檢查當前unix時間戳是否大於key的過期時間...

Redis 過期策略

redis 缺省會每秒進行十次過期掃瞄,過期掃瞄不會遍歷過期字典中所有的 key,而是採用了一種簡單的貪心策略。從過期字典中隨機 20 個 key 刪除這 20 個 key 中已經過期的 key 如果過期的 key 比率超過 1 4,那就重複步驟 1 同時,為了保證過期掃瞄不會出現迴圈過度,導致執行...