Redis中的數據過期策略詳解

轉自https://www.jb51.net/article/103236.htm 

Redis key過期的方式有三種:

  1. 被動刪除:當讀/寫一個已經過期的key時,會觸發惰性刪除策略,直接刪除掉這個過期key
  2. 主動刪除:由於惰性刪除策略無法保證冷數據被及時刪掉,所以Redis會定期主動淘汰一批已過期的key
  3. 當前已用內存超過maxmemory限定時,觸發主動清理策略

被動刪除

只有key被操作時(如GET),REDIS纔會被動檢查該key是否過期,如果過期則刪除之並且返回NIL。

1、這種刪除策略對CPU是友好的,刪除操作只有在不得不的情況下才會進行,不會其他的expire key上浪費無謂的CPU時間。

2、但是這種策略對內存不友好,一個key已經過期,但是在它被操作之前不會被刪除,仍然佔據內存空間。如果有大量的過期鍵存在但是又很少被訪問到,那會造成大量的內存空間浪費。expireIfNeeded(redisDb *db, robj *key)函數位於src/db.c。

但僅是這樣是不夠的,因爲可能存在一些key永遠不會被再次訪問到,這些設置了過期時間的key也是需要在過期後被刪除的,我們甚至可以將這種情況看作是一種內存泄露----無用的垃圾數據佔用了大量的內存,而服務器卻不會自己去釋放它們,這對於運行狀態非常依賴於內存的Redis服務器來說,肯定不是一個好消息

 

主動刪除

先說一下時間事件,對於持續運行的服務器來說, 服務器需要定期對自身的資源和狀態進行必要的檢查和整理, 從而讓服務器維持在一個健康穩定的狀態, 這類操作被統稱爲常規操作(cron job)

在 Redis 中, 常規操作由 redis.c/serverCron 實現, 它主要執行以下操作

  • 更新服務器的各類統計信息,比如時間、內存佔用、數據庫佔用情況等。
  • 清理數據庫中的過期鍵值對。
  • 對不合理的數據庫進行大小調整。
  • 關閉和清理連接失效的客戶端。
  • 嘗試進行 AOF 或 RDB 持久化操作。
  • 如果服務器是主節點的話,對附屬節點進行定期同步。
  • 如果處於集羣模式的話,對集羣進行定期同步和連接測試。

Redis 將 serverCron 作爲時間事件來運行, 從而確保它每隔一段時間就會自動運行一次, 又因爲 serverCron 需要在 Redis 服務器運行期間一直定期運行, 所以它是一個循環時間事件: serverCron 會一直定期執行,直到服務器關閉爲止。

在 Redis 2.6 版本中, 程序規定 serverCron 每秒運行 10 次, 平均每 100 毫秒運行一次。 從 Redis 2.8 開始, 用戶可以通過修改 hz選項來調整 serverCron 的每秒執行次數, 具體信息請參考 redis.conf 文件中關於 hz 選項的說明

redis.windows-service.conf 文件中搜索hz 默認是: hz 10

也叫定時刪除,這裏的“定期”指的是Redis定期觸發的清理策略,由位於src/redis.c的activeExpireCycle(void)函數來完成。

serverCron是由redis的事件框架驅動的定位任務,這個定時任務中會調用activeExpireCycle函數,針對每個db在限制的時間REDIS_EXPIRELOOKUPS_TIME_LIMIT內遲可能多的刪除過期key,之所以要限制時間是爲了防止過長時間 的阻塞影響redis的正常運行。這種主動刪除策略彌補了被動刪除策略在內存上的不友好。

因此,Redis會週期性的隨機測試一批設置了過期時間的key並進行處理。測試到的已過期的key將被刪除。

典型的方式爲,Redis每秒做10次如下的步驟:

  • 隨機測試100個設置了過期時間的key
  • 刪除所有發現的已過期的key
  • 若刪除的key超過25個則重複步驟1

這是一個基於概率的簡單算法,基本的假設是抽出的樣本能夠代表整個key空間,redis持續清理過期的數據直至將要過期的key的百分比降到了25%以下。這也意味着在任何給定的時刻已經過期但仍佔據着內存空間的key的量最多爲每秒的寫操作量除以4.

Redis-3.0.0中的默認值是10,代表每秒鐘調用10次後臺任務。

除了主動淘汰的頻率外,Redis對每次淘汰任務執行的最大時長也有一個限定,這樣保證了每次主動淘汰不會過多阻塞應用請求,以下是這個限定計算公式:

#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調大將會提高Redis主動淘汰的頻率,如果你的Redis存儲中包含很多冷數據佔用內存過大的話,可以考慮將這個值調大,但Redis作者建議這個值不要超過100。我們實際線上將這個值調大到100,觀察到CPU會增加2%左右,但對冷數據的內存釋放速度確實有明顯的提高(通過觀察keyspace個數和used_memory大小)。

可以看出timelimit和server.hz是一個倒數的關係,也就是說hz配置越大,timelimit就越小。換句話說是每秒鐘期望的主動淘汰頻率越高,則每次淘汰最長佔用時間就越短。這裏每秒鐘的最長淘汰佔用時間是固定的250ms(1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/100),而淘汰頻率和每次淘汰的最長時間是通過hz參數控制的。

