天天看點

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 中, 正常操作由 <code>redis.c/servercron</code> 實作, 它主要執行以下操作

更新伺服器的各類統計資訊,比如時間、記憶體占用、資料庫占用情況等。

清理資料庫中的過期鍵值對。

對不合理的資料庫進行大小調整。

關閉和清理連接配接失效的用戶端。

嘗試進行 aof 或 rdb 持久化操作。

如果伺服器是主節點的話,對附屬節點進行定期同步。

如果處于叢集模式的話,對叢集進行定期同步和連接配接測試。

redis 将 <code>servercron</code> 作為時間事件來運作, 進而確定它每隔一段時間就會自動運作一次, 又因為  <code>servercron</code> 需要在 redis 伺服器運作期間一直定期運作, 是以它是一個循環時間事件:  <code>servercron</code> 會一直定期執行,直到伺服器關閉為止。

也叫定時删除,這裡的“定期”指的是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對每次淘汰任務執行的最大時長也有一個限定,這樣保證了每次主動淘汰不會過多阻塞應用請求,以下是這個限定計算公式:

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限定時,觸發 主動清理 政策

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号資料庫的操作。

Redis 資料過期政策

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檔案