Redis緩存的一些理解


之前簡單的學習過Redis,但只是簡單的增刪改查,最近在看極客時間關於Redis的專欄(《Redis核心技術與實戰》),有不少的收獲,記錄一下

 

緩存機制

一.工作原理

1.為什么redis適合做緩存?

緩存的兩個特征,分別是可以快速訪問;緩存寫滿時,數據需要被淘汰。而 Redis 天然就具有高性能訪問和數據淘汰機制,正好符合緩存的這兩個特征的要求,所以非常適合用作緩存。

2.redis做緩存的兩種模式

只讀緩存和讀寫緩存,

讀寫緩存提供了同步直寫和異步寫回這兩種模式,

同步直寫模式側重於保證數據可靠性,

而異步寫回模式則側重於提供低延遲訪問,

我們要根據實際的業務場景需求來進行選擇。

舉個例子,在商品大促的場景中,商品的庫存信息會一直被修改。如果每次修改都需到數據庫中處理,就會拖慢整個應用,此時,我們通常會選擇讀寫緩存的模式。而在短視頻 App 的場景中,雖然視頻的屬性有很多,但是,一般確定后,修改並不頻繁,此時,在數據庫中進行修改對緩存影響不大,所以只讀緩存模式是一個合適的選擇。

3.只讀緩存和使用直寫策略的讀寫緩存有什么區別嗎?

只讀緩存是犧牲了一定的性能,優先保證數據庫和緩存的一致性,它更適合對於一致性要求比較要高的業務場景。

而如果對於數據庫和緩存一致性要求不高,或者不存在並發修改同一個值的情況,那么使用讀寫緩存就比較合適,它可以保證更好的訪問性能。

二.替換策略

 

                                                           圖片來自極客專欄《Redis核心技術與實戰》

1.主要是注意LRU算法和LFU算法

LRU算法:

LRU算法是把所有的數據放在一個列表里,然后兩端分別為MRU端,LRU端,當某個位置的數據被訪問或者是添加了某個新數據的時候,會被移動到MRU端,如果在存儲空間滿了的情況下,新進來的數據會被放在MRU端,LRU端的一個數據會被淘汰.

但是LRU算法存在兩個問題:1.用鏈表存儲,會有額外的內存空間開銷;2.操作過程中會有數據移動,如果數據量過大,會很費時間,降低redis的緩存性能.

Redis修改了LRU算法:

Redis會記錄每個數據最近被訪問的時間戳(由鍵值對數據結構RedisObject里面的lru記錄),在Redis淘汰數據的時候,會隨機選出N個數據(maxmemory-samples)組成候選集,將lru最小的數據淘汰,(注意是隨機,這是一種局部最優解,所以會有過期很久但是沒有刪除的數據,但是性能提高了)

當第二次要淘汰數據的時候,Redis會再選一批數據進入第一次選出來的那個候選集合(能進入候選集合的必須是lru值比當前候選集合里最小的lru值還小的數據),當數據量達到maxmemory-samples時,Redis會把lru值最小的數據淘汰.

2.不考慮LFU算法,其他算法如何選擇?

答案1:優先使用 allkeys-lru 策略。這樣,可以充分利用 LRU 這一經典緩存算法的優勢,把最近最常訪問的數據留在緩存中,提升應用的訪問性能。如果你的業務數據中有明顯的冷熱數據區分,我建議你使用 allkeys-lru 策略。

如果業務應用中的數據訪問頻率相差不大,沒有明顯的冷熱數據區分,建議使用 allkeys-random 策略,隨機選擇淘汰的數據就行。

如果你的業務中有置頂的需求,比如置頂新聞、置頂視頻,那么,可以使用 volatile-lru 策略,同時不給這些置頂數據設置過期時間。這樣一來,這些需要置頂的數據一直不會被刪除,而其他數據會在過期時根據 LRU 規則進行篩選。


