如何使用 Redis 快取

ZhanLi發表於2022-04-20

如何使用 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中快取如何使用/

相關文章