Redis過期策略以及Redis的內存淘汰機制

此篇介紹了Redis過期策略以及Redis的內存淘汰機制,從內存淘汰的8種策略,如何開啓內存淘汰策略如何選擇合適的淘汰策略,對Redis的內存淘汰機制做了全方位的闡述

 

如何高效的使用內存對於redis來說是非常關鍵的,因爲redis的操作都是基於內存的,而每臺機器的內存大小都有限制,且全沒有磁盤空間那麼大

一、Redis過期策略

爲了不數據佔滿內存,這時候我們就會想的將一些不需要永久保持的數據設置一個過期時間。

接下來我們看下以下幾個問題:

如何設置key的過期時間?

redis提供了四種命令來設置key的過期時間:
(1) EXPIRE key seconds // 設置多少秒後過期
(2) EXPIREAT key timestamp 設置 key 過期時間的時間戳(unix timestamp) 以秒計
(3) PEXPIRE key milliseconds // 設置多少毫秒後過期
(4) PEXPIREAT key milliseconds-timestamp // 設置 key 過期時間的時間戳(unix timestamp) 以毫秒計

移除redis的過期時間:
PERSIST key // 移除key的過期時間,key將保持永久
查詢剩餘生存時間:
TTL key // 以秒爲單位,返回給定 key 的剩餘生存時間
PTTL key // 以毫秒爲單位返回 key 的剩餘的過期時間

設置完一個key的過期時間後,到了這個過期時間,這個key保存的數據還佔據着內存嗎?
當key過期後,該key保存的數據還是會佔據內存的,因爲每當我們設置一個鍵的過期時間時,Redis會將該鍵帶上過期時間存放到一個過期字典中。當key過期後,如果沒有觸發redis的刪除策略的話,過期後的數據依然會保存在內存中的,這時候即便這個key已經過期,我們還是能夠獲取到這個key的數據。

redis什麼時候去刪除過期的數據?
redis過期刪除策略通常有三種:定時刪除,定期刪除,惰性刪除。

redis使用的是:“定期刪除+惰性刪除”。
(1) 定時刪除

在設置某個key 的過期時間同時,我們創建一個定時器,讓定時器在該過期時間到來時,立即執行對其進行刪除的操作。
優點:定時刪除對內存是最友好的,能夠保存內存的key一旦過期就能立即從內存中刪除。
缺點:對CPU最不友好,在過期鍵比較多的時候,刪除過期鍵會佔用一部分 CPU 時間,對服務器的響應時間和吞吐量造成影響。

(2) 定期刪除


每隔一段時間,我們就對一些key進行檢查,刪除裏面過期的key。Redis默認每隔100ms就隨機抽取部分設置了過期時間的key,檢測這些key是否過期,如果過期了就將其刪除。

這裏有兩點需要注意下:

默認的每隔100ms是在Redis的配置文件redis.conf中有一個屬性"hz",默認爲10,表示1s執行10次定期刪除,即每隔100ms執行一次,可以修改這個配置的值來設置默認的間隔時間。

隨機抽取部分,而不是全部key。因爲如果Redis裏面有大量key都設置了過期時間,全部都去檢測一遍的話CPU負載就會很高,會浪費大量的時間在檢測上面,甚至直接導致redis掛掉。所有隻會抽取一部分而不會全部檢查。
優點:可以通過限制刪除操作執行的時長和頻率來減少刪除操作對 CPU 的影響。另外定期刪除,也能有效釋放過期鍵佔用的內存。
缺點:難以確定刪除操作執行的時長和頻率。如果執行的太頻繁,定期刪除策略變得和定時刪除策略一樣,對CPU不友好。如果執行的太少,那又和惰性刪除一樣了,過期鍵長時間佔用的內存沒有及時釋放的話,當我們再次獲取這個過期的key時,依然會返回這個key的值,就相當於這個過期時間是無效的了。

(3) 惰性刪除

設置該key 過期時間後,我們不去管它,當需要該key時,我們在檢查其是否過期,如果過期,我們就刪掉它,反之返回該key。
優點:對 CPU友好,我們只會在使用該鍵時纔會進行過期檢查,對於很多用不到的key不用浪費時間進行過期檢查。
缺點:對內存不友好,如果一個鍵已經過期,但是一直沒有使用,那麼該鍵就會一直存在內存中,如果數據庫中有很多這種使用不到的過期鍵,這些鍵便永遠不會被刪除,內存永遠不會釋放,從而造成內存泄漏。所以redis還引入了另一種內存淘汰機制。

二、內存淘汰機制

Redis的內存淘汰機制是指在Redis的用於緩存的內存不足時,怎麼處理需要新寫入且需要申請額外空間的數據。

怎麼設置redis內存大小?開啓Redis 的內存淘汰機制

redis.conf 中:

  • 配置 maxmemory <bytes> ,設置Redis的最大內存空間。不設定該參數默認是無限制的,但是通常會設定其爲物理內存的四分之三。
  • 配置maxmemory-policy noeviction,設置淘汰策略,默認爲 noeviction


內存淘汰方式有哪些?

