使用redis可能出現的問題

2021-10-08 21:53:09 字數 4433 閱讀 2683

首先我們應該先明確快取處理的流程

前台請求,後台先從快取中取資料,取到直接返回結果,取不到時從資料庫中取,資料庫取到更新快取,並返回結果,資料庫也沒取到,那直接返回空結果.

[1] 快取雪崩

快取由於某些原因(比如 宕機、cache服務掛了或者不響應)整體失效了,導致大量請求到達後端資料庫,從而導致資料庫崩潰,整個系統崩潰,發生災難.

[2] 快取擊穿

快取擊穿是指快取中沒有但資料庫中有的資料.如果一些key被高併發訪問**,恰好在這個時間點某個key快取過期,從而導致了大量請求達到資料庫**,,這就導致資料庫中併發的去執行了很多不必要的查詢操作,從而導致巨大衝擊和壓力.

[3] 快取穿透

是指快取和資料庫中都沒有的資料,而使用者不斷發起請求,導致請求都打到了資料庫上,導致資料庫異常.

[4] 三種問題區別記憶方法

在乙個高頻訪問的應用系統中,每次使用者的請求需要去db中獲取資料,會對資料庫造成很大的壓力、容易導致資料庫的奔潰.所以才會出現快取來分擔一部分的資料庫的壓力. 但是使用快取也帶來了一系列問題:

打個比方,資料庫是人,快取是防彈衣,子彈是執行緒,本來防彈衣是防止子彈打到人身上的,但是當防彈衣裡面沒有防彈的物質時,子彈就會穿過它打到人身上.

快取雪崩(被霰彈槍打)"雪崩"對應的是大量請求,快取某一時間失效了,然後大量請求到達後端資料庫,從而導致資料庫崩潰.

解決方法:避免同時過期,構建多級快取,部署多個redis例項。

快取擊穿(被狙擊槍多次打一點)"擊穿"對應的是redis某點被(key)擊穿。如果一些key被高併發訪問,恰好在這個時間點某個key快取過期,從而導致了大量請求達到資料庫,,這就導致資料庫中併發的去執行了很多不必要的查詢操作,從而導致巨大衝擊和壓力.

解決方法:加互斥鎖,永不過期.

快取穿透(被巴雷特打)"穿透"最為嚴重,指的是快取和資料庫被穿透,都穿的透透的了。是指快取和資料庫中都沒有的資料,而使用者不斷發起請求,導致請求都打到了資料庫上,導致資料庫異常.這時的使用者很可能是攻擊者.

解決方法:快取空值,布隆過濾器.

[5] 資料庫和快取的雙寫一致性問題

高併發請求下很容易導致資料不一致的問題,如果你的業務需要保證資料的強一致性,那麼建議不要使用快取.在資料庫中和快取資料的刪除或者寫入過程中,如果有失敗的情況,會導致資料的不一致.

解決辦法:

雙刪延時的解決辦法.可以先刪除快取資料,然後再更新資料庫資料,最後再隔固定的時間再次刪除快取.

更新資料庫產生的binlog訂閱(使用canal).將有變化的key記錄下來,並且嘗試去不斷的去刪除快取(如果上次刪除快取失敗)

快取不一致詳解:

最初級的快取不一致問題及解決方案

問題:先修改資料庫,再刪除快取.如果刪除快取失敗了,那麼會導致資料庫中是新資料,快取中是舊資料,資料就出現了不一致.

解決思路:先刪除快取,再修改資料庫.如果資料庫修改失敗了,那麼資料庫中是舊資料,快取中是空的,那麼資料不會不一致.因為讀的時候快取沒有,則讀資料庫中舊資料,然後更新到快取中.

比較複雜的資料不一致問題分析

資料發生了變更,先刪除了快取,然後要去修改資料庫,此時還沒修改.乙個請求過來,去讀快取,發現快取空了,去查詢資料庫,查到了修改前的舊資料,放到了快取中.隨後資料變更的程式完成了資料庫的修改.完了,資料庫和快取中的資料不一樣了…

為什麼上億流量高併發場景下,快取會出現這個問題?

只有在對乙個資料在併發的進行讀寫的時候,才可能會出現這種問題.其實如果說你的併發量很低的話,特別是讀併發很低,每天訪問量就 1 萬次,那麼很少的情況下,會出現剛才描述的那種不一致的場景.但是問題是,如果每天的是上億的流量,每秒併發讀是幾萬,每秒只要有資料更新的請求,就可能會出現上述的資料庫+快取不一致的情況.

解決方案如下:

更新資料的時候,根據資料的唯一標識,將操作路由之後,傳送到乙個 jvm 內部佇列中.讀取資料的時候,如果發現資料不在快取中,那麼將重新讀取資料+更新快取的操作,根據唯一標識路由之後,也傳送同乙個 jvm 內部佇列中.

乙個佇列對應乙個工作執行緒,每個工作執行緒序列拿到對應的操作,然後一條一條的執行.這樣的話,乙個資料變更的操作,先刪除快取,然後再去更新資料庫,但是還沒完成更新.此時如果乙個讀請求過來,讀到了空的快取,那麼可以先將快取更新的請求傳送到佇列中,此時會在佇列中積壓,然後同步等待快取更新完成.

這裡有乙個優化點,乙個佇列中,其實多個更新快取請求串在一起是沒意義的,因此可以做過濾,如果發現佇列中已經有乙個更新快取的請求了,那麼就不用再放個更新請求操作進去了,直接等待前面的更新操作請求完成即可.