答案2:先根據是否有始終會被頻繁訪問的數據(例如置頂消息),來選擇淘汰數據的候選集,也就是決定是針對所有數據進行淘汰,還是針對設置了過期時間的數據進行淘汰。候選數據集范圍選定后,建議優先使用 LRU 算法,也就是,allkeys-lru 或 volatile-lru 策略。

當然,設置緩存容量的大小也很重要,我的建議是:結合實際應用的數據總量、熱數據的體量,以及成本預算,把緩存空間大小設置在總數據量的 15% 到 30% 這個區間就可以。

3.哪些數據要被淘汰?

要看采用哪種淘汰策略.

4.被淘汰的數據怎樣處理?

一般來說,如果是干凈的數據直接處理,如果是臟數據則寫回數據庫,但是Redis規定只要是淘汰的數據一定會被刪除.

所以一般對緩存的修改,要在修改時寫入數據庫.

5.怎樣判斷數據是干凈數據還是臟數據?

臟數據是曾經修改過,和數據庫的數據不一樣.

6.當一個系統引入緩存時,需要面臨最大的問題就是,如何保證緩存和后端數據庫的一致性問題

最常見的3個解決方案分別是Cache Aside、Read/Write Throught和Write Back緩存更新策略。

1、Cache Aside策略:就是文章所講的只讀緩存模式。讀操作命中緩存直接返回,否則從后端數據庫加載到緩存再返回。寫操作直接更新數據庫,然后刪除緩存。這種策略的優點是一切以后端數據庫為准,可以保證緩存和數據庫的一致性。缺點是寫操作會讓緩存失效,再次讀取時需要從數據庫中加載。這種策略是我們在開發軟件時最常用的,在使用Memcached或Redis時一般都采用這種方案。

2、Read/Write Throught策略:應用層讀寫只需要操作緩存,不需要關心后端數據庫。應用層在操作緩存時,緩存層會自動從數據庫中加載或寫回到數據庫中,這種策略的優點是,對於應用層的使用非常友好,只需要操作緩存即可,缺點是需要緩存層支持和后端數據庫的聯動。

3、Write Back策略:類似於文章所講的讀寫緩存模式+異步寫回策略。寫操作只寫緩存,比較簡單。而讀操作如果命中緩存則直接返回,否則需要從數據庫中加載到緩存中,在加載之前,如果緩存已滿,則先把需要淘汰的緩存數據寫回到后端數據庫中,再把對應的數據放入到緩存中。這種策略的優點是,寫操作飛快(只寫緩存),缺點是如果數據還未來得及寫入后端數據庫,系統發生異常會導致緩存和數據庫的不一致。這種策略經常使用在操作系統Page Cache中,或者應對大量寫操作的數據庫引擎中。

7.操作緩存或數據庫發生異常時如何處理?例如緩存操作成功,數據庫操作失敗,或者反過來,還是有可能會產生不一致的情況。

解決方案是,根據業務設計好更新緩存和數據庫的先后順序來降低影響,或者給緩存設置較短的有效期來降低不一致的時間。如果需要嚴格保證緩存和數據庫的一致性,即保證兩者操作的原子性,這就涉及到分布式事務問題了,常見的解決方案就是我們經常聽到的兩階段提交(2PC)、三階段提交(3PC)、TCC、消息隊列等方式來保證了,方案也會比較復雜,一般用在對於一致性要求較高的業務場景中。

三.異常處理

緩存異常一般有4個問題分別是:

1.緩存中的數據和數據庫中的數據不一致問題;

數據不一致的原因有兩條:

刪除緩存值或更新數據庫失敗而導致數據不一致,你可以使用重試機制確保刪除或更新操作成功。

在刪除緩存值、更新數據庫的這兩步操作中,有其他線程的並發讀操作,導致其他線程讀取到舊值,應對方案是延遲雙刪

 

第一種原因

刪改:既要在數據庫刪改,又要在緩存中刪除,主要考慮的就是這種情況;

                                                                    圖片來自極客專欄《Redis核心技術與實戰》