noeviction:當內存不足以容納新寫入數據時,新寫入操作會報錯。(默認選項,一般不會選用)
allkeys-lru:當內存不足以容納新寫入數據時,在整個鍵空間中,移除最近最少使用的key。(這個是最常用的)
allkeys-lfu:當內存不足以容納新寫入數據時,在整個鍵空間中,移除最不經常(最少)使用的key。
allkeys-random:當內存不足以容納新寫入數據時,在整個鍵空間中,隨機移除某個key。
volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的key。
volatile-lfu:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最不經常(最少)使用的key。
volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個key。
volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的key優先移除。

 


三、常用的淘汰算法

FIFO 算法(Fist in first out:先進先出)


FIFO 算法是一種比較容易實現的算法。它的思想:是基於隊列的先進先出原則,最先進入的數據會被最先淘汰掉。這是最簡單、最公平的一種思想。
(1)實現:維護一個FIFO隊列,按照時間順序將各數據(已分配頁面)鏈接起來組成隊列,並將置換指針指向隊列的隊首。再進行置換時,只需把置換指針所指的數據(頁面)順次換出,並把新加入的數據插到隊尾即可。
(2)缺點:這種算法有個很嚴重的缺點,就是會導致缺頁率增加。缺頁率指的是判斷一個頁面置換算法優劣的指標。隨着分配頁面的增加,被置換的內存頁面往往是被頻繁訪問的,因此FIFO算法會使一些頁面頻繁地被替換和重新申請內存,從而導致缺頁率增加。由於缺頁率會隨着分配頁面的增加而增加,使得redis的開銷也逐漸增加,所以這種算法已經不再使用。

LRU算法(Least recently used:最近最少使用)


LRU算法是一種常見的緩存算法,它的思想是:最近最少使用的會被優先淘汰。如果一個數據在最近一段時間沒有被訪問到,那麼可以認爲在將來它被訪問的可能性也很小。因此,當空間滿時,最久沒有訪問的數據最先被淘汰掉。
(1)實現:最簡單的實現方法是用數組+時間戳的方式,不過這樣做效率較低。因此,我們可以用雙向鏈表(LinkedList)+ 哈希表(HashMap)實現(鏈表用來表示位置,哈希表用來存儲和查找),在Java裏有對應的數據結構LinkedHashMap。

(2)缺點:它在需要淘汰時,只是隨機選取有限的key進行對比,排除掉訪問時間最久的元素,也就意味着它不能選擇整個候選元素的最優解,只是局部最優。默認隨機選取的key的數目爲5,在配置文件redis.conf 中由maxmemory_samples屬性的值決定,採樣數量越大越接近於標準LRU算法,但也會帶來性能的消耗。在Redis 3.0以後增加了LRU淘汰池,進一步提高了與標準LRU算法效果的相似度。淘汰池即維護的一個數組,數組大小等於抽樣數量 maxmemory_samples,在每一次淘汰時,新隨機抽取的key和淘汰池中的key進行合併,然後淘汰掉最舊的key,將剩餘較舊的前面5個key放入淘汰池中待下一次循環使用。假如maxmemory_samples=5,隨機抽取5個元素,淘汰池中還有5個元素,相當於變相的maxmemory_samples=10了,所以進一步提高了與LRU算法的相似度。

 

LFU算法(Least frequently used:最不常使用)


LFU算法的思想是:如果一個數據在最近一段時間很少被訪問到,那麼可以認爲在將來它被訪問的可能性也很小。因此,當空間滿時,最小頻率訪問的數據最先被淘汰。
實現:如果只爲每個key維護了一個計數器,每次key被訪問的時候,計數器增大,計數器越大,則認爲訪問越頻繁。

這樣還是遠遠不夠的,還會存在兩個問題:
(1)因爲可能存在在開始一個小時內,某個key1有100萬的訪問量,但是在之後的一個小時內,這個key1的訪問量爲0了,而在這第二個小時內另外有個key2的訪問量達到了20萬,雖然這20萬不如前面那個key1開始那個小時的100萬訪問量大,但是在第二個小時內這key2的訪問量遠大於key1的訪問量,所以在第二個小時內key1依然會優先於key2被淘汰掉。
(2)當新加入的key,由於沒有被訪問過,所以初始的計數器爲0,如果這時候觸發淘汰機制的話,就會把最先添加到key最先淘汰掉。
所以在LFU算法中維護了這個24bit的字段,不過被分成了16 bits與8 bits兩部分。第一部分:高16 bits用來記錄計數器的上次縮減時間,時間戳,單位精確到分鐘。第二部分:低8 bits用來記錄計數器的當前數值,這個數值反映了訪問頻率,而不是次數。

在redis.conf配置文件中還有2個屬性可以調整LFU算法的執行參數:lfu-log-factor、lfu-decay-time。其中lfu-log-factor用來調整計數器counter的增長速度,lfu-log-factor越大,counter增長的越慢。lfu-decay-time是一個以分鐘爲單位的數值,用來調整counter的縮減速度。

 

LRU與LFU的選擇

需要根據業務權衡到底是選擇 淘汰最近最少使用(LRU) 還是選擇 最不經常使用(LFU)

總的來說,無論是 LRU LFU TTL 還是Random 都是幾近算法來實現的,在可靠性和性能上做了一定的平衡。還是應該在業務中主動刪除沒有價值的數據,或者更新某些key的過期時間等來提高Redis的性能和空間,不能過分依賴於淘汰策略。



四、總結


Redis的內存淘汰策略的選取並不會影響過期的key的處理。內存淘汰策略用於處理內存不足時的需要申請額外空間的數據;過期策略用於處理過期的緩存數據

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章