內存耗盡後Redis會發生什麼


點擊上方藍字,關注我們

前言


作爲一臺服務器來說,內存並不是無限的,所以總會存在內存耗盡的情況,那麼當  Redis  服務器的內存耗盡後,如果繼續執行請求命令, Redis  會如何處理呢?

內存回收

使用 Redis  服務時,很多情況下某些鍵值對只會在特定的時間內有效,爲了防止這種類型的數據一直佔有內存,我們可以給鍵值對設置有效期。 Redis  中可以通過  4  個獨立的命令來給一個鍵設置過期時間:
  • expire key ttl :將  key  值的過期時間設置爲  ttl  
  • pexpire key ttl :將  key  值的過期時間設置爲  ttl  毫秒
  • expireat key timestamp :將  key  值的過期時間設置爲指定的  timestamp  秒數
  • pexpireat key timestamp :將  key  值的過期時間設置爲指定的  timestamp  毫秒數
PS:不管使用哪一個命令,最終  Redis  底層都是使用  pexpireat  命令來實現的。另外, set  等命令也可以設置  key  的同時加上過期時間,這樣可以保證設值和設過期時間的原子性。
設置了有效期後,可以通過  ttl  和  pttl  兩個命令來查詢剩餘過期時間(如果未設置過期時間則下面兩個命令返回  -1 ,如果設置了一個非法的過期時間,則都返回  -2 ):
  • ttl key  返回  key  剩餘過期秒數。
  • pttl key  返回  key  剩餘過期的毫秒數。


過期策略


如果將一個過期的鍵刪除,我們一般都會有三種策略:
  • 定時刪除:爲每個鍵設置一個定時器,一旦過期時間到了,則將鍵刪除。這種策略對內存很友好,但是對  CPU  不友好,因爲每個定時器都會佔用一定的  CPU  資源。
  • 惰性刪除:不管鍵有沒有過期都不主動刪除,等到每次去獲取鍵時再判斷是否過期,如果過期就刪除該鍵,否則返回鍵對應的值。這種策略對內存不夠友好,可能會浪費很多內存。
  • 定期掃描:系統每隔一段時間就定期掃描一次,發現過期的鍵就進行刪除。這種策略相對來說是上面兩種策略的折中方案,需要注意的是這個定期的頻率要結合實際情況掌控好,使用這種方案有一個缺陷就是可能會出現已經過期的鍵也被返回。
在  Redis  當中,其選擇的是策略  2  和策略  3  的綜合使用。不過  Redis  的定期掃描只會掃描設置了過期時間的鍵,因爲設置了過期時間的鍵  Redis  會單獨存儲,所以不會出現掃描所有鍵的情況:
  
  
  
typedef struct redisDb {
dict *dict; //所有的鍵值對
dict *expires; //設置了過期時間的鍵值對
dict *blocking_keys; //被阻塞的key,如客戶端執行BLPOP等阻塞指令時
dict *watched_keys; //WATCHED keys
int id; //Database ID
//... 省略了其他屬性
} redisDb;


8 種淘汰策略


假如  Redis  當中所有的鍵都沒有過期,而且此時內存滿了,那麼客戶端繼續執行  set  等命令時  Redis  會怎麼處理呢? Redis  當中提供了不同的淘汰策略來處理這種場景。
首先  Redis  提供了一個參數  maxmemory  來配置  Redis  最大使用內存:
  
  
  
maxmemory <bytes>
或者也可以通過命令  config set maxmemory 1GB  來動態修改。
如果沒有設置該參數,那麼在  32  位的操作系統中  Redis  最多使用  3GB  內存,而在  64  位的操作系統中則不作限制。
Redis  中提供了  8  種淘汰策略,可以通過參數  maxmemory-policy  進行配置:
淘汰策略
說明