解決方法是重試機制:

將要刪改的操作放入消息隊列,如下圖,如果刪除失敗,從消息隊列中取出,再去執行,如果多次失敗,就向業務層發送報錯信息;

                                                                     圖片來自極客專欄《Redis核心技術與實戰》

第二種原因

即使這兩個操作第一次執行時都沒有失敗,當有大量並發請求時,應用還是有可能讀到不一致的數據。

情況一:先刪除緩存,再更新數據庫。

                                                                      圖片來自極客專欄《Redis核心技術與實戰》

在線程 A 更新完數據庫值以后,我們可以讓它先 sleep 一小段時間,再進行一次緩存刪除操作。之所以要加上 sleep 的這段時間,就是為了讓線程 B 能夠先從數據庫讀取數據,再把缺失的數據寫入緩存,然后,線程 A 再進行刪除。所以,線程 A sleep 的時間,就需要大於線程 B 讀取數據再寫入緩存的時間。這個時間怎么確定呢?建議你在業務程序運行的時候,統計下線程讀數據和寫緩存的操作時間,以此為基礎來進行估算。這樣一來,其它線程讀取數據時,會發現緩存缺失,所以會從數據庫中讀取最新值。因為這個方案會在第一次刪除緩存值后,延遲一段時間再次進行刪除,所以我們也把它叫做“延遲雙刪”。

情況二:先更新數據庫值,再刪除緩存值。

如果線程 A 刪除了數據庫中的值,但還沒來得及刪除緩存值,線程 B 就開始讀取數據了,那么此時,線程 B 查詢緩存時,發現緩存命中,就會直接從緩存中讀取舊值。不過,在這種情況下,如果其他線程並發讀緩存的請求不多,那么,就不會有很多請求讀取到舊值。而且,線程 A 一般也會很快刪除緩存值,這樣一來,其他線程再次讀取時,就會發生緩存缺失,進而從數據庫中讀取最新值。所以,這種情況對業務的影響較小。

 

                                                                       圖片來自極客專欄《Redis核心技術與實戰》

總結:

緩存和數據庫不一致的問題。針對這個問題,我們可以分成讀寫緩存和只讀緩存兩種情況進行分析。對於讀寫緩存來說,如果我們采用同步寫回策略,那么可以保證緩存和數據庫中的數據一致。只讀緩存的情況比較復雜,我總結了一張表,以便於你更加清晰地了解數據不一致的問題原因、現象和應對方案。

2.緩存雪崩;

表現:大量的應用請求無法在 Redis 緩存中進行處理,緊接着,應用將大量請求發送到數據庫層,導致數據庫層的壓力激增。

誘發原因:1.大量緩存數據同時失效;2.實例宕機

解決方法:

對於第一種原因:

1.盡量設置過期時間不要在同一時間,如果必須在同一時間,可以對時間做微調(給時間加一個很小的隨機數);

2.服務降級,如果訪問非核心數據,暫時停止從緩存種查詢這些數據,返回預定義的信息;如果是核心數據,可以查緩存或者數據庫;

對於第二種原因:

1.服務熔斷或者請求限流

服務熔斷就是暫停業務應用對緩存接口的訪問,就是請求不會到達redis實例,而是直接返回,等redis實例恢復之后再允許訪問

請求限流就是在業務系統的請求入口前端控制每秒進入系統的請求數,避免過多請求被發送到數據庫;

2.提前預防,用主從方式的redis緩存高可用集群,主庫宕機之后從庫可以繼續提供緩存服務;

3.緩存擊穿;

表現:緩存擊穿是指,針對某個訪問非常頻繁的熱點數據的請求,無法在緩存中進行處理,緊接着,訪問該數據的大量請求,一下子都發送到了后端數據庫,導致了數據庫壓力激增,會影響數據庫處理其他請求。

誘發原因:熱點數據過期失效

解決方法:對於訪問頻繁的熱點數據,不設置過期時間

4.緩存穿透;

