Redis 高並發帶來的一些問題


前言

本文講述Redis在遇到高並發時的一些問題。即遇到大量請求時需要思考的點,如緩存穿透 緩存擊穿 緩存雪崩 熱key處理。一般中小型傳統軟件企業,很難碰到這個問題。如果有大並發的項目,流量有幾百萬左右。這些要深刻考慮。

1. 緩存穿透和緩存擊穿

簡單的講就是如果該數據原本就不存在,那么就會發生緩存穿透;如果緩存內容因為各種原因失效,那么就會發生緩存擊穿。具體一點來說,如果緩存中不存在需要查詢的內容,一般情況下需要再深入一層進行查詢,一般為不能承受壓力的關系型數據庫(承壓能力為緩存的1%,甚至更低),如果數據庫中不存在,則叫做緩存穿透;反之,如果數據庫中存在這個數據,則叫做緩存擊穿。這種查詢在流量不高的情況下,不會出現問題,如果查詢數據庫的流量過高,尤其是數據庫中不存在的情況下,嚴重時會導致數據庫不可用,連帶影響使用數據庫的其他業務,本業務也有很大的可能性受到影響。

緩存穿透指緩存中數據不存在,然后大量請求查數據庫導致數據庫異常。緩存沒有數據,數據庫也有數據,穿透兩層。

緩存擊穿指緩存中數據不存在,數據庫中有此數據,請求穿透一層。

1.1 緩存穿透

解決方案如下

1.1.1 空值緩存

既然該數據本身就不存在,最簡單粗暴的方式就是直接將不存在的值定義為空(視具體業務和緩存的方式定義為null或者””)。具體方式是每次查詢完數據庫,我們可以將key在緩存中設置對應的值為空,短期內再次查詢這個key的時候就不用查詢數據庫了。

為了系統的最終一致性,這些key必須設置過期時間,或者必須存在更新方式,防止這個key的數據后期真實存在,但該key始終為空,導致數據不一致的情況出現。

缺點:如果key數量巨大且分散無任何規律,就會浪費大量緩存空間,並且不能抗住瞬時流量沖擊(尤其是遇到惡意的攻擊的時候,有可能將緩存空間打爆,影響范圍更大),需要額外配置降級開關(查詢數據庫的開關或者限流),這時本方案就顯得沒想象的那么美好。針對不能抗住瞬時流量的情況,常見的處理方式是使用計數器,對不存在的key進行計數,當某個key在一定時間達到一定的量級,就查詢一次數據庫,按照數據庫的返回值對key進行緩存。未達指定閾值數量之前,按照商定的空值返回。

應用場景:key全集數據數據量級較小,並且完全可預測,可以通過提前填充的方式直接將數據緩存。

1.1.2 布隆過濾器(BloomFilter)

提供一個能迅速判斷請求是否有效的攔截機制,比如,利用布隆過濾器,內部維護一系列合法有效的key。迅速判斷出,請求所攜帶的Key是否合法有效。如果不合法,則直接返回。

實際應用中,Google BigTable,Apache HBbase 和 Apache Cassandra 使用布隆過濾器減少對不存在的行和列的查找。

高效的hash算法:建議的算法包括MurmurHash、Fnv的穩定高效的算法。

1.2 緩存擊穿

解決方案如下:

1.2.1 利用互斥鎖

利用互斥鎖,緩存失效的時候,先去獲得鎖,得到鎖了,再去請求數據庫。沒得到鎖,則休眠一段時間重試

這是比較常見的做法,是在緩存失效的時候,不是立即去查詢數據庫,先搶互斥鎖(比如Redis的SETNX一個mutex key),當操作返回成功時(即獲取到互斥鎖),再進行查詢數據庫的操作並回設緩存;否則,就重試整個獲取緩存的方法或者直接返回空。

1.2.3 異步構建緩存

采用異步更新策略,無論key是否取到值,都直接返回。value值中維護一個緩存失效時間,緩存如果過期,異步起一個線程去讀數據庫,更新緩存。需要做緩存預熱(項目啟動前,先加載緩存)操作。

當緩存失效時,不是立刻去查詢數據庫,而是先創建緩存更新的異步任務,然后直接返回空值。這種做法不會阻塞當前線程,並且對於數據庫的壓力基本可控,但犧牲了整體數據的一致性。從實際的使用看,這種方法對於性能非常友好,唯一不足的就是構建緩存時候,所有查詢返回的內容均為空值,但是對於一致性要求不高的互聯網功能來說這個還是可以忍受。

2. 緩存雪崩

緩存雪崩,即緩存同一時間大面積的失效,這個時候又來了一波請求,結果請求都懟到數據庫上,從而導致數據庫連接異常。

(一)給緩存的失效時間,加上一個隨機值,避免集體失效,但是不能徹底規避。

(二)使用互斥鎖,但是該方案吞吐量明顯下降了。

