作為一台伺服器來說,記憶體並不是無限的,所以總會存在記憶體耗盡的情況,那麼當 redis 伺服器的記憶體耗盡後,如果繼續執行請求命令,redis 會如何處理呢?
ps:不管使用哪乙個命令,最終 redis 底層都是使用 pexpireat 命令來實現的。另外,set 等命令也可以設定 key 的同時加上過期時間,這樣可以保證設值和設過期時間的原子性。設定了有效期後,可以通過 ttl 和 pttl 兩個命令來查詢剩餘過期時間(如果未設定過期時間則下面兩個命令返回 -1,如果設定了乙個非法的過期時間,則都返回 -2):
在 redis 當中,其選擇的是策略 2 和策略 3 的綜合使用。不過 redis 的定期掃瞄只會掃瞄設定了過期時間的鍵,因為設定了過期時間的鍵 redis 會單獨儲存,所以不會出現掃瞄所有鍵的情況:
typedef struct假如 redis 當中所有的鍵都沒有過期,而且此時記憶體滿了,那麼客戶端繼續執行 set 等命令時 redis 會怎麼處理呢?redis 當中提供了不同的淘汰策略來處理這種場景。redisdb redisdb;
maxmemory或者也可以通過命令
config set maxmemory 1gb
來動態修改。
如果沒有設定該引數,那麼在 32 位的作業系統中 redis 最多使用 3gb 記憶體,而在 64 位的作業系統中則不作限制。
ps:淘汰策略也可以直接使用命令config set maxmemory-policy 《策略》
來進行動態配置。
為了避免以上 2 個問題,redis 當中對傳統的 lru 演算法進行了改造,通過抽樣的方式進行刪除。
配置檔案中提供了乙個屬性maxmemory_samples 5
,預設值就是 5,表示隨機抽取 5 個 key 值,然後對這 5 個 key 值按照 lru 演算法進行刪除,所以很明顯,key 值越大,刪除的準確度越高。
左上角第一幅圖代表的是傳統 lru 演算法,可以看到,當抽樣數達到 10 個(右上角),已經和傳統的 lru 演算法非常接近了。
typedef structlru 屬性是建立物件的時候寫入,物件被訪問到時也會進行更新。正常人的思路就是最後決定要不要刪除某乙個鍵肯定是用當前時間戳減去 lru,差值最大的就優先被刪除。redisobject robj;
但是 redis 裡面並不是這麼做的,redis 中維護了乙個全域性屬性 lru_clock,這個屬性是通過乙個全域性函式 servercron 每隔 100 毫秒執行一次來更新的,記錄的是當前 unix 時間戳。
最後決定刪除的資料是通過 lru_clock 減去物件的 lru 屬性而得出的。那麼為什麼 redis 要這麼做呢?直接取全域性時間不是更準確嗎?
這是因為這麼做可以避免每次更新物件的 lru 屬性的時候可以直接取全域性屬性,而不需要去呼叫系統函式來獲取系統時間,從而提公升效率(redis 當中有很多這種細節考慮來提公升效能,可以說是對效能盡可能的優化到極致)。
不過這裡還有乙個問題,我們看到,redisobject 物件中的 lru 屬性只有 24 位,24 位只能儲存 194 天的時間戳大小,一旦超過 194 天之後就會重新從 0 開始計算,所以這時候就可能會出現 redisobject 物件中的 lru 屬性大於全域性的 lru_clock 屬性的情況。
需要注意的是,這種計算方式並不能保證抽樣的資料中一定能刪除空閒時間最長的。這是因為首先超過 194 天還不被使用的情況很少,再次只有 lruclock 第 2 輪繼續超過 lru 屬性時,計算才會出問題。比如物件 a 記錄的 lru 是 1 天,而 lruclock 第二輪都到 10 天了,這時候就會導致計算結果只有
10-1=9
天,實際上應該是194+10-1=203
天。
但是這種情況可以說又是更少發生,所以說這種處理方式是可能存在刪除不準確的情況,但是本身這種演算法就是一種近似的演算法,所以並不會有太大影響。
lfu 全稱為:least frequently used。即:最近最少頻率使用,這個主要針對的是使用頻率。這個屬性也是記錄在redisobject 中的 lru 屬性內。
當我們採用 lfu **策略時,lru 屬性的高 16 位用來記錄訪問時間(last decrement time:ldt,單位為分鐘),低 8 位用來記錄訪問頻率(logistic counter:logc),簡稱 counter。
lfu 計數器每個鍵只有 8 位,它能表示的最大值是 255,所以 redis 使用的是一種基於概率的對數器來實現 counter 的遞增。r
可以看到,當對數因子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看起來這麼複雜,其實計算公式就是一句話:取出當前的時間戳和物件中的 lru 屬性進行對比,計算出當前多久沒有被訪問到,比如計算得到的結果是 100 分鐘沒有被訪問,然後再去除配置引數
lfu_decay_time
,如果這個配置預設為 1也即是100/1=100
,代表 100 分鐘沒訪問,所以 counter 就減少 100。
本文主要介紹了 redis 過期鍵的處理策略,以及當伺服器記憶體不夠時 redis 的 8 種淘汰策略,最後介紹了 redis 中的兩種主要的淘汰演算法 lru 和 lfu。
鍵入url會發生什麼
現在還不知道該發往哪,所以需要dns協議將網域名稱解析為對應的ip位址 填充tcp協議頭,tcp裡面有首部長度,源埠號,目的埠號,還有標誌位,比如syn,ack等,序列號,確認序列號,視窗大小等一些資訊,但是tcp在傳輸之前需要先建立連線,需要進行三次握手 填充ip協議頭,包括版本號,首部長度,源i...
隧道而言, TCP In TCP會發生什麼
上週晚上在家中,當我搭起熟悉的ss梯子時,發現不可用了t.t。登陸到控制台檢視,發現國內的ip被block了。昨天瞎逛,看到乙個開源專案 udp2raw tunnel,他實現的是將乙個ip報文偽裝成tcp報文,目的是穿過網路中udp防火牆.哈?這難道是tcp in tcp?這玩意兒不是不可用嗎?很早...
使用new時,會發生什麼?
使用new來呼叫函式,或者說發生構造函式呼叫時,會自動執行下面的操作 建立 或說構造 乙個全新的物件。這個新物件會被執行 prototype 連線。這個新物件會繫結到函式呼叫的this。如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件。如下 function fn var ...