從以上的分析看,當redis中的過期key比率沒有超過25%之前,提高hz可以明顯提高掃描key的最小個數。假設hz爲10,則一秒內最少掃描200個key(一秒調用10次*每次最少隨機取出20個key),如果hz改爲100,則一秒內最少掃描2000個key;另一方面,如果過期key比率超過25%,則掃描key的個數無上限,但是cpu時間每秒鐘最多佔用250ms。

當REDIS運行在主從模式時,只有主結點纔會執行上述這兩種過期刪除策略,然後把刪除操作”del key”同步到從結點。

maxmemory

當前已用內存超過maxmemory限定時,觸發主動清理策略

  • volatile-lru:只對設置了過期時間的key進行LRU(默認值)
  • allkeys-lru : 刪除lru算法的key
  • volatile-random:隨機刪除即將過期key
  • allkeys-random:隨機刪除
  • volatile-ttl : 刪除即將過期的
  • noeviction : 永不過期,返回錯誤當mem_used內存已經超過maxmemory的設定,對於所有的讀寫請求,都會觸發redis.c/freeMemoryIfNeeded(void)函數以清理超出的內存。注意這個清理過程是阻塞的,直到清理出足夠的內存空間。所以如果在達到maxmemory並且調用方還在不斷寫入的情況下,可能會反覆觸發主動清理策略,導致請求會有一定的延遲。

當mem_used內存已經超過maxmemory的設定,對於所有的讀寫請求,都會觸發redis.c/freeMemoryIfNeeded(void)函數以清理超出的內存。注意這個清理過程是阻塞的,直到清理出足夠的內存空間。所以如果在達到maxmemory並且調用方還在不斷寫入的情況下,可能會反覆觸發主動清理策略,導致請求會有一定的延遲。

清理時會根據用戶配置的maxmemory-policy來做適當的清理(一般是LRU或TTL),這裏的LRU或TTL策略並不是針對redis的所有key,而是以配置文件中的maxmemory-samples個key作爲樣本池進行抽樣清理。

maxmemory-samples在redis-3.0.0中的默認配置爲5,如果增加,會提高LRU或TTL的精準度,redis作者測試的結果是當這個配置爲10時已經非常接近全量LRU的精準度了,並且增加maxmemory-samples會導致在主動清理時消耗更多的CPU時間,建議:

  • 儘量不要觸發maxmemory,最好在mem_used內存佔用達到maxmemory的一定比例後,需要考慮調大hz以加快淘汰,或者進行集羣擴容。
  • 如果能夠控制住內存,則可以不用修改maxmemory-samples配置;如果Redis本身就作爲LRU cache服務(這種服務一般長時間處於maxmemory狀態,由Redis自動做LRU淘汰),可以適當調大maxmemory-samples。

以下是上文中提到的配置參數的說明

# Redis calls an internal function to perform many background tasks, like 
# closing connections of clients in timeout, purging expired keys that are 
# never requested, and so forth. 
# 
# Not all tasks are performed with the same frequency, but Redis checks for 
# tasks to perform according to the specified "hz" value. 
# 
# By default "hz" is set to 10. Raising the value will use more CPU when 
# Redis is idle, but at the same time will make Redis more responsive when 
# there are many keys expiring at the same time, and timeouts may be 
# handled with more precision. 
# 
# The range is between 1 and 500, however a value over 100 is usually not 
# a good idea. Most users should use the default of 10 and raise this up to 
# 100 only in environments where very low latency is required. 
hz 10 
 
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory 
# is reached. You can select among five behaviors: 
# 
# volatile-lru -> remove the key with an expire set using an LRU algorithm 
# allkeys-lru -> remove any key according to the LRU algorithm 
# volatile-random -> remove a random key with an expire set 
# allkeys-random -> remove a random key, any key 
# volatile-ttl -> remove the key with the nearest expire time (minor TTL) 
# noeviction -> don't expire at all, just return an error on write operations 
# 
# Note: with any of the above policies, Redis will return an error on write 
# operations, when there are no suitable keys for eviction. 
# 
# At the date of writing these commands are: set setnx setex append 
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd 
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby 
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby 
# getset mset msetnx exec sort 
# 
# The default is: 
# 
maxmemory-policy noeviction 
 
# LRU and minimal TTL algorithms are not precise algorithms but approximated 
# algorithms (in order to save memory), so you can tune it for speed or 
# accuracy. For default Redis will check five keys and pick the one that was 
# used less recently, you can change the sample size using the following 
# configuration directive. 
# 
# The default of 5 produces good enough results. 10 Approximates very closely 
# true LRU but costs a bit more CPU. 3 is very fast but not very accurate. 
# 
maxmemory-samples 5

Replication link和AOF文件中的過期處理

爲了獲得正確的行爲而不至於導致一致性問題,當一個key過期時DEL操作將被記錄在AOF文件並傳遞到所有相關的slave。也即過期刪除操作統一在master實例中進行並向下傳遞,而不是各salve各自掌控。這樣一來便不會出現數據不一致的情形。當slave連接到master後並不能立即清理已過期的key(需要等待由master傳遞過來的DEL操作),slave仍需對數據集中的過期狀態進行管理維護以便於在slave被提升爲master會能像master一樣獨立的進行過期處理。

 

 

 

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