如果請求還在等待時間範圍內,不斷輪詢發現可以取到值了,那麼就直接返回;如果請求等待的時間超過一定時長,那麼這一次直接從資料庫中讀取當前的舊值.

高併發的場景下,該解決方案要注意的問題:

由於讀請求進行了非常輕度的非同步化,所以一定要注意讀超時的問題,每個讀請求必須在超時時間範圍內返回.

該解決方案,最大的風險點在於說,可能資料更新很頻繁,導致佇列中積壓了大量更新操作在裡面,然後讀請求會發生大量的超時,最後導致大量的請求直接走資料庫.務必通過一些模擬真實的測試,看看更新資料的頻率是怎樣的.

另外一點,因為乙個佇列中,可能會積壓針對多個資料項的更新操作,因此需要根據自己的業務情況進行測試,可能需要部署多個服務,每個服務分攤一些資料的更新操作.如果乙個記憶體佇列裡居然會擠壓 100 個商品的庫存修改操作,每隔庫存修改操作要耗費 10ms 去完成,那麼最後乙個商品的讀請求,可能等待 10 * 100 = 1000ms = 1s 後,才能得到資料,這個時候就導致讀請求的長時阻塞.

一定要做根據實際業務系統的運**況,去進行一些壓力測試,和模擬線上環境,去看看最繁忙的時候,記憶體佇列可能會擠壓多少更新操作,可能會導致最後乙個更新操作對應的讀請求,會 hang 多少時間,如果讀請求在 200ms 返回,如果你計算過後,哪怕是最繁忙的時候,積壓 10 個更新操作,最多等待 200ms,那還可以的.

如果乙個記憶體佇列中可能積壓的更新操作特別多,那麼你就要加機器,讓每個機器上部署的服務例項處理更少的資料,那麼每個記憶體佇列中積壓的更新操作就會越少.

其實根據之前的專案經驗,一般來說,資料的寫頻率是很低的,因此實際上正常來說,在佇列中積壓的更新操作應該是很少的.像這種針對讀高併發、讀快取架構的專案,一般來說寫請求是非常少的,每秒的 qps 能到幾百就不錯了.

我們來實際粗略測算一下.

如果一秒有 500 的寫操作,如果分成 5 個時間片,每 200ms 就 100 個寫操作,放到 20 個記憶體佇列中,每個記憶體佇列,可能就積壓 5 個寫操作.每個寫操作效能測試後,一般是在 20ms 左右就完成,那麼針對每個記憶體佇列的資料的讀請求,也就最多 hang 一會兒,200ms 以內肯定能返回了.

經過剛才簡單的測算,我們知道,單機支撐的寫 qps 在幾百是沒問題的,如果寫 qps 擴大了 10 倍,那麼就擴容機器,擴容 10 倍的機器,每個機器 20 個佇列.

這裡還必須做好壓力測試,確保恰巧碰上上述情況的時候,還有乙個風險,就是突然間大量讀請求會在幾十毫秒的延時 hang 在服務上,看服務能不能扛的住,需要多少機器才能扛住最大的極限情況的峰值.

但是因為並不是所有的資料都在同一時間更新,快取也不會同一時間失效,所以每次可能也就是少數資料的快取失效了,然後那些資料對應的讀請求過來,併發量應該也不會特別大.

可能這個服務部署了多個例項,那麼必須保證說,執行資料更新操作,以及執行快取更新操作的請求,都通過 nginx 伺服器路由到相同的服務例項上.

比如說,對同乙個商品的讀寫請求,全部路由到同一臺機器上.可以自己去做服務間的按照某個請求引數的 hash 路由,也可以用 nginx 的 hash 路由功能等等.

萬一某個商品的讀寫請求特別高,全部打到相同的機器的相同的佇列裡面去了,可能會造成某台機器的壓力過大.就是說,因為只有在商品資料更新的時候才會清空快取,然後才會導致讀寫併發,所以其實要根據業務系統去看,如果更新頻率不是太高的話,這個問題的影響並不是特別大,但是的確可能某些機器的負載會高一些.

遞迴可能出現的效能問題

遞迴演算法的 很簡潔。但同時也存在缺點。遞迴由於函式要呼叫自身,而函式呼叫是有時間和空間的消耗的。每一次函式呼叫,都需要在記憶體棧中分配空間以儲存引數 返回位址及臨時變數,而且往棧裡壓入資料和彈出資料都需要時間。遞迴有可能很多計算都是重複的,從而對效能帶來很大的負面影響。遞迴的本質是把乙個問題分解成...

Redis作為快取可能會出現的問題及解決方案

redis是個大話題,只要是去面試j a開發,幾乎必問。基礎一點的問redis是什麼東西?用來做什麼?redis支援哪些資料型別?redis的效能為什麼那麼好?複雜一點的就會問到快取穿透 快取擊穿 快取雪崩等問題。而我在面試的時候也被問到了redis為什麼用來做快取的問題。所以我覺得很有必要總結一下...

寫R包可能出現的問題

最近學習寫r包,參考部落格 如何快速寫乙個r包。根據該部落格教程寫r包非常簡單,補充我遇到的兩個小問題。寫入函式前需要先載入所寫的包 libray 所寫的包 the existing namespace file was not generated by roxygen2 自動生成的namespac...