閱讀258 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Redis 數據過期策略

1、設置過期時間

  • expire key time(以秒為單位)--這是最常用的方式
  • setex(String key, int seconds, String value)–字符串獨有的方式

注意

  • 除了字符串自己獨有設置過期時間的方法外,其他方法都需要依靠expire方法來設置時間
  • 如果沒有設置時間,那緩存就是永不過期
  • 如果設置了過期時間,之後又想讓緩存永不過期,使用persist key


2、三種過期策略


Redis key過期的方式有三種:

    被動刪除:當讀/寫一個已經過期的key時,會觸發惰性刪除策略,直接刪除掉這個過期key

    主動刪除:由於惰性刪除策略無法保證冷數據被及時刪掉,所以Redis會定期主動淘汰一批已過期的key

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


被動刪除(惰性刪除)

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

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

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

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


優點:刪除操作隻發生在從數據庫取出key的時候發生,而且隻刪除當前key,所以對CPU時間的占用是比較少的,而且此時的刪除是已經到了非做不可的地步(如果此時還不刪除的話,我們就會獲取到了已經過期的key了)

缺點:若大量的key在超出超時時間後,很久一段時間內,都沒有被獲取過,那麼可能發生內存泄露(無用的垃圾占用了大量的內存)


主動刪除(定期刪除)

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

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

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

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

也叫定時刪除,這裏的“定期”指的是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”同步到從結點。


優點:

通過限製刪除操作的時長和頻率,來減少刪除操作對CPU時間的占用–處理"定時刪除"的缺點

定期刪除過期key–處理"惰性刪除"的缺點


缺點

在內存友好方麵,不如"定時刪除"

在CPU時間友好方麵,不如"惰性刪除"


難點

合理設置刪除操作的執行時長(每次刪除執行多長時間)和執行頻率(每隔多長時間做一次刪除)(這個要根據服務器運行情況來定了)



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服務器有16個數據庫(關於數據庫的設置,看下邊代碼),默認使用0號數據庫,所有的操作都是對0號數據庫的操作。
    ExpandedBlockStart.gif
    # 設置數據庫數量。默認為16個庫,默認使用DB 0,可以使用"select 1"來選擇一號數據庫 # 注意:由於默認使用0號數據庫,那麼我們所做的所有的緩存操作都存在0號數據庫上, # 當你在1號數據庫上去查找的時候,就查不到之前set過得緩存 # 若想將0號數據庫上的緩存移動到1號數據庫,可以使用"move key 1" databases 16
  • memcached隻是用了惰性刪除,而redis同時使用了惰性刪除與定期刪除,這也是二者的一個不同點(可以看做是redis優於memcached的一點)
  • 對於惰性刪除而言,並不是隻有獲取key的時候才會檢查key是否過期,在某些設置key的方法上也會檢查(eg.setnx key2 value2:該方法類似於memcached的add方法,如果設置的key2已經存在,那麼該方法返回false,什麼都不做;如果設置的key2不存在,那麼該方法設置緩存key2-value2。假設調用此方法的時候,發現redis中已經存在了key2,但是該key2已經過期了,如果此時不執行刪除操作的話,setnx方法將會直接返回false,也就是說此時並沒有重新設置key2-value2成功,所以對於一定要在setnx執行之前,對key2進行過期檢查)


3、Redis采用的過期策略

惰性刪除+定期刪除

  • 惰性刪除流程
    • 在進行get或setnx等操作時,先檢查key是否過期,
    • 若過期,刪除key,然後執行相應操作;
    • 若沒過期,直接執行相應操作
  • 定期刪除流程(簡單而言,對指定個數個庫的每一個庫隨機刪除小於等於指定個數個過期key)
    • 遍曆每個數據庫(就是redis.conf中配置的"database"數量,默認為16)
      • 檢查當前庫中的指定個數個key(默認是每個庫檢查20個key,注意相當於該循環執行20次,循環體時下邊的描述)
        • 如果當前庫中沒有一個key設置了過期時間,直接執行下一個庫的遍曆
        • 隨機獲取一個設置了過期時間的key,檢查該key是否過期,如果過期,刪除key
        • 判斷定期刪除操作是否已經達到指定時長,若已經達到,直接退出定期刪除。

注意

  • 對於定期刪除,在程序中有一個全局變量current_db來記錄下一個將要遍曆的庫,假設有16個庫,我們這一次定期刪除遍曆了10個,那此時的current_db就是11,下一次定期刪除就從第11個庫開始遍曆,假設current_db等於15了,那麼之後遍曆就再從0號庫開始(此時current_db==0)
  • 由於在實際中並沒有操作過定期刪除的時長和頻率,所以這兩個值的設置方式作為疑問?


4、RDB對過期key的處理

過期key對RDB沒有任何影響

  • 從內存數據庫持久化數據到RDB文件
    • 持久化key之前,會檢查是否過期,過期的key不進入RDB文件
  • 從RDB文件恢複數據到內存數據庫
    • 數據載入數據庫之前,會對key先進行過期檢查,如果過期,不導入數據庫(主庫情況)


5、AOF對過期key的處理

過期key對AOF沒有任何影響

  • 從內存數據庫持久化數據到AOF文件:
    • 當key過期後,還沒有被刪除,此時進行執行持久化操作(該key是不會進入aof文件的,因為沒有發生修改命令)
    • 當key過期後,在發生刪除操作時,程序會向aof文件追加一條del命令(在將來的以aof文件恢複數據的時候該過期的鍵就會被刪掉)
  • AOF重寫
    • 重寫時,會先判斷key是否過期,已過期的key不會重寫到aof文件

最後更新:2017-06-20 17:02:32

  上一篇:go  為提升在線語音識別效率,他創造了兩種升級版算法模型
  下一篇:go  增強學習與無人駕駛