volatile-lru
根據 LRU 算法刪除設置了過期時間的鍵,直到騰出可用空間。如果沒有可刪除的鍵對象,且內存還是不夠用時,則報錯
allkeys-lru
根據 LRU 算法刪除所有的鍵,直到騰出可用空間。如果沒有可刪除的鍵對象,且內存還是不夠用時,則報錯
volatile-lfu
根據 LFU 算法刪除設置了過期時間的鍵,直到騰出可用空間。如果沒有可刪除的鍵對象,且內存還是不夠用時,則報錯
allkeys-lfu
根據 LFU 算法刪除所有的鍵,直到騰出可用空間。如果沒有可刪除的鍵對象,且內存還是不夠用時,則報錯
volatile-random
隨機刪除設置了過期時間的鍵,直到騰出可用空間。如果沒有可刪除的鍵對象,且內存還是不夠用時,則報錯
allkeys-random
隨機刪除所有鍵,直到騰出可用空間。如果沒有可刪除的鍵對象,且內存還是不夠用時,則報錯
volatile-ttl
根據鍵值對象的 ttl 屬性, 刪除最近將要過期數據。如果沒有,則直接報錯
noeviction
默認策略,不作任何處理,直接報錯
PS:淘汰策略也可以直接使用命令  config set maxmemory-policy <策略>  來進行動態配置。


LRU 算法


LRU  全稱爲: Least Recently Used 。即:最近最長時間未被使用。這個主要針對的是使用時間。

Redis 改進後的 LRU 算法

在  Redis  當中,並沒有採用傳統的  LRU  算法,因爲傳統的  LRU  算法存在  2  個問題:
  • 需要額外的空間進行存儲。
  • 可能存在某些  key  值使用很頻繁,但是最近沒被使用,從而被  LRU  算法刪除。
爲了避免以上  2  個問題, Redis  當中對傳統的  LRU  算法進行了改造,通過抽樣的方式進行刪除
配置文件中提供了一個屬性  maxmemory_samples 5 ,默認值就是  5 ,表示隨機抽取  5  個  key  值,然後對這  5  個  key  值按照  LRU  算法進行刪除,所以很明顯, key  值越大,刪除的準確度越高。
對抽樣  LRU  算法和傳統的  LRU  算法, Redis  官網當中有一個對比圖:
  • 淺灰色帶是被刪除的對象。
  • 灰色帶是未被刪除的對象。
  • 綠色是添加的對象。
左上角第一幅圖代表的是傳統  LRU  算法,可以看到,當抽樣數達到  10  個(右上角),已經和傳統的  LRU  算法非常接近了。

Redis 如何管理熱度數據

前面我們講述字符串對象時,提到了  redisObject  對象中存在一個  lru  屬性:
   
   
   
