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

































