08 | 替换策略: 缓存满了怎么办?
1. 前言
??為了保證較高的性價比,緩存的空間容量必然要小于后端數(shù)據(jù)庫的數(shù)據(jù)總量。不過,內(nèi)存大小畢竟有限,隨著要緩存的數(shù)據(jù)量越來越大,有限的緩存空間不可避免地會被寫滿。此時,該怎么辦呢?
??解決這個問題就涉及到緩存系統(tǒng)的一個重要機制,即緩存數(shù)據(jù)的淘汰機制。簡單來說,數(shù)據(jù)淘汰機制包括兩步:第一,根據(jù)一定的策略,篩選出對應(yīng)用訪問來說“不重要”的數(shù)據(jù);第二,將這些數(shù)據(jù)從緩存中刪除,為新來的數(shù)據(jù)騰出空間,
2. 設(shè)置的緩存容量
??我們先看看藍線。它表示的就是“八二原理”,有 20% 的數(shù)據(jù)貢獻了 80% 的訪問了,而剩余的數(shù)據(jù)雖然體量很大,但只貢獻了 20% 的訪問量。這 80% 的數(shù)據(jù)在訪問量上就形成了一條長長的尾巴,我們也稱為“長尾效應(yīng)”。我會建議把緩存容量設(shè)置為總數(shù)據(jù)量的 15% 到 30%,兼顧訪問性能和內(nèi)存空間開銷。
3. Redis緩存有淘汰策略
??我們可以按照是否會進行數(shù)據(jù)淘汰把它們分成兩類:
- 不進行數(shù)據(jù)淘汰的策略,只有 noeviction 這一種。
- 會進行淘汰的 7 種其他策略。
會進行淘汰的 7 種策略,
??我們可以再進一步根據(jù)淘汰候選數(shù)據(jù)集的范圍把它們分成兩類:
- 在設(shè)置了過期時間的數(shù)據(jù)中進行淘汰,包括 volatile-random、volatile-ttl、volatile、lru、volatile-lfu(Redis 4.0 后新增)四種。
- 在所有數(shù)據(jù)范圍內(nèi)進行淘汰,包括 allkeys-lru、allkeys-random、allkeys-lfu(Redis4.0 后新增)三種。
??默認情況下,Redis 在使用的內(nèi)存空間超過 maxmemory 值時,并不會淘汰數(shù)據(jù),也就是設(shè)定的noeviction 策略。對應(yīng)到 Redis 緩存,也就是指,一旦緩存被寫滿了,再有寫請求來時,Redis 不再提供服務(wù),而是直接返回錯誤。Redis 用作緩存時,實際的數(shù)據(jù)集通常都是大于緩存容量的,總會有新的數(shù)據(jù)要寫入緩存,這個策略本身不淘汰數(shù)據(jù),也就不會騰出新的緩存空間,我們不把它用在 Redis 緩存中。
??我們再分析下 volatile-random、volatile-ttl、volatile-lru 和 volatile-lfu 這四種淘汰策略。它們篩選的候選數(shù)據(jù)范圍,被限制在已經(jīng)設(shè)置了過期時間的鍵值對上。也正因為此,即使緩存沒有寫滿,這些數(shù)據(jù)如果過期了,也會被刪除。
??例如,我們使用 EXPIRE 命令對一批鍵值對設(shè)置了過期時間后,無論是這些鍵值對的過期時間是快到了,還是 Redis 的內(nèi)存使用量達到了 maxmemory 閾值,Redis 都會進一步按照 volatile-ttl、volatile-random、volatile-lru、volatile-lfu 這四種策略的具體篩選規(guī)則進行淘汰。
- volatile-ttl 在篩選時,會針對設(shè)置了過期時間的鍵值對,根據(jù)過期時間的先后進行刪除,越早過期的越先被刪除。
- volatile-random 就像它的名稱一樣,在設(shè)置了過期時間的鍵值對中,進行隨機刪除。
- volatile-lru 會使用 LRU 算法篩選設(shè)置了過期時間的鍵值對。
- volatile-lfu 會使用 LFU 算法選擇設(shè)置了過期時間的鍵值對
??相對于 volatile-ttl、volatile-random、volatile-lru、volatile-lfu 這四種策略淘汰的是設(shè)置了過期時間的數(shù)據(jù),allkeys-lru、allkeys-random、allkeys-lfu 這三種淘汰策略的備選淘汰數(shù)據(jù)范圍,就擴大到了所有鍵值對,無論這些鍵值對是否設(shè)置了過期時間。它們篩選數(shù)據(jù)進行淘汰的規(guī)則是:
- allkeys-random 策略,從所有鍵值對中隨機選擇并刪除數(shù)據(jù);
- allkeys-lru 策略,使用 LRU 算法在所有數(shù)據(jù)中進行篩選。
- allkeys-lfu 策略,使用 LFU 算法在所有數(shù)據(jù)中進行篩選。
??LRU 算法的全稱是 Least Recently Used,從名字上就可以看出,這是按照最近最少使用的原則來篩選數(shù)據(jù),最不常用的數(shù)據(jù)會被篩選出來,而最近頻繁使用的數(shù)據(jù)會留在緩存中。
??那具體是怎么篩選的呢?LRU 會把所有的數(shù)據(jù)組織成一個鏈表,鏈表的頭和尾分別表示MRU 端和 LRU 端,分別代表最近最常使用的數(shù)據(jù)和最近最不常用的數(shù)據(jù)。我們看一個例子。
??LRU 算法在實際實現(xiàn)時,需要用鏈表管理所有的緩存數(shù)據(jù),這會帶來額外的空間開銷。而且,當(dāng)有數(shù)據(jù)被訪問時,需要在鏈表上把該數(shù)據(jù)移動到 MRU 端,如果有大量數(shù)據(jù)被訪問,就會帶來很多鏈表移動操作,會很耗時,進而會降低 Redis 緩存性能。
??所以,在 Redis 中,LRU 算法被做了簡化,以減輕數(shù)據(jù)淘汰對緩存性能的影響。具體來說,Redis 默認會記錄每個數(shù)據(jù)的最近一次訪問的時間戳(由鍵值對數(shù)據(jù)結(jié)構(gòu)RedisObject 中的 lru 字段記錄)。然后,Redis 在決定淘汰的數(shù)據(jù)時,第一次會隨機選出N 個數(shù)據(jù),把它們作為一個候選集合。接下來,Redis 會比較這 N 個數(shù)據(jù)的 lru 字段,把lru 字段值最小的數(shù)據(jù)從緩存中淘汰出去。
??Redis 提供了一個配置參數(shù) maxmemory-samples,這個參數(shù)就是 Redis 選出的數(shù)據(jù)個數(shù)N。例如,我們執(zhí)行如下命令,可以讓 Redis 選出 100 個數(shù)據(jù)作為候選數(shù)據(jù)集:
??當(dāng)需要再次淘汰數(shù)據(jù)時,Redis 需要挑選數(shù)據(jù)進入第一次淘汰時創(chuàng)建的候選集合。這兒的挑選標準是:能進入候選集合的數(shù)據(jù)的 lru 字段值必須小于候選集合中最小的 lru 值。有新數(shù)據(jù)進入候選數(shù)據(jù)集后,如果候選數(shù)據(jù)集中的數(shù)據(jù)個數(shù)達到了 maxmemory-samples,Redis 就把候選數(shù)據(jù)集中 lru 字段值最小的數(shù)據(jù)淘汰出去。
??我們就學(xué)完了除了使用 LFU 算法以外的 5 種緩存淘汰策略,我再給你三個使用建議。
- 優(yōu)先使用 allkeys-lru 策略。這樣,可以充分利用 LRU 這一經(jīng)典緩存算法的優(yōu)勢,把最近最常訪問的數(shù)據(jù)留在緩存中,提升應(yīng)用的訪問性能。
- 如果你的業(yè)務(wù)數(shù)據(jù)中有明顯的冷熱數(shù)據(jù)區(qū)分,我建議你使用 allkeys-lru 策略。
- 如果業(yè)務(wù)應(yīng)用中的數(shù)據(jù)訪問頻率相差不大,沒有明顯的冷熱數(shù)據(jù)區(qū)分,建議使用allkeys-random 策略,隨機選擇淘汰的數(shù)據(jù)就行。
- 如果你的業(yè)務(wù)中有置頂?shù)男枨?#xff0c;比如置頂新聞、置頂視頻,那么,可以使用 volatile-lru策略,同時不給這些置頂數(shù)據(jù)設(shè)置過期時間。這樣一來,這些需要置頂?shù)臄?shù)據(jù)一直不會被刪除,而其他數(shù)據(jù)會在過期時根據(jù) LRU 規(guī)則進行篩選。
總結(jié)
以上是生活随笔為你收集整理的08 | 替换策略: 缓存满了怎么办?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 07丨切片集群:数据增多了,是该加内存还
- 下一篇: 03 | 事务隔离:为什么你改了我还看不