typedef struct redisObject { unsigned type:4;//對象類型(4位=0.5字節) unsigned encoding:4;//編碼(4位=0.5字節) unsigned lru:LRU_BITS;//記錄對象最後一次被應用程序訪問的時間(24位=3字節) int refcount;//引用計數。等於0時表示可以被垃圾回收(32位=4字節) void *ptr;//指向底層實際的數據存儲結構,如:SDS等(8字節) } robj;
lru  屬性是創建對象的時候寫入,對象被訪問到時也會進行更新。正常人的思路就是最後決定要不要刪除某一個鍵肯定是用當前時間戳減去  lru ,差值最大的就優先被刪除。但是  Redis  裏面並不是這麼做的, Redis  中維護了一個全局屬性  lru_clock ,這個屬性是通過一個全局函數  serverCron  每隔  100  毫秒執行一次來更新的,記錄的是當前  unix  時間戳。
最後決定刪除的數據是通過  lru_clock  減去對象的  lru  屬性而得出的。那麼爲什麼  Redis  要這麼做呢?直接取全局時間不是更準確嗎?
這是因爲這麼做可以避免每次更新對象的  lru  屬性的時候可以直接取全局屬性,而不需要去調用系統函數來獲取系統時間,從而提升效率( Redis  當中有很多這種細節考慮來提升性能,可以說是對性能儘可能的優化到極致)。
不過這裏還有一個問題,我們看到, redisObject  對象中的  lru  屬性只有  24  位, 24  位只能存儲  194  天的時間戳大小,一旦超過  194  天之後就會重新從  0  開始計算,所以這時候就可能會出現  redisObject  對象中的  lru  屬性大於全局的  lru_clock  屬性的情況。
正因爲如此,所以計算的時候也需要分爲  2  種情況:
  • 當全局  lruclock  >  lru ,則使用  lruclock  -  lru  得到空閒時間。
  • 當全局  lruclock  <  lru ,則使用  lruclock_max (即  194  天) -  lru  +  lruclock  得到空閒時間。
需要注意的是,這種計算方式並不能保證抽樣的數據中一定能刪除空閒時間最長的。這是因爲首先超過  194  天還不被使用的情況很少,再次只有  lruclock  第  2  輪繼續超過  lru  屬性時,計算纔會出問題。
比如對象  A  記錄的  lru  是  1  天,而  lruclock  第二輪都到  10  天了,這時候就會導致計算結果只有  10-1=9  天,實際上應該是  194+10-1=203  天。但是這種情況可以說又是更少發生,所以說這種處理方式是可能存在刪除不準確的情況,但是本身這種算法就是一種近似的算法,所以並不會有太大影響。


LFU 算法


LFU  全稱爲: Least Frequently Used 。即:最近最少頻率使用,這個主要針對的是使用頻率。這個屬性也是記錄在 redisObject  中的  lru  屬性內。
當我們採用  LFU  回收策略時, lru  屬性的高  16  位用來記錄訪問時間(last decrement time:ldt,單位爲分鐘),低  8  位用來記錄訪問頻率(logistic counter:logc),簡稱  counter

訪問頻次遞增

LFU  計數器每個鍵只有  8  位,它能表示的最大值是  255 ,所以  Redis  使用的是一種基於概率的對數器來實現  counter  的遞增。r
給定一箇舊的訪問頻次,當一個鍵被訪問時, counter  按以下方式遞增:
  1. 提取  0  和  1  之間的隨機數  R
  2. counter  - 初始值(默認爲  5 ),得到一個基礎差值,如果這個差值小於  0 ,則直接取  0 ,爲了方便計算,把這個差值記爲  baseval
  3. 概率  P  計算公式爲: 1/(baseval * lfu_log_factor + 1)
  4. 如果  R < P  時,頻次進行遞增( counter++ )。
公式中的  lfu_log_factor  稱之爲對數因子,默認是  10  ,可以通過參數來進行控制:
  
  
  
lfu_log_factor 10
下圖就是對數因子  lfu_log_factor  和頻次  counter  增長的關係圖:
可以看到,當對數因子  lfu_log_factor  爲  100  時,大概是  10M(1000萬)  次訪問纔會將訪問  counter  增長到  255 ,而默認的  10  也能支持到  1M(100萬)  次訪問  counter  才能達到  255  上限,這在大部分場景都是足夠滿足需求的。

訪問頻次遞減

如果訪問頻次  counter  只是一直在遞增,那麼遲早會全部都到  255 ,也就是說  counter  一直遞增不能完全反應一個  key  的熱度的,所以當某一個  key  一段時間不被訪問之後, counter  也需要對應減少。
counter  的減少速度由參數  lfu-decay-time  進行控制,默認是  1 ,單位是分鐘。默認值  1  表示: N  分鐘內沒有訪問, counter  就要減  N
  
  
  
lfu-decay-time 1
具體算法如下:
  1. 獲取當前時間戳,轉化爲分鐘後取低  16  位(爲了方便後續計算,這個值記爲  now )。
  2. 取出對象內的  lru  屬性中的高  16  位(爲了方便後續計算,這個值記爲  ldt )。
  3. 當  lru  >  now  時,默認爲過了一個週期( 16  位,最大  65535 ),則取差值  65535-ldt+now :當  lru  <=  now  時,取差值  now-ldt (爲了方便後續計算,這個差值記爲  idle_time )。
  4. 取出配置文件中的  lfu_decay_time  值,然後計算: idle_time / lfu_decay_time (爲了方便後續計算,這個值記爲 num_periods )。
  5. 最後將 counter 減少: counter - num_periods
看起來這麼複雜,其實計算公式就是一句話:取出當前的時間戳和對象中的  lru  屬性進行對比,計算出當前多久沒有被訪問到,比如計算得到的結果是  100  分鐘沒有被訪問,然後再去除配置參數  lfu_decay_time ,如果這個配置默認爲  1 也即是  100/1=100 ,代表  100  分鐘沒訪問,所以  counter  就減少  100

總結

本文主要介紹了  Redis  過期鍵的處理策略,以及當服務器內存不夠時  Redis  的  8  種淘汰策略,最後介紹了  Redis  中的兩種主要的淘汰算法  LRU  和  LFU
回覆乾貨獲取精選乾貨視頻教程
回覆加羣加入疑難問題攻堅交流羣
回覆mat獲取內存溢出問題分析詳細文檔教程
回覆賺錢獲取用java寫一個能賺錢的微信機器人
回覆副業獲取程序員副業攻略一份

都收藏了,就點個「在看」支持一下吧!


點下在看,你最好看


本文分享自微信公衆號 - 俠夢的開發筆記(xmdevnote)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

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