Redis 內存回收策略

內存回收策略


Redis的內存回收機制主要體現在以下兩個方面:
·刪除到達過期時間的鍵對象。
·內存使用達到maxmemory上限時觸發內存溢出控制策略。

1.刪除過期鍵對象
Redis所有的鍵都可以設置過期屬性, 內部保存在過期字典中。 由於進程內保存大量的鍵, 維護每個鍵精準的過期刪除機制會導致消耗大量的CPU, 對於單線程的Redis來說成本過高, 因此Redis採用惰性刪除和定時任務刪除機制實現過期鍵的內存回收。

·惰性刪除: 惰性刪除用於當客戶端讀取帶有超時屬性的鍵時, 如果已經超過鍵設置的過期時間, 會執行刪除操作並返回空, 這種策略是出於節省CPU成本考慮, 不需要單獨維護TTL鏈表來處理過期鍵的刪除。 但是單獨用這種方式存在內存泄露的問題, 當過期鍵一直沒有訪問將無法得到及時刪除, 從而導致內存不能及時釋放。 正因爲如此, Redis還提供另一種定時任務刪除機制作爲惰性刪除的補充。

·定時任務刪除: Redis內部維護一個定時任務, 默認每秒運行10次(通過配置hz控制) 。 定時任務中刪除過期鍵邏輯採用了自適應算法, 根據鍵的過期比例、 使用快慢兩種速率模式回收鍵, 流程如圖所示:

流程說明:
1) 定時任務在每個數據庫空間隨機檢查20個鍵, 當發現過期時刪除對應的鍵。

2) 如果超過檢查數25%的鍵過期, 循環執行回收邏輯直到不足25%或運行超時爲止, 慢模式下超時時間爲25毫秒。
3) 如果之前回收鍵邏輯超時, 則在Redis觸發內部事件之前再次以快模式運行回收過期鍵任務, 快模式下超時時間爲1毫秒且2秒內只能運行1次。
4) 快慢兩種模式內部刪除邏輯相同, 只是執行的超時時間不同。
 

 內存溢出控制策略


當Redis所用內存達到maxmemory上限時會觸發相應的溢出控制策略。具體策略受maxmemory-policy參數控制, Redis支持6種策略, 如下所示:
1) noeviction: 默認策略, 不會刪除任何數據, 拒絕所有寫入操作並返回客戶端錯誤信息( error) OOM command not allowed when used memory, 此時Redis只響應讀操作。
2) volatile-lru: 根據LRU算法刪除設置了超時屬性( expire) 的鍵, 直到騰出足夠空間爲止。 如果沒有可刪除的鍵對象, 回退到noeviction策略。
3) allkeys-lru: 根據LRU算法刪除鍵, 不管數據有沒有設置超時屬性,直到騰出足夠空間爲止。

4) allkeys-random: 隨機刪除所有鍵, 直到騰出足夠空間爲止。
5) volatile-random: 隨機刪除過期鍵, 直到騰出足夠空間爲止。
6) volatile-ttl: 根據鍵值對象的ttl屬性, 刪除最近將要過期數據。 如果沒有, 回退到noeviction策略。

內存溢出控制策略可以採用config set maxmemory-policy{policy}動態配置。 Redis支持豐富的內存溢出應對策略, 可以根據實際需求靈活定製, 比如當設置volatile-lru策略時, 保證具有過期屬性的鍵可以根據LRU剔除, 而未設置超時的鍵可以永久保留。 還可以採用allkeys-lru策略把Redis變爲純緩存服務器使用。 當Redis因爲內存溢出刪除鍵時, 可以通過執行info stats命令查看evicted_keys指標找出當前Redis服務器已剔除的鍵數量。每次Redis執行命令時如果設置了maxmemory參數, 都會嘗試執行回收內存操作。 當Redis一直工作在內存溢出(used_memory>maxmemory) 的狀態下且設置非noeviction策略時, 會頻繁地觸發回收內存的操作, 影響Redis服務器的性能。 回收內存邏輯僞代碼如下:
 

def freeMemoryIfNeeded() :
int mem_used, mem_tofree, mem_freed;
// 計算當前內存總量, 排除從節點輸出緩衝區和AOF緩衝區的內存佔用
int slaves = server.slaves;
mem_used = used_memory()-slave_output_buffer_size(slaves)-aof_rewrite_buffer_
size();
// 如果當前使用小於等於maxmemory退出
if (mem_used <= server.maxmemory) :
return REDIS_OK;
// 如果設置內存溢出策略爲noeviction(不淘汰) , 返回錯誤。
if (server.maxmemory_policy == 'noeviction') :
return REDIS_ERR;
// 計算需要釋放多少內存
mem_tofree = mem_used - server.maxmemory;
// 初始化已釋放內存量
mem_freed = 0;
// 根據maxmemory-policy策略循環刪除鍵釋放內存
while (mem_freed < mem_tofree) :
// 迭代Redis所有數據庫空間
for (int j = 0; j < server.dbnum; j++) :
String bestkey = null;
dict dict;
if (server.maxmemory_policy == 'allkeys-lru' ||
server.maxmemory_policy == 'allkeys-random'):
// 如果策略是 allkeys-lru/allkeys-random
// 回收內存目標爲所有的數據庫鍵
dict = server.db[j].dict;
else :
// 如果策略是volatile-lru/volatile-random/volatile-ttl
// 回收內存目標爲帶過期時間的數據庫鍵
dict = server.db[j].expires;
// 如果使用的是隨機策略, 那麼從目標字典中隨機選出鍵
if (server.maxmemory_policy == 'allkeys-random' ||
server.maxmemory_policy == 'volatile-random') :
// 隨機返回被刪除鍵
bestkey = get_random_key(dict);
else if (server.maxmemory_policy == 'allkeys-lru' ||
server.maxmemory_policy == 'volatile-lru') :
// 循環隨機採樣maxmemory_samples次(默認5次), 返回相對空閒時間最長的鍵
bestkey = get_lru_key(dict);
else if (server.maxmemory_policy == 'volatile-ttl') :
// 循環隨機採樣maxmemory_samples次, 返回最近將要過期的鍵
bestkey = get_ttl_key(dict);
// 刪除被選中的鍵
if (bestkey != null) :
long delta = used_memory();
deleteKey(bestkey);
// 計算刪除鍵所釋放的內存量
delta -= used_memory();
mem_freed += delta;
// 刪除操作同步給從節點
if (slaves):
flushSlavesOutputBuffers();
return REDIS_OK;

從僞代碼可以看到, 頻繁執行回收內存成本很高, 主要包括查找可回收鍵和刪除鍵的開銷, 如果當前Redis有從節點, 回收內存操作對應的刪除命令會同步到從節點, 導致寫放大的問題, 如圖所示:

運維提示
建議線上Redis內存工作在maxmemory>used_memory狀態下, 避免頻繁內存回收開銷。對於需要收縮Redis內存的場景, 可以通過調小maxmemory來實現快速回收。 比如對一個實際佔用6GB內存的進程設置maxmemory=4GB, 之後第一次執行命令時, 如果使用非noeviction策略, 它會一次性回收到maxmemory指定的內存量, 從而達到快速回收內存的目的。 注意, 此操作會導致數據丟失和短暫的阻塞問題, 一般在緩存場景下使用。

 

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