Redis數據清理詳解
Redis常用的刪除策略有以下三種:
- 被動(dòng)刪除(惰性刪除):當讀/寫(xiě)一個(gè)已經(jīng)過(guò)期的Key時(shí),會(huì )觸發(fā)惰性刪除策略,直接刪除掉這個(gè)Key;
- 主動(dòng)刪除(定期刪除):Redis會(huì )定期巡檢,來(lái)清理過(guò)期Key;
- 當內存達到maxmemory配置時(shí)候,會(huì )觸發(fā)Key的刪除操作;
另外,還有一種基于觸發(fā)器的刪除策略,因為對Redis壓力太大,一般沒(méi)人使用。
主動(dòng)刪除(定期刪除)
在 Redis 中,常規操作由 redis.c/serverCron 實(shí)現,它主要執行以下操作:
- 更新服務(wù)器的各類(lèi)統計信息,比如時(shí)間、內存占用、數據庫占用情況等。
- 清理數據庫中的過(guò)期鍵值對。
- 對不合理的數據庫進(jìn)行大小調整。
- 關(guān)閉和清理連接失效的客戶(hù)端。
- 嘗試進(jìn)行 AOF 或 RDB 持久化操作。
- 如果服務(wù)器是主節點(diǎn)的話(huà),對附屬節點(diǎn)進(jìn)行定期同步。
- 如果處于集群模式的話(huà),對集群進(jìn)行定期同步和連接測試。
Redis 將 serverCron 作為時(shí)間事件來(lái)運行,從而確保它每隔一段時(shí)間就會(huì )自動(dòng)運行一次, 又因為 serverCron 需要在 Redis 服務(wù)器運行期間一直定期運行, 所以它是一個(gè)循環(huán)時(shí)間事件:serverCron 會(huì )一直定期執行,直到服務(wù)器關(guān)閉為止。
在 Redis 2.6 版本中, 程序規定 serverCron 每秒運行 10 次, 平均每 100 毫秒運行一次。 從 Redis 2.8 開(kāi)始, 用戶(hù)可以通過(guò)修改 hz選項來(lái)調整 serverCron 的每秒執行次數, 具體信息請參考 redis.conf 文件中關(guān)于 hz 選項的說(shuō)明。
也叫定時(shí)刪除,這里的“定期”指的是Redis定期觸發(fā)的清理策略,由位于src/redis.c的activeExpireCycle(void)函數來(lái)完成。
serverCron是由redis的事件框架驅動(dòng)的定位任務(wù),這個(gè)定時(shí)任務(wù)中會(huì )調用activeExpireCycle函數,針對每個(gè)db在限制的時(shí)間REDIS_EXPIRELOOKUPS_TIME_LIMIT內遲可能多的刪除過(guò)期key,之所以要限制時(shí)間是為了防止過(guò)長(cháng)時(shí)間 的阻塞影響redis的正常運行。這種主動(dòng)刪除策略彌補了被動(dòng)刪除策略在內存上的不友好。
因此,Redis會(huì )周期性的隨機測試一批設置了過(guò)期時(shí)間的key并進(jìn)行處理。測試到的已過(guò)期的key將被刪除。典型的方式為,Redis每秒做10次如下的步驟:
- 隨機測試100個(gè)設置了過(guò)期時(shí)間的key
- 刪除所有發(fā)現的已過(guò)期的key
- 若刪除的key超過(guò)25個(gè)則重復步驟1
這是一個(gè)基于概率的簡(jiǎn)單算法,基本的假設是抽出的樣本能夠代表整個(gè)key空間,redis持續清理過(guò)期的數據直至將要過(guò)期的key的百分比降到了25%以下。這也意味著(zhù)在任何給定的時(shí)刻已經(jīng)過(guò)期但仍占據著(zhù)內存空間的key的量最多為每秒的寫(xiě)操作量除以4.
Redis-3.0.0中的默認值是10,代表每秒鐘調用10次后臺任務(wù)。
除了主動(dòng)淘汰的頻率外,Redis對每次淘汰任務(wù)執行的最大時(shí)長(cháng)也有一個(gè)限定,這樣保證了每次主動(dòng)淘汰不會(huì )過(guò)多阻塞應用請求,以下是這個(gè)限定計算公式:
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* CPU max % for keys collection */
…
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
hz調大將會(huì )提高Redis主動(dòng)淘汰的頻率,如果你的Redis存儲中包含很多冷數據占用內存過(guò)大的話(huà),可以考慮將這個(gè)值調大,但Redis作者建議這個(gè)值不要超過(guò)100。我們實(shí)際線(xiàn)上將這個(gè)值調大到100,觀(guān)察到CPU會(huì )增加2%左右,但對冷數據的內存釋放速度確實(shí)有明顯的提高(通過(guò)觀(guān)察keyspace個(gè)數和used_memory大?。?。
可以看出timelimit和server.hz是一個(gè)倒數的關(guān)系,也就是說(shuō)hz配置越大,timelimit就越小。換句話(huà)說(shuō)是每秒鐘期望的主動(dòng)淘汰頻率越高,則每次淘汰最長(cháng)占用時(shí)間就越短。這里每秒鐘的最長(cháng)淘汰占用時(shí)間是固定的250ms(1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/100),而淘汰頻率和每次淘汰的最長(cháng)時(shí)間是通過(guò)hz參數控制的。
從以上的分析看,當redis中的過(guò)期key比率沒(méi)有超過(guò)25%之前,提高hz可以明顯提高掃描key的最小個(gè)數。假設hz為10,則一秒內最少掃描200個(gè)key(一秒調用10次*每次最少隨機取出20個(gè)key),如果hz改為100,則一秒內最少掃描2000個(gè)key;另一方面,如果過(guò)期key比率超過(guò)25%,則掃描key的個(gè)數無(wú)上限,但是cpu時(shí)間每秒鐘最多占用250ms。
當REDIS運行在主從模式時(shí),只有主結點(diǎn)才會(huì )執行上述這兩種過(guò)期刪除策略,然后把刪除操作”del key”同步到從結點(diǎn)。
maxmemory
當前已用內存超過(guò)maxmemory限定時(shí),觸發(fā)主動(dòng)清理策略,這些策略可以配置(參數maxmemory-policy),包括以下幾個(gè):
- volatile-lru:從已設置過(guò)期時(shí)間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
- volatile-ttl:從已設置過(guò)期時(shí)間的數據集(server.db[i].expires)中挑選將要過(guò)期的數據淘汰
- volatile-random:從已設置過(guò)期時(shí)間的數據集(server.db[i].expires)中任意選擇數據淘汰-
- allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
- allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
- no-enviction(驅逐):禁止驅逐數據
當mem_used內存已經(jīng)超過(guò)maxmemory的設定,對于所有的讀寫(xiě)請求,都會(huì )觸發(fā)redis.c/freeMemoryIfNeeded(void)函數以清理超出的內存。注意這個(gè)清理過(guò)程是阻塞的,直到清理出足夠的內存空間。所以如果在達到maxmemory并且調用方還在不斷寫(xiě)入的情況下,可能會(huì )反復觸發(fā)主動(dòng)清理策略,導致請求會(huì )有一定的延遲。
清理時(shí)會(huì )根據用戶(hù)配置的maxmemory-policy來(lái)做適當的清理(一般是LRU或TTL),這里的LRU或TTL策略并不是針對redis的所有key,而是以配置文件中的maxmemory-samples個(gè)key作為樣本池進(jìn)行抽樣清理。
總結與備忘
如果Redis中每天過(guò)期大量Key(比如幾千萬(wàn)),那么必須得考慮過(guò)期Key的清理:
增加Redis主動(dòng)清理的頻率(通過(guò)調大hz參數);
手動(dòng)清理過(guò)期Key,最簡(jiǎn)單的方法是進(jìn)行scan操作,scan操作會(huì )觸發(fā)第一種被動(dòng)刪除,scan操作時(shí)候別忘了加count;
dbsize命令返回的Key數量,包含了過(guò)期Key;
randomkey命令返回的Key,不包含過(guò)期Key;
scan命令返回的Key,包含過(guò)期Key;
info命令返回的# Keyspace:
db6:keys=1034937352,expires=994731489,avg_ttl=507838502
keys對應的Key數量等同于dbsize;
expires指的是設置了過(guò)期時(shí)間的Key數量;
avg_ttl指設置了過(guò)期時(shí)間的Key的平均過(guò)期時(shí)間(單位:毫秒);