表現:緩存穿透是指要訪問的數據既不在 Redis 緩存中,也不在數據庫中,導致請求在訪問緩存時,發生緩存缺失,再去訪問數據庫時,發現數據庫中也沒有要訪問的數據。

誘發原因:

業務層誤操作:緩存中的數據和數據庫中的數據被誤刪除了,所以緩存和數據庫中都沒有數據;

惡意攻擊:專門訪問數據庫中沒有的數據。

解決方法:

1.緩存空值或者缺省值

2.使用布隆過濾器判斷數據是否存在

3.在請求入口的前端進行請求檢查

總結:

 

                                                                            圖片來自極客專欄《Redis核心技術與實戰》

 

盡量使用預防式方案:

緩存雪崩,合理地設置數據過期時間,以及搭建高可靠緩存集群;

緩存擊穿,在緩存訪問非常頻繁的熱點數據時,不要設置過期時間;

緩存穿透,提前在入口前端實現惡意請求檢測,或者規范數據庫的數據刪除操作,避免誤刪除。

擴展:緩存污染

什么是緩存污染?

在一些場景下,有些數據被訪問的次數非常少,甚至只會被訪問一次。當這些數據服務完訪問請求后,如果還繼續留存在緩存中的話,就只會白白占用緩存空間。這種情況,就是緩存污染。

怎樣解決緩存污染?

解決緩存污染就是將不會再被訪問的數據從緩存里刪除,而刪除哪些又要看淘汰策略

LRU策略可以解決,但是LRU策略在掃描式單次查詢時會造成緩存污染,所以redis增加了LFU策略;

volatile-random 和 allkeys-random 是隨機選擇數據進行淘汰,無法把不再訪問的數據篩選出來,可能會造成緩存污染。如果業務層明確知道數據的訪問時長,可以給數據設置合理的過期時間,再設置 Redis 緩存使用 volatile-ttl 策略。當緩存寫滿時,剩余存活時間最短的數據就會被淘汰出緩存,避免滯留在緩存中,造成污染。

當我們使用 LRU 策略時,由於 LRU 策略只考慮數據的訪問時效,對於只訪問一次的數據來說,LRU 策略無法很快將其篩選出來。而 LFU 策略在 LRU 策略基礎上進行了優化,在篩選數據時,首先會篩選並淘汰訪問次數少的數據,然后針對訪問次數相同的數據,再篩選並淘汰訪問時間最久遠的數據。

在具體實現上,相對於 LRU 策略,Redis 只是把原來 24bit 大小的 lru 字段,又進一步拆分成了 16bit 的 ldt 和 8bit 的 counter,分別用來表示數據的訪問時間戳和訪問次數。為了避開 8bit 最大只能記錄 255 的限制,LFU 策略設計使用非線性增長的計數器來表示數據的訪問次數。

在實際業務應用中,LRU 和 LFU 兩個策略都有應用。LRU 和 LFU 兩個策略關注的數據訪問特征各有側重,LRU 策略更加關注數據的時效性,而 LFU 策略更加關注數據的訪問頻次。通常情況下,實際應用的負載具有較好的時間局部性,所以 LRU 策略的應用會更加廣泛。但是,在掃描式查詢的應用場景中,LFU 策略就可以很好地應對緩存污染問題了,建議你優先使用。

此外,如果業務應用中有短時高頻訪問的數據,除了 LFU 策略本身會對數據的訪問次數進行自動衰減以外,我再給你個小建議:你可以優先使用 volatile-lfu 策略,並根據這些數據的訪問時限設置它們的過期時間,以免它們留存在緩存中造成污染。

四.擴展機制

大內存 Redis 實例的潛在問題內存快照 RDB 生成和恢復效率低,以及主從節點全量同步時長增加、緩沖區易溢出。

內存快照 RDB 生成和恢復效率低,以及主從節點全量同步時長增加、緩沖區易溢出。

所以可以使用SSD,SSD成本低,內存大,訪問速度快。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM