面試必問:Redis過期Key刪除和內(nèi)存淘汰策略
本文轉(zhuǎn)載自微信公眾號「蟲爸說說」,作者蟲爸 。轉(zhuǎn)載本文請聯(lián)系蟲爸說說公眾號。
眾所周知,Redis是一種內(nèi)存級kv數(shù)據(jù)庫,所有的操作都是在內(nèi)存里面進行,定期通過異步操作把數(shù)據(jù)庫數(shù)據(jù)flush到硬盤上進行保存。因此它是純內(nèi)存操作,Redis的性能非常出色,每秒可以處理超過10萬次讀寫操作。雖然是內(nèi)存數(shù)據(jù)庫,但是其數(shù)據(jù)可以持久化,而且支持豐富的數(shù)據(jù)類型。
正因為是內(nèi)存級操作,那么其受限于物理內(nèi)存,所以Redis提供了過期key的刪除以及內(nèi)存淘汰策略,從而在一定程度上,能夠避免達到內(nèi)存上限。
在本文中,我們首先介紹下如何對某個key設(shè)置過期時間,然后再次介紹對于這些過期key都有哪些處理策略,隨后分析下在內(nèi)存達到上限時候,redis采取的策略。
設(shè)置過期
redis中設(shè)置過期時間有四種方式:
- expire key seconds:設(shè)置key在N秒后過期;
- pexpire key milliseconds:設(shè)置key在n毫秒后過期;
- expire key timestamp:設(shè)置key在某個時間戳后過期(精確到秒)
- pexpireat key millisecondstimestamp:設(shè)置key在一個時間戳后過期(精確到毫秒)
下面,我們來看看具體命令的用法。
expire: N秒后過期
- 127.0.0.1:6379> set key value
- OK
- 127.0.0.1:6379> expire key 100
- (integer) 1
- 127.0.0.1:6379> ttl key
- (integer) 93
其中命令TTL的全稱是 time to live,意思是key在N秒后過期。比如上面的結(jié)果93表示key在93s后過期。
pexpire: N毫秒后過期
- 127.0.0.1:6379> set key2 value2
- OK
- 127.0.0.1:6379> pexpire key2 100000
- (integer) 1
- 127.0.0.1:6379> pttl key2
- (integer) 94524
pexpire key2 100000 表示 key2 設(shè)置為在 100000 毫秒(100 秒)后過期。
expireat: 在某個時間戳過期(精確到秒)
- 127.0.0.1:6379> set key3 value3
- OK
- 127.0.0.1:6379> expireat key3 1630644399
- (integer) 1
- 127.0.0.1:6379> ttl key3
- (integer) 67
expired Key3 1630644399(精確到秒)之后過期。使用TTL查詢,可以發(fā)現(xiàn)Key3會在67s后過期。
在redis中,可以使用time命令查詢當前時間的時間戳(精確到秒),例如:
127.0.0.1:6379> time
1) "1630644526"
2) "239640"
pexpireat: 在某個時間戳過期(精確到毫秒)
- 127.0.0.1:6379> set key4 value4
- OK
- 127.0.0.1:6379> pexpireat key4 1630644499740
- (integer) 1
- 127.0.0.1:6379> pttl key4
- (integer) 3522
其中,pexpireat key4 1630644499740表示key4在時間戳1630644499740(精確到毫秒)之后過期。使用TTL查詢可以發(fā)現(xiàn)key4會在3522ms后過期。
value為string時候的過期設(shè)置
直接操作value為string的過期時間有幾種方法,如下所示:
- set key value ex seconds:N秒后過期
- set key value ex milliseconds:設(shè)置key在n毫秒后過期;
- setex key seconds value:為指定的 key 設(shè)置值及其過期時間,如果 key 已經(jīng)存在, SETEX 命令將會替換舊的值。
設(shè)置kv對在N秒后過期
- 127.0.0.1:6379> set k v ex 100
- OK
- 127.0.0.1:6379> ttl k
- (integer) 97
設(shè)置kv對在N毫秒后過期
- 127.0.0.1:6379> set k2 v2 px 100000
- OK
- 127.0.0.1:6379> pttl k2
- (integer) 92483
使用setex來設(shè)置
- 127.0.0.1:6379> setex k3 100 v3
- OK
- 127.0.0.1:6379> ttl k3
- (integer) 91
取消過期
使用命令:persist key去除key值的過期時間,如下代碼所示:
- 127.0.0.1:6379> ttl k3
- (integer) 97
- 127.0.0.1:6379> persist k3
- (integer) 1
- 127.0.0.1:6379> ttl k3
- (integer) -1
可以看出,第一次使用TTL查詢K3,97s后就會過期。使用persist命令查詢K3的生命周期的結(jié)果是-1,表示K3永不過期。
過期策略
redis對過期key的刪除策略,有定時刪除、定期刪除和惰性刪除三種。
定時刪除
創(chuàng)建一個定時器,當key設(shè)置有過期時間,且過期時間到達時,由定時器任務(wù)執(zhí)行對key的刪除操作。
- 優(yōu)點:節(jié)約內(nèi)存,到時就刪除,快速釋放掉不必要的內(nèi)存占用
- 缺點:CPU壓力很大,無論CPU此時負載量多高,均占用CPU,會影響redis服務(wù)器響應(yīng)時間和指令吞吐量
定期刪除
redis默認是每隔100ms就隨機抽取一些設(shè)置了過期時間的key,檢查其是否過期,如果過期就刪除。注意這里是隨機抽取的。為什么要隨機呢?假如redis存了幾十萬個key,每隔100ms就遍歷所有的設(shè)置過期時間的key的話,就會給CPU帶來很大的負載。
- 優(yōu)點:可以通過限制刪除操作執(zhí)行的時長和頻率來減少刪除操作對 CPU 的影響。另外定期刪除,也能有效釋放過期鍵占用的內(nèi)存。
- 缺點:難以確定刪除操作執(zhí)行的時長和頻率。
如果執(zhí)行的太頻繁,定期刪除策略變得和定時刪除策略一樣,對CPU不友好,如果執(zhí)行的太少,那又和惰性刪除一樣了,過期鍵占用的內(nèi)存不會及時得到釋放。
另外最重要的是,在獲取某個鍵時,如果某個鍵的過期時間已經(jīng)到了,但是還沒執(zhí)行定期刪除,那么就會返回這個鍵的值,這是業(yè)務(wù)不能忍受的錯誤。
惰性刪除
定期刪除可能會導(dǎo)致很多過期key到了時間并沒有被刪除掉。所以就有了惰性刪除。假如你的過期key,靠定期刪除沒有被刪除掉,還停留在內(nèi)存里,除非你的系統(tǒng)去查一下那個key,才會被redis給刪除掉。這就是所謂的惰性刪除。expireIfNeeded(),檢查數(shù)據(jù)是否過期,執(zhí)行g(shù)et的時候調(diào)用。
- 優(yōu)點:節(jié)約CPU性能,發(fā)現(xiàn)必須刪除的時候才刪除。
- 缺點:內(nèi)存壓力很大,出現(xiàn)長期占用內(nèi)存的數(shù)據(jù)
換句話說,惰性刪除就是用存儲空間換取處理器性能
結(jié)合上述三種策略的優(yōu)缺點,redis采取了折中的刪除策略,即采用的是定期刪除+惰性刪除策略。
1、定時刪除,用一個定時器來負責(zé)監(jiān)視key,過期則自動刪除。雖然內(nèi)存及時釋放,但是十分消耗CPU資源。在大并發(fā)請求下,CPU要將時間應(yīng)用在處理請求,而不是刪除key,因此沒有采用這一策略
定期刪除+惰性刪除是如何工作的呢?
2、定期刪除,redis默認每個100ms檢查,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每個100ms將所有的key檢查一次,而是隨機抽取進行檢查(如果每隔100ms,全部key進行檢查,redis豈不是卡死)。因此,如果只采用定期刪除策略,會導(dǎo)致很多key到時間沒有刪除。
3、惰性刪除,也就是說在你獲取某個key的時候,redis會檢查一下,這個key如果設(shè)置了過期時間那么是否過期了?如果過期了此時就會刪除。
但是這種方案,仍然存在缺點: 如果定期刪除沒刪除key。然后你也沒及時去請求key,也就是說惰性刪除也沒生效。這樣,redis的內(nèi)存會越來越高。那么就應(yīng)該采用內(nèi)存淘汰機制。
內(nèi)存淘汰策略
maxmemory 用于指定 Redis 能使用的最大內(nèi)存。既可以在 redis.conf 文件中設(shè)置, 也可以在運行過程中通過 CONFIG SET 命令動態(tài)修改。
例如, 要設(shè)置 100MB 的內(nèi)存限制, 可以在 redis.conf 文件中這樣配置:
- maxmemory 100mb
上述命令設(shè)置了redis內(nèi)存上限,當內(nèi)存中的數(shù)據(jù)量達到其設(shè)置的上限的時候,就需要采取一定的淘汰策略,否則會影響redis的正常訪問。
為了更好的實現(xiàn)這一點,必須針對不同的應(yīng)用場景提供不同的策略,下面,我們將介紹下redis支持的幾種內(nèi)存淘汰策略。
Redis 提供了以下幾種策略供用戶選擇,其中noeviction 策略的默認策略為。
- noeviction:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,新寫入操作會報錯。
- allkeys-lru:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在鍵空間中,移除最近最少使用的key。
- allkeys-random:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在鍵空間中,隨機移除某個key。
- volatile-lru:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在設(shè)置了過期時間的鍵空間中,移除最近最少使用的key。
- volatile-random:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在設(shè)置了過期時間的鍵空間中,隨機移除某個key。
- volatile-ttl:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在設(shè)置了過期時間的鍵空間中,有更早過期時間的key優(yōu)先移除。
需要注意的是,如果沒有設(shè)置 expire 的key, 不滿足先決條件,那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行為, 和 noeviction(不刪除) 基本上一致。
Redis 使用的并不是完全LRU算法。自動驅(qū)逐的 key , 并不一定是最滿足LRU特征的那個. 而是通過近似LRU算法, 抽取少量的 key 樣本, 然后刪除其中訪問時間最古老的那個key。
驅(qū)逐算法, 從 Redis 3.0 開始得到了巨大的優(yōu)化, 使用 pool(池子) 來作為候選. 這大大提升了算法效率, 也更接近于真實的LRU算法。
在 Redis 的 LRU 算法中, 可以通過設(shè)置樣本(sample)的數(shù)量來調(diào)優(yōu)算法精度。
maxmemory-samples 5
以上就是Redis的六種淘汰策略。關(guān)于這六種策略的使用,使用者需要根據(jù)自身實際需要,選擇合理的淘汰策略。讀者可以根據(jù)自身需求,再結(jié)合下面的筆者經(jīng)驗,進行策略選擇。
- 當部分數(shù)據(jù)訪問頻率較高而其余部分訪問頻率較低,或者數(shù)據(jù)的使用頻率無法預(yù)測時,設(shè)置allkeys-lru比較合適。
- 如果所有數(shù)據(jù)訪問概率大致相等,可以選擇allkeys-random。
- 如果開發(fā)者需要通過設(shè)置不同的ttls來確定數(shù)據(jù)過期的順序,此時可以選擇volatile-ttl策略。
- 如果你想讓一些數(shù)據(jù)長期保存,而一些數(shù)據(jù)可以消除,最好選擇volatile-lru或volatile-random。
- 由于設(shè)置expire會消耗額外的內(nèi)存,如果你打算避免Redis內(nèi)存浪費在這一項上,可以選擇allkeys-lru策略,這樣就可以不再設(shè)置過期時間,高效利用內(nèi)存。
經(jīng)驗之談
對于redis的操作,我們應(yīng)該慎之又慎。
- 不要放垃圾數(shù)據(jù),及時清理無用數(shù)據(jù)。
- key盡量都設(shè)置過期時間。對具有時效性的key設(shè)置過期時間,通過redis自身的過期key清理策略來降低過期key對于內(nèi)存的占用,同時也能夠減少業(yè)務(wù)的麻煩,不需要定期手動清理了。
- 單Key不要過大,這種key在get的時候網(wǎng)絡(luò)傳輸延遲會比較大,需要分配的輸出緩沖區(qū)也比較大,在定期清理的時候也容易造成比較高的延遲. 最好能通過業(yè)務(wù)拆分,數(shù)據(jù)壓縮等方式避免這種過大的key的產(chǎn)生。
- 不同業(yè)務(wù)如果公用一個業(yè)務(wù)的話,最好使用不同的邏輯db分開。這是因為Redis的過期Key清理策略和強制淘汰策略都會遍歷各個db。將key分布在不同的db有助于過期Key的及時清理。另外不同業(yè)務(wù)使用不同db也有助于問題排查和無用數(shù)據(jù)的及時下線。