作者 | KL博主
背景
摸清 Redis 的數(shù)據(jù)清理策略,給內(nèi)存使用高的被動緩存場景,在遇到內(nèi)存不足時,怎么做是最優(yōu)解提供決策依據(jù)。?
本文整理 Redis 的數(shù)據(jù)清理策略所有代碼來自 Redis version :5.0, 不同版本的 Redis 策略可能有調(diào)整
清理策略
Redis 的清理策略,總結(jié)概括為三點,被動清理、定時清理、驅(qū)逐清理
被動清理
訪問 Key 時,每次都會檢查該 Key 是否已過期,如果過期則刪除該 Key ,get 、scan 等指令都會觸發(fā) Key 的過期檢查。
關(guān)鍵代碼如下, expireIfNeeded (redisDb *db, robj *key) 函數(shù)會觸發(fā)檢查并刪除
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) { robj *val; if (expireIfNeeded(db,key) == 1) { /* Key expired. If we are in the context of a master, expireIfNeeded() * returns 0 only when the key does not exist at all, so it's safe * to return NULL ASAP. */ if (server.masterhost == NULL) { server.stat_keyspace_misses++; return NULL; } if (server.current_client && server.current_client != server.master && server.current_client->cmd && server.current_client->cmd->flags & CMD_READONLY) { server.stat_keyspace_misses++; return NULL; } } val = lookupKey(db,key,flags); if (val == NULL) server.stat_keyspace_misses++; else server.stat_keyspace_hits++; return val; }
定時清理
通過 serverCron 定期觸發(fā)清理,可以通過 hz 參數(shù),配置每秒執(zhí)行多少次清理任務(wù),流程如下
1、Redis 配置項 hz 定義了 serverCron 任務(wù)的執(zhí)行周期,默認(rèn)為 10,即 CPU 空閑時每秒執(zhí)行 10 次
2、每次過期 Key 清理的 timelimit 不超過 CPU 時間的 25% ,即若 hz = 1,則一次清理時間最大為 250ms,若 hz = 10,則一次清理時間最大為 25ms,計算邏輯(timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;)
3、清理時依次遍歷所有的 db;
4、從 db 中隨機取 20 個 key,判斷是否過期,若過期,則逐出;
5、若有 5 個以上 key 過期,則重復(fù)步驟 4,否則遍歷下一個 db;
6、在清理過程中,若達(dá)到了 timelimit 時間,退出清理過程;
關(guān)鍵代碼如下,activeExpireCycle (int type) 會執(zhí)行上述邏輯
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ... databasesCron(); ... } void databasesCron(void) { /* Expire keys by random sampling. Not required for slaves * as master will synthesize DELs for us. */ if (server.active_expire_enabled) { if (server.masterhost == NULL) { activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW); } else { expireSlaveKeys(); } } ... }ps: activeExpireCycle 還會在主事件循環(huán) eventLoop 中被調(diào)用,此時 type = ACTIVE_EXPIRE_CYCLE_FAST, 控制了最多執(zhí)行 timelimit = 1000us 的快速清理,也會刪除部分 Key 。?
驅(qū)逐清理
Redis 在命令處理函數(shù) processCommand 會進(jìn)行內(nèi)存的檢查和驅(qū)逐,任何命令都會出觸發(fā),包括 ping 命令。
如果配置了 maxmemory ,且當(dāng)前內(nèi)存超過 maxmemory 時,則會執(zhí)行 maxmemory_policy 篩選出需要清理的 Key,繼而判斷 lazyfree-lazy-eviction 是否開啟來進(jìn)行 Key 的同步還是異步刪除。無論是同步刪除還是異步刪除,最后都會繼續(xù)校驗內(nèi)存是否超限,直到內(nèi)存低于 maxmemory。驅(qū)逐只會在 Master 節(jié)點進(jìn)行。
maxmemory_policy 可選如下:
volatile-lru:從已設(shè)置過期時間的數(shù)據(jù)集中挑選【最近最少使用】的 Key 進(jìn)行刪除
volatile-ttl:從己設(shè)置過期時間的數(shù)據(jù)集中挑選【將要過期】的 Key 進(jìn)行刪除
volatile-lfu:從己設(shè)置過期時間的數(shù)據(jù)集中選擇【最不常用】的 Key 進(jìn)行刪除
volatile-random:從己設(shè)置過期時間的數(shù)據(jù)集中【任意選擇】Key 進(jìn)行刪除
allkeys-lru:從數(shù)據(jù)集中挑選【最近最少使用】的 Key 進(jìn)行刪除
allkeys-lfu:從數(shù)據(jù)集中【優(yōu)先刪除掉最不常用】的 Key
allkeys-random:從數(shù)據(jù)集中【任意選擇】 Key 進(jìn)行刪除
no-enviction:禁止驅(qū)逐數(shù)據(jù)
如上圖,6.2 后的版本支持通過逐出因子 maxmemory-eviction-tenacity 來控制逐出阻塞的時間。具體的阻塞耗時間可以通過 latency-monitor 里的?eviction-cycle、eviction-del?來觀測。 關(guān)鍵代碼如下,freeMemoryIfNeeded () 函數(shù)會執(zhí)行上述邏輯
int processCommand(client *c) { ... if (server.maxmemory && !server.lua_timedout) { int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR; ... } ... } int freeMemoryIfNeededAndSafe(void) { if (server.lua_timedout || server.loading) return C_OK; return freeMemoryIfNeeded(); }
ps: 當(dāng)觸發(fā) aof 文件重寫,fork 操作會阻塞主進(jìn)程,此時積壓的指令需要的內(nèi)存,在 fork 結(jié)束后,需要一次性 eviction 出來,這時的 eviction-cycle 耗時會惡化的很嚴(yán)重,達(dá)到秒級的阻塞,此時可通過 latency-monitor 觀測 eviction-cycle 、fork 總是成對出現(xiàn)。
總結(jié)
回到開篇的背景問題,當(dāng)遇到內(nèi)存使用高的被動緩存場景,可用內(nèi)存不足時:
離線分析內(nèi)存,是否存在大量【已過期】的內(nèi)存來不及定時清理,此時可調(diào)大 hz 參數(shù)來加速過期內(nèi)存的主動清理。hz 參數(shù)最大 500 ,不過要觀察 CPU 的影響,不要因為 hz 影響讀寫流量
如果調(diào)整 hz 還是沒法及時清理已過期的內(nèi)存,則可以使用 scan 指令來被動訪問 key 的方式手動刪除,注意執(zhí)行 scan 時的 count ,同時觀測 CPU 使用情況,scan 的 count 越大,CPU 消耗會越高,完成一次 sacn 刪除的時間最快。為了減少對線上的影響,可以在業(yè)務(wù)低峰期,周期性的執(zhí)行。
通過 latency-monitor 觀測 eviction-cycle、eviction-del 指標(biāo),是否因內(nèi)存驅(qū)逐阻塞嚴(yán)重,可開啟 lazyfree-lazy-eviction 來緩解阻塞。
業(yè)務(wù)上可以考慮關(guān)閉 aof 的影響,關(guān)閉 aof 可以減少驅(qū)逐清理 eviction-cycle 延遲帶來的讀寫超時影響。
可升級到 7.x 版本的 Redis ,通過 maxmemory-eviction-tenacity 參數(shù)主動控制每次驅(qū)逐的阻塞時間
如果還是很慢,可考慮升級內(nèi)存規(guī)格
編輯:黃飛
?
評論
查看更多