天天看點

Redis系列之過期淘汰機制

概述

使用redis時,一般是作為緩存系統,而不是存儲系統。緩存系統,即需要設定一個生存時間(TTL,time to live);存儲系統,即不設定生存時間,永不過期。除了生存時間,還有一個過期時間的概念,expire time,效果一樣,本文不加以區分。帶有TTL屬性的key在Redis中被稱為是不穩定的。設定TTL時間後,又想讓緩存永不過期,可使用​

​persist key​

​​,persist可以移除一個鍵的過期時間。過期時間​

​timestamp​

​是一個unix時間戳。

設定過期時間的幾類方式:

  1. ​expire key time​

    ​​:機關為秒;​

    ​pexpire key time​

    ​:機關毫秒;
  2. ​expireat key timestamp​

    ​​或者​

    ​pexpireat key timestamp​

    ​​為key指定過期時間,機關分别為秒和毫秒;​

    ​p​

    ​表示毫秒,不再贅述;
  3. ​setex(String key, int seconds, String value)​

    ​,隻能用于字元串鍵,還有其他指令如​

    ​setnx​

    ​;

​expire、pexpire、expireat、pexpireat​

​四個指令,前三個底層都是基于pexpireat,

怎麼得知某個鍵的剩餘過期時間還有多少?通過TTL指令或PTTL指令

設定過期時間後,Redis如何判斷是否過期,怎麼删除?

過期政策

有3個删除政策:

  1. 定時删除;
  2. 惰性删除;
  3. 定期删除

定時删除

政策:在設定key的過期時間的同時,為該key建立一個定時器,讓定時器在key的過期時間來臨時,對key進行删除。

優點:記憶體友好,保證記憶體被盡快釋放。

缺點:

  1. 若過期key很多,删除這些key會占用很多的CPU時間,在CPU時間緊張的情況下,顯得避重就輕沒有優先級;
  2. 定時器的建立需要用到redis的時間事件,其實作方式為無序連結清單,查找事件的時間複雜度為​

    ​O(N)​

    ​,效率低;

惰性删除

政策:key過期時不删除,每次從鍵空間擷取鍵時,檢查鍵是否過期,若過期,則删除,傳回null。

優點:對CPU時間友好。

缺點:若大量的key在超出逾時時間後,很久一段時間内,都沒有被擷取過,則可能發生記憶體洩露(無用的垃圾占用大量的記憶體)

定期删除

政策:每隔一段時間,程式對資料庫進行一次檢查,删除過期鍵。掃描什麼庫,删除多少鍵,有算法決定。定期删除主要是為了避免定時删除和惰性删除的問題,是前兩者的一個組合折中。

優點:

  1. 通過限制删除操作的時長和頻率,來減少删除操作對CPU時間的占用,解決定時删除的CPU時間占用問題;
  2. 定期删除過期key,解決惰性删除的記憶體浪費問題。

難點:

  1. 合理設定删除操作的執行時長(每次删除執行多長時間)和執行頻率(每隔多長時間做一次删除),每次執行時間太長,或者執行頻率太高對cpu都是一種壓力。
  2. 每次進行定期删除操作執行之後,需要記錄周遊循環到哪個标志位,以便下一次定期時間來時,從上次位置開始進行循環周遊。

說明:

memcached隻用惰性删除,而redis使用惰性删除與定期删除,二者的差別之一;

對于懶漢式删除而言,并不是隻有擷取key的時候才會檢查key是否過期,在某些設定key的方法上也會檢查(eg.setnx key2 value2:該方法類似于memcached的add方法,如果設定的key2已經存在,那麼該方法傳回false,什麼都不做;如果設定的key2不存在,那麼該方法設定緩存key2-value2。假設調用此方法的時候,發現redis中已經存在了key2,但是該key2已經過期了,如果此時不執行删除操作的話,setnx方法将會直接傳回false,也就是說此時并沒有重新設定key2-value2成功,是以對于一定要在setnx執行之前,對key2進行過期檢查)。

Redis

Redis采用的過期政策:惰性删除+定期删除,在合理使用CPU時間和避免記憶體空間浪費之間取得平衡。

惰性删除

有​

​db.c/expireIfNeeded​

​函數實作,所有讀寫資料庫的Redis指令在執行之前都會調用此函數對鍵進行過期檢查,過期則删除。相當于一個過濾器的角色。

定期删除

有​

​redis.c/activeExpireCycle​

​​函數實作,每當伺服器周期性操作​

​redis.c/serverCron​

​​函數時被調用執行,在規定的時間内,分多次周遊伺服器的各個資料庫,從資料庫的expires字典中随機檢查一部分鍵的過期時間,過期則删除。

僞代碼:

流程總結:

  1. 函數每次執行時,都從一定數量的資料庫中取出一定數量的随機鍵進行檢查,删除其中的過期鍵;
  2. 全局變量​

    ​current_db​

    ​​記錄目前​

    ​activeExpireCycle​

    ​函數檢查進度,在下一次被調用時,接着上一次的進度進行處理;
  3. 随着函數​

    ​activeExpireCycle​

    ​​的不斷執行,伺服器中的所有資料庫都會被檢查一遍,此時可以将​

    ​current_db​

    ​重置為0,開始新一輪的檢查。

記憶體不足

目前已用記憶體超過maxmemory限定時,觸發主動清理政策,Redis有6中政策:

  • volatile-lru:隻對設定過期時間的key進行LRU(預設值)
  • allkeys-lru:删除lru算法的key
  • volatile-random:随機删除即将過期key
  • allkeys-random:随機删除
  • volatile-ttl:删除即将過期的
  • noeviction:永不過期,傳回錯誤當​

    ​mem_used​

    ​記憶體已經超過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​

​。

原理

參考