如何使用 Redis 緩存


如何使用 Redis 緩存

前言

對於 Redis 來講,作為緩存使用,是我們在業務中經常使用的,這里總結下,Redis 作為緩存在業務中的使用。

旁路緩存

Cache Aside(旁路緩存)策略以數據庫中的數據為准,緩存中的數據是按需加載的。它可以分為讀策略和寫策略。

只讀緩存

只讀緩存 從緩存中讀取數據;如果緩存命中,則直接返回數據;如果緩存不命中,則從數據庫中查詢數據;查詢到數據后,將數據寫入到緩存中,並且返回給用戶。

如果需要對數據進行修改的時候,直接修改數據庫中的數據,然后刪除緩存中的舊數據。

只讀緩存的優點:

所有最新的數據都在數據庫中,數據不存在丟失的風險。

缺點:

每次修改數據,都會刪除緩沖,之后的請求會發生一次緩存缺失。

讀寫緩存

除了進行讀操作外,數據的修改操作也會發送到緩存中,直接在緩存中對數據進行修改。此時,得益於Redis的高性能訪問特性,數據的增刪改操作可以在緩存中快速完成,處理結果也會快速返回給業務應用,這就可以提升業務應用的響應速度。

當然 Redis 是內存數據庫,一旦掉電或宕機,內存中的數據就有可能存在丟失。

針對這種情況,一般會有兩種回寫策略:

  • 1、同步回寫;

寫請求發給緩存的同時,也會發給后端數據庫進行處理,等到緩存和數據庫都寫完數據,才給客戶端返回。這樣,即使緩存宕機或發生故障,最新的數據仍然保存在數據庫中,這就提供了數據可靠性保證。

不過,同步直寫會降低緩存的訪問性能。這是因為緩存中處理寫請求的速度是很快的,而數據庫處理寫請求的速度較慢。即使緩存很快地處理了寫請求,也需要等待數據庫處理完所有的寫請求,才能給應用返回結果,這就增加了緩存的響應延遲。

  • 2、異步回寫。

所有寫請求都先在緩存中處理。可以定時將緩存寫入到內存中,然后等到這些增改的數據要被從緩存中淘汰出來時,再次將它們寫回后端數據庫。這樣一來,處理這些數據的操作是在緩存中進行的,很快就能完成。只不過,如果發生了掉電,而它們還沒有被寫回數據庫,就會有丟失的風險了。

優點:

被修改的數據永遠在緩存中,不會發生緩存缺失,下次可以直接訪問,不在需要向數據庫中進行一次查詢。

缺點:

數據可能存在丟失的風險。

設置多大的緩存合適

緩存能夠提高響應速度,但是緩存的數量也不是越多越好?

1、大容量緩存是能帶來性能加速的收益,但是成本也會更高;

2、在一些場景中,比如秒殺,少量的緩存承擔的就是絕大部分的流量訪問。

系統的設計選擇是一個權衡的過程:大容量緩存是能帶來性能加速的收益,但是成本也會更高,而小容量緩存不一定就起不到加速訪問的效果。一般來說,建議把緩存容量設置為總數據量的15%到30%,兼顧訪問性能和內存空間開銷。

內存被寫滿了如何處理

Redis 中的內存被寫滿了,就會觸發內存淘汰機制了

具體參加內存淘汰機制

緩存經常遇到的問題

Redis 作為緩存,經常遇到的幾種情況:緩存中的數據和數據庫中的不一致;緩存雪崩;緩存擊穿和緩存穿透。

下面一一來探討下

1、緩存中的數據和數據庫中的不一致

數據一致性,通俗的理解就是,數據庫中的數據和緩沖中的數據完全一致就滿足一致性。不過對於只讀緩存,如果緩沖中沒有就去數據庫中查詢,這樣如果緩存中沒有數據,但是數據庫中的數據是最新的,最終也能滿足數據一致性。

所以總結下,一致性大致分成下面的兩種情況:

1、緩存中有數據,緩存中的數據和數據庫中的數據一樣;

2、緩存中沒有數據,數據庫中記錄了最新的數據。

下面分析下只讀緩存和讀寫緩存中的數據不一致情況

讀寫緩存

讀寫緩存有同步寫回和異步寫回兩種策略

同步寫回:緩存在新增修改的時候,也會同步數據到數據庫中,這樣總能保持緩存中的數據和數據庫中的一致;

異步寫回:緩存新增修改時候,先不寫回到數據庫中,定時或者緩存中數據淘汰的時候,再寫回到數據庫中。這種,如果 Redis 故障宕機了,沒有及時寫回數據到數據庫中,就會造成數據的不一致。

對於讀寫緩存,使用同步寫回的策略,能保證數據數據的一致性。不過,需要在業務應用中使用事務機制,來保證緩存和數據庫的更新具有原子性,也就是說,兩者要不一起更新,要不都不更新,返回錯誤信息,進行重試。否則,我們就無法實現同步直寫。