(三)雙緩存。我們有兩個緩存,緩存A和緩存B。緩存A的失效時間為20分鍾,緩存B不設失效時間。自己做緩存預熱操作。然后細分以下幾個小點

  • I 從緩存A讀數據庫,有則直接返回
  • II A沒有數據,直接從B讀數據,直接返回,並且異步啟動一個更新線程。
  • III 更新線程同時更新緩存A和緩存B。

3. 熱key

熱key問題說來也很簡單,就是瞬間有幾十萬的請求去訪問redis上某個固定的key,從而壓垮緩存服務的情情況。

3.1 怎么發現熱key

方法一:憑借業務經驗,進行預估哪些是熱key

其實這個方法還是挺有可行性的。比如某商品在做秒殺,那這個商品的key就可以判斷出是熱key。缺點很明顯,並非所有業務都能預估出哪些key是熱key。

方法二:在客戶端進行收集

這個方式就是在操作redis之前,加入一行代碼進行數據統計。那么這個數據統計的方式有很多種,也可以是給外部的通訊系統發送一個通知信息。缺點就是對客戶端代碼造成入侵。

方法三:在Proxy層做收集

有些集群架構是下面這樣的,Proxy可以是Twemproxy,是統一的入口。可以在Proxy層做收集上報,但是缺點很明顯,並非所有的redis集群架構都有proxy。

graph LR

clinet-->proxy

proxy-->redis1
proxy-->redis2
proxy-->redis3

方法四:用redis自帶命令

(1)monitor命令,該命令可以實時抓取出redis服務器接收到的命令,然后寫代碼統計出熱key是啥。當然,也有現成的分析工具可以給你使用,比如redis-faina。但是該命令在高並發的條件下,有內存增暴增的隱患,還會降低redis的性能。

(2)hotkeys參數,redis 4.0.3提供了redis-cli的熱點key發現功能,執行redis-cli時加上–hotkeys選項即可。但是該參數在執行的時候,如果key比較多,執行起來比較慢。

方法五:自己抓包評估

Redis客戶端使用TCP協議與服務端進行交互,通信協議采用的是RESP。自己寫程序監聽端口,按照RESP協議規則解析數據,進行分析。缺點就是開發成本高,維護困難,有丟包可能性。

以上五種方案,各有優缺點。根據自己業務場景進行抉擇即可。那么發現熱key后,如何解決呢?

3.2 如何解決

(1) 利用二級緩存

比如利用ehcache,或者一個HashMap都可以。在你發現熱key以后,把熱key加載到系統的JVM中。
針對這種熱key請求,會直接從jvm中取,而不會走到redis層。
假設此時有十萬個針對同一個key的請求過來,如果沒有本地緩存,這十萬個請求就直接懟到同一台redis上了。
現在假設,你的應用層有50台機器,OK,你也有jvm緩存了。這十萬個請求平均分散開來,每個機器有2000個請求,會從JVM中取到value值,然后返回數據。避免了十萬個請求懟到同一台redis上的情形。

(2) 備份熱key

這個方案主要防止熱key放在一台redis服務器中,把熱key服務到集群中的多台服務器中。根據redis集群數量構建一個新的key,判斷這個key不存在后,把熱key數據復制到這個新key上,這個新key也冗余到了其他redis集群中,下次取的時候計算方式是新key方式,取到了。

熱key.png

有辦法在項目運行過程中,自動發現熱key,然后程序自動處理么?

(1)監控熱key

(2)通知系統做處理

監控熱key的方式上面有說到,監控服務監控到熱key后通過手段(如zk)通知各個業務系統緩存熱key。如下圖所示
熱key實現

4. 並發競爭key

這個問題大致就是,同時有多個子系統去set一個key。這個時候要注意什么呢?大家思考過么。需要說明一下,博主提前百度了一下,發現答案基本都是推薦用redis事務機制。博主不推薦使用redis的事務機制。因為我們的生產環境,基本都是redis集群環境,做了數據分片操作。你一個事務中有涉及到多個key操作的時候,這多個key不一定都存儲在同一個redis-server上。因此,redis的事務機制,十分雞肋。

解決方案:

(1)如果對這個key操作,不要求順序
這種情況下,准備一個分布式鎖,大家去搶鎖,搶到鎖就做set操作即可,比較簡單。

(2)如果對這個key操作,要求順序
假設有一個key1,系統A需要將key1設置為valueA,系統B需要將key1設置為valueB,系統C需要將key1設置為valueC.
期望按照key1的value值按照 valueA-->valueB-->valueC的順序變化。這種時候我們在數據寫入數據庫的時候,需要保存一個時間戳。假設時間戳如下

系統A key 1 {valueA  3:00}
系統B key 1 {valueB  3:05}
系統C key 1 {valueC  3:10}

那么,假設這會系統B先搶到鎖,將key1設置為{valueB 3:05}。接下來系統A搶到鎖,發現自己的valueA的時間戳早於緩存中的時間戳,那就不做set操作了。加版本號的方式。以此類推。

其他方法,比如利用隊列,將set方法變成串行訪問也可以。總之,靈活變通。

References


免責聲明!

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



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