Redis 設定過期時間
Redis 有四個不同的命令可以用于設定鍵的生存時間(鍵可以存在多久)或過期時間(鍵什么時候會被洗掉):- EXPIRE <key> <ttl> ——將鍵 key 的生存時間設定為 ttl 秒,
- PEXPIRE <key> <ttl>——將鍵 key 的生存時間設定為 ttl 毫秒,
- EXPIREAT <key> <timestamp>——將鍵 key 的過期時間設定為 timestamp 所指定的秒數時間戳,
- PEXPIREAT <key> <timestamp>——將鍵 key 的過期時間設定為 timestamp 所指定的毫秒數時間戳,
Redis 計算并回傳剩余時間
Redis 提供了兩個命令,其中 TTL 命令以秒為單位回傳鍵的剩余生存時間;PTTL 命令則以毫秒為單位回傳鍵的剩余生存時間,TTL 和 PTTL 兩個命令都是通過計算鍵的過期時間和當前時間之間的差來實作的,Redis 過期字典
redisDb 結構的 expires 字典保存了資料庫中所有鍵的過期時間,我們稱這個字典為過期字典:- 過期字典的鍵是一個指標,這個指標指向鍵空間的某個鍵物件(也即是某個資料庫鍵),
- 過期字典的值是一個 long long 型別的整數,這個整數保存了鍵所指向的資料庫鍵的過期時間——一個毫秒精度的 UNIX 時間戳,
Redis 過期鍵的判定
通過過期字典,程式可以用以下步驟檢查一個給定鍵是否過期:- 檢查給定鍵是否存在于過期字典,如果存在,那么取得鍵的過期時間,
- 檢查當前 UNIX 時間戳是否大于鍵的過期時間,如果是的話,那么鍵已經過期,否則的話,鍵未過期,
三種過期鍵洗掉策略
定時洗掉
在設定鍵的過期時間的同時,創建一個定時器(timer),讓定時器在鍵的過期時間來臨時,立即執行對鍵的洗掉操作,優點:
- 對記憶體是最友好的,通過使用定時器,定時洗掉策略可以保證過期鍵會盡可能快地被洗掉,并釋放過期鍵所占用地記憶體,
缺點:
- 對 CPU 時間是最不友好的,在過期鍵比較多的情況下,洗掉過期鍵這一行為可能會占用相當一部分 CPU 時間,在記憶體不緊張但是 CPU 時間非常緊張的情況下,將 CPU 時間用在洗掉和當前任務無關的過期鍵上,無疑會對服務器的回應時間和吞吐量造成影響,
- 創建一個定時器需要用到 Redis 服務器中的時間事件,而當前時間事件的實作方式是無序鏈表,查找一個事件的時間復雜度為 O(N),這并不能高效地處理大量時間事件,
惰性洗掉
放任鍵過期不管,但是每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過期,如果過期的話,就洗掉該鍵;如果沒有過期,就回傳該鍵,優點:
- 對 CPU 時間來說是最友好的,程式只會在取出鍵時才對鍵進行過期檢查,這可以保證洗掉過期鍵的操作只會在非做不可的情況下進行,并且洗掉的目標僅限于當前處理的鍵,這個策略不會在洗掉其他無關的過期鍵上花費任何 CPU 時間,
缺點:
- 對記憶體是最不友好的,如果一個鍵已經過期,而這個鍵又仍然保留在資料庫中,那么只要這個過期鍵不被洗掉,它所占用的記憶體就不會釋放,如果資料庫中有非常多的過期鍵,而這些過期鍵又恰好沒有被訪問到的話,那么它們也許永遠也不會被洗掉(除非用戶手動執行 FLUSHDB),我們甚至可以將這種情況看作是一種記憶體泄露,無用的垃圾資料占用了大量記憶體,而服務器卻不會自己去釋放它們,
定期洗掉
每隔一段時間,程式就對資料庫進行一次檢查,洗掉里面的過期鍵,至于要洗掉多少過期鍵,以及要檢查多少個資料庫,則由演算法決定,優點:
- 之前討論的兩種洗掉策略都有明顯的缺陷,定期洗掉策略是前兩種策略的一種整合和折中,
- 定期洗掉策略每隔一段時間執行一次洗掉過期鍵操作,并通過限制洗掉操作執行的時長和頻率來減少洗掉操作對 CPU 時間的影響,
- 除此以外,通過定期洗掉過期鍵,定期洗掉策略有效地減少了因為過期鍵而帶來的記憶體浪費,
缺點:
- 定期洗掉的難點是確定洗掉操作執行的時長和頻率,
- 如果洗掉操作執行得太頻繁,或者執行的時間太長,定期洗掉策略就會退化成定時洗掉策略,以至于將 CPU 時間過多地消耗在洗掉過期鍵上面,
- 如果洗掉操作執行得太少,或者執行的時間太短,定期洗掉策略又會和惰性洗掉策略一樣,出現浪費記憶體的情況,
Redis 的過期洗掉策略
前面討論了定時洗掉、惰性洗掉和定期洗掉三種過期鍵洗掉策略,Redis 服務器實際使用的是惰性洗掉和定期洗掉兩種策略,通過配合使用這兩種策略,服務器可以很好地在合理使用 CPU 時間和避免浪費記憶體空間之間取得平衡,惰性洗掉策略的實作
過期鍵的惰性洗掉策略由 db.c/expireIfNeeded 函式實作,所有讀寫資料庫的 Redis 命令在執行之前都會呼叫 expireIfNeeded 函式對輸入鍵進行檢查:- 如果輸入鍵已經過期,那么 expireIfNeeded 函式將輸入鍵從資料庫中洗掉,
- 如果輸入鍵未過期,那么 expireIfNeeded 函式不做動作,
定期洗掉策略的實作
過期鍵的定期洗掉策略由 redis.c/activeExpireCycle 函式實作,每當 Redis 的服務器周期性操作 redis.c/serverCron 函式執行時,activeExpireCycle 函式就會呼叫,它在規定的時間內,分多次遍歷服務器中的各個資料庫,從資料庫的 expires 字典中隨機檢查一部分鍵的過期時間,并洗掉其中的過期鍵, Redis 默認每秒進行 10 次過期掃描(Redis 的組態檔里面的 hz 引數配置),過期掃描不會遍歷過期字典中所有的 key,而是采用了一種簡單的貪心策略,步驟如下:- 從過期字典中隨機選出 20 個 key,
- 洗掉這 20 個 key 中已經過期的 key,
- 如果過期的 key 的比例超過 1/4,那就重復步驟(1),
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,
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 串列放入淘汰池中留待下一個回圈,轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/304832.html
標籤:NoSQL
