redis的淘汰機制的分享
本文目的:瞭解redis的記憶體優化,對後續可能的redis優化工作提供一定的幫助
名詞解釋
過期策略:即redis針對過期的key使用的清除策略,策略為,定期刪除+惰性刪除
記憶體淘汰機制:即記憶體佔用達到記憶體限制設定值時觸發的redis的淘汰策略來刪除鍵
說明:Redis2.6以後expire精度可以控制在0到1毫秒內,key的過期資訊以絕對Unix時間戳的形式儲存(Redis2.6之後以毫秒級別的精度儲存),所以在多伺服器同步的時候,一定要同步各個伺服器的時間
redis key過期的方式有三種
1、定期刪除,redis預設每隔100ms檢查,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每隔100ms將所有的key檢查一次,而是隨機抽取進行檢查。因此,如果只採用定期刪除策略,會導致很多key到時間沒有刪除。
在 Redis 2.6 版本中, 程式規定 serverCron 每秒執行 10 次, 平均每 100 毫秒執行一次。 從 Redis 2.8 開始, 使用者可以通過修改 hz選項來調整 serverCron 的每秒執行次數, 具體資訊請參考 redis.conf 檔案中關於 hz 選項的說明
2、惰性刪除,也就是說在你獲取某個key的時候,redis會檢查一下,這個key如果設定了過期時間那麼是否過期了?如果過期了此時就會刪除。
這種刪除策略對CPU是友好的,刪除操作只有在不得不的情況下才會進行,不會其他的expire key上浪費無謂的CPU時間。 但是這種策略對記憶體不友好,一個key已經過期,但是在它被操作之前不會被刪除,仍然佔據記憶體空間。如果有大量的過期鍵存在但是又很少被訪問到,那會造成大量的記憶體空間浪費。expireIfNeeded(redisDb *db, robj *key)函式位於src/db.c。
3、當前已用記憶體超過maxmemory限定時,觸發主動清理策略。即淘汰策略
定期刪除
Redis會週期性的隨機測試一批設定了過期時間的key並進行處理。測試到的已過期的key將被刪除。典型的方式為,Redis每秒做10次如下的步驟:
- 隨機測試100個設定了過期時間的key
- 刪除所有發現的已過期的key
- 若刪除的key超過25個則重複步驟1
這是一個基於概率的簡單演算法,基本的假設是抽出的樣本能夠代表整個key空間,redis持續清理過期的資料直至將要過期的key的百分比降到了25%以下。這也意味著在任何給定的時刻已經過期但仍佔據著記憶體空間的key的量最多為每秒的寫操作量除以4.
除了主動淘汰的頻率外,Redis對每次淘汰任務執行的最大時長也有一個限定,這樣保證了每次主動淘汰不會過多阻塞應用請求
將redis.conf 配置檔案中的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執行在主從模式時,只有主結點才會執行上述這兩種過期刪除策略,然後把刪除操作”del key”同步到從結點。
過期策略存在的問題,由於redis定期刪除是隨機抽取檢查,不可能掃描清除掉所有過期的key並刪除,然後一些key由於未被請求,惰性刪除也未觸發。這樣redis的記憶體佔用會越來越高。此時就需要記憶體淘汰機制
記憶體淘汰機制
redis配置檔案中可以使用maxmemory 將記憶體使用限制設定為指定的位元組數。當達到記憶體限制時,Redis會根據選擇的淘汰策略來刪除鍵。(ps:沒搞明白為什麼不是百分比)
策略有如下幾種:(LRU的意思是:Least Recently Used最近最少使用的,LFU的意思是:Least Frequently Used最不常用的)
allkeys-lru:在主鍵空間中,優先移除最近未使用的key。
volatile-lru:在設定了過期時間的鍵空間中,優先移除最近未使用的key。
allkeys-random:在主鍵空間中,隨機移除某個key。
volatile-random:在設定了過期時間的鍵空間中,隨機移除某個key。
volatile-ttl:在設定了過期時間的鍵空間中,具有更早過期時間的key優先移除。
noeviction:永不過期。返回錯誤當memory_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。
# 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
最常見的實現是使用一個連結串列儲存快取資料,詳細演算法實現如下
1、新資料插入到連結串列頭部;
2、每當快取命中(即快取資料被訪問),則將資料移到連結串列頭部;
3、當連結串列滿的時候,將連結串列尾部的資料丟棄。
Redis中的LRU與常規的LRU實現並不相同,常規LRU會準確的淘汰掉隊頭的元素,但是Redis的LRU並不維護佇列,只是根據配置的策略要麼從所有的key中隨機選擇N個(N可以配置)要麼從所有的設定了過期時間的key中選出N個鍵,然後再從這N個鍵中選出最久沒有使用的一個key進行淘汰。
主鍵空間和設定了過期時間的鍵空間:舉個例子,假設我們有一批鍵儲存在Redis中,則有那麼一個雜湊表用於儲存這批鍵及其值,如果這批鍵中有一部分設定了過期時間,那麼這批鍵還會被儲存到另外一個雜湊表中,這個雜湊表中的值對應的是鍵被設定的過期時間。設定了過期時間的鍵空間為主鍵空間的子集。
Replication link和AOF檔案中的過期處理
為了獲得正確的行為而不至於導致一致性問題,當一個key過期時DEL操作將被記錄在AOF檔案並傳遞到所有相關的slave。也即過期刪除操作統一在master例項中進行並向下傳遞,而不是各salve各自掌控。這樣一來便不會出現資料不一致的情形。當slave連線到master後並不能立即清理已過期的key(需要等待由master傳遞過來的DEL操作),slave仍需對資料集中的過期狀態進行管理維護以便於在slave被提升為master會能像master一樣獨立的進行過期處理。
為什麼要使用近似LRU?
1、效能問題,由於近似LRU演算法只是最多隨機取樣N個key並對其進行排序,如果精準需要對所有key進行排序,這樣近似LRU效能更高
2、記憶體佔用問題,redis對記憶體要求很高,會盡量降低記憶體使用率,如果是抽樣排序可以有效降低記憶體的佔用
3、實際效果基本相等,如果請求符合長尾法則,那麼真實LRU與Redis LRU之間表現基本無差異
4、在近似情況下提供可自配置的取樣率來提升精準度,例如通過 CONFIG SET maxmemory-samples 指令可以設定取樣數,取樣數越高越精準,如果你的CPU和記憶體有足夠,可以提高取樣數看命中率來探測最佳的取樣比例。