如果系統沒宕機,redis 系統正常的情況下,因為讀寫緩存,緩存中的數據是一直存在的,所以當修改數據的時候先修改緩存中的數據,這樣就算並發很大的情況下,因為緩存中的數據都是最新的,並且一直存在,這樣數據總能讀取到最新的數據。

只讀緩存

只讀緩存,如果數據新增,直接寫入到數據庫中,如果有數據修改刪除,也是直接操作數據庫不過緩存中的數據不會更新,而是直接刪除緩存中的數據。

這樣數據的更新操作之后,數據庫中的數據總是最新的,緩存中就會發生緩存缺失,此時就會從數據庫中讀取數據,然后再加載到緩存中,這樣緩存中的數據總能和數據庫中的數據一致。

只讀緩存在數據新增的時候,緩存中是沒有數據的,所以肯定是要從數據庫中加載,這種情況不存在數據不一致的情況。

在只讀緩存中,數據不一致的情況,發生在數據的更新刪除操作中,下面來一一分析下

刪改操作既要修改數據庫,同時還要刪除對應的緩存,如果這兩個操作的原子性無法得到保證,(一起操作成功,或者一起操作失敗),那么數據的一致性就得不到保證了。

來個異常的栗子

1、先修改數據庫,然后刪除緩存,但是刪除緩存失敗了;

刪除緩存失敗了,那么緩存中存在的就是舊值,這時候用戶的請求過來了,首先去緩存中查詢,這時候拿到的就是老舊的數據。

2、先刪除緩存,在修改數據庫,修改數據庫失敗了;

緩存刪除成功,數據庫修改失敗了,那么數據庫中存在的就是舊值,因為緩存已經被刪除了,這時候去緩存中查詢,發生了緩存的缺失,數據就會從數據庫中加載到緩存中,這時候讀取到也是老舊的數據。

針對這種問題如何解決呢?

上面出現異常的兩種場景,歸根到底,就是兩者操作的原子性沒有得到保證,所以可以借助於消息隊列實現最終的一致性。

使用 mq 解決分布式事務可參見分布式事務

這里的操作場景相對簡單一點,只要借助於 mq 的重試機制,保證第二步的操成功就可以了。

栗如:

1、先修改數據庫;

2、發送刪除緩存的消息到 mq 中;

3、下游收到刪除的消息,操作刪除緩存,如果失敗,借助於 mq 的重試機制,就能進行重試操作,直到成功。當然如果,重試多次還是失敗,我們需要記錄錯誤原因,然后通知業務方。

redis

那到底應該先刪除緩存還是先修改數據庫呢?這里我們再探討一下

1、先刪除緩存后修改數據庫

先刪除緩存,然后修改數據庫

如果數據庫的更新有延遲,那么這時候一個線程過來查詢該數據,因為緩存中已經刪除了,這時候發生了緩存的缺失,然后就回去數據庫中查詢,數據庫可能還沒有更新成功,就可能獲取到舊值。

如何解決呢

使用 延遲雙刪 策略

當數據庫被修改之后,線程 sleep 一段時間,然后再次刪除緩存,然緩存發生一次缺失,這樣下次的請求,就能把數據庫中最新的數據加載到緩存中。

redis

比如上面的這種情況,因為數據庫的更新可能存在延遲,所以時候線程2讀取到了數據庫的舊值,然后加載到了緩存中,這樣接下來的所有的查詢就都會讀取舊值

所以 線程1,通過延遲雙刪來處理這種情況

線程1,在 sleep 一段時間之后,刪除緩存,這樣就能使后續的緩存缺失,后續的查詢就能加載數據庫中最新的數據到緩存中。

不過 sleep 的時間需要大於,線程2,讀數據並且寫入數據到內存的時間,如果 sleep 時間過小,這時候線程2,的舊值還沒有寫入到緩存中,線程1,已經再次刪除了緩存,然后這時候線程2把舊值寫入,導致緩存中依然是舊數據。

redis.delKey(X)
db.update(X)
Thread.sleep(N)
redis.delKey(X)

當然,這在 sleep 的時間內,還是有一部分請求會讀取到舊值

2、先修改數據庫然后刪除緩存

先修改數據庫,然后刪除緩存

如果緩存刪除有延遲,那么這時候過來的請求,就會讀取到緩存中老舊的數據,不過緩存會馬上被刪除,只會有少部分的數據讀取到老舊的數據,對業務影響比較小。

經過對比,發現先修改數據庫然后在刪除緩存,對我們業務的影響比較小,同時也跟容易處理。

只讀緩存和讀寫緩存如何選擇

讀寫緩存對比只讀緩存

優點:緩存中一直會有數據,如果更新操作后會立即再次訪問,可以直接命中緩存,能夠降低讀請求對於數據庫的壓力。

缺點:如果更新后的數據,之后很少再被訪問到,會導致緩存中保留的不是最熱的數據,緩存利用率不高(只讀緩存中保留的都是熱數據)。

所以讀寫緩存比較適合用於讀寫相當的業務場景。

2、緩存雪崩

什么是緩存雪崩

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

緩存雪崩有兩種場景

1、大量緩存同時過期

如果有大量的緩存 key 設置了同樣的過期時間,如果這些緩存 key 過期了,同時有大量的請求,進來了,這些請求就會直接打到數據庫中,數據庫可能因為這些請求,導致數據庫壓力增大,嚴重的時候數據庫宕機。

如何解決呢?

1、避免給大量的過期鍵設置相同的過期時間,設計過期時間的時候,可以考慮加入一個業務上允許的過期隨機值;

2、服務降級,只有部分核心業務的請求,才會流轉到數據庫中,數據庫的壓力就會被大大減輕了;

  • 當業務應用訪問的是非核心數據(例如電商商品屬性)時,暫時停止從緩存中查詢這些數據,而是直接返回預定義信息、空值或是錯誤信息;

  • 當業務應用訪問的是核心數據(例如電商商品庫存)時,仍然允許查詢緩存,如果緩存缺失,也可以繼續通過數據庫讀取。

2、Redis 實例發生宕機

Redis 實例的宕機,緩存層就不能處理數據,最總流量都會流入到數據庫中

如何解決呢?

1、業務中實現服務熔斷或者請求限流機制;

  • 服務熔斷:如果監聽到發生了緩存雪崩,直接暫停對緩存服務的請求,但是這種對業務的影響比較大;

  • 服務限流:可以在入口做限流,不要讓所有的請求都流入到后端的服務中;

2、提前預防,搭建 Redis 的高可用集群;

  • 嘗試構建 Redis 的高可用集群,比如當某主節點掛掉了,集群能夠馬上重新選出新的主節點。例如哨兵機制

3、緩存擊穿

其實跟緩存雪崩有點類似,緩存雪崩是大規模的key失效,而緩存擊穿是一個熱點的Key,有大並發集中對其進行訪問,突然間這個Key失效了,導致大並發全部打在數據庫上,導致數據庫壓力劇增。這種現象就叫做緩存擊穿。

如何解決?

對於熱點 key 可以不設置過期時間,或者設置一個超過使用周期的過期時間,保證這個 key 在業務使用期間永遠存在。

4、緩存穿透

如果業務請求的緩存,既不在緩存中,也不再數據庫中,那么緩存將沒有用,所有的請求都會流入到數據庫中。

那么,緩存穿透會發生在什么時候呢?一般來說,有兩種情況。

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

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

如何解決?

1、緩存空值或缺省值;

一旦發生緩存穿透,在緩存中寫入一個業務中允許的空值,這樣緩存中有數據了,就避免了緩存穿透。

2、使用布隆過濾器;

使用布隆過濾器判斷下數據是否存在,數據如果不存在,就不向數據庫發起請求了。

布隆過濾器

3、在請求入口的前端進行請求檢測;

緩存穿透的一個原因是有大量的惡意請求訪問不存在的數據,所以,一個有效的應對方案是在請求入口前端,對業務系統接收到的請求進行合法性檢測,把惡意的請求(例如請求參數不合理、請求參數是非法值、請求字段不存在)直接過濾掉,不讓它們訪問后端緩存和數據庫。這樣一來,也就不會出現緩存穿透問題了。

緩存中的 hot key 和 big key

這兩種的處理方式可參見

Hot Key 和 big key

總結

對於緩存的使用,我們經常用到的有兩種1、只讀緩存;2、讀寫緩存;

只讀緩存,對比讀寫緩存

優點:緩存中一直會有數據,如果更新操作后會立即再次訪問,可以直接命中緩存,能夠降低讀請求對於數據庫的壓力。

缺點:如果更新后的數據,之后很少再被訪問到,會導致緩存中保留的不是最熱的數據,緩存利用率不高(只讀緩存中保留的都是熱數據)。

所以讀寫緩存比較適合用於讀寫相當的業務場景。

緩存在使用的過程中,會面臨緩存中的數據和數據庫中的不一致;緩存雪崩;緩存擊穿和緩存穿透,這些我們需要弄明白這些情況發生的額場景,然后再業務中一一去避免。

參考

【Redis核心技術與實戰】https://time.geekbang.org/column/intro/100056701
【Redis設計與實現】https://book.douban.com/subject/25900156/
【什么是緩存雪崩、緩存擊穿、緩存穿透】https://zhuanlan.zhihu.com/p/346651831
【詳解布隆過濾器的原理,使用場景和注意事項】https://zhuanlan.zhihu.com/p/43263751
【Redis 學習筆記】https://github.com/boilingfrog/Go-POINT/tree/master/redis
【如何使用Redis緩存】https://boilingfrog.github.io/2022/04/20/Redis中緩存如何使用/


免責聲明!

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



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