1. 程式人生 > 實用技巧 >常見快取問題處理-快取熱點key

常見快取問題處理-快取熱點key

參考:

https://blog.csdn.net/shipfei_csdn/article/details/103380618

https://www.cnblogs.com/rjzheng/p/10874537.html

快取熱點key的處理探討

使用快取叢集的時候,最怕的就是熱key、大value這兩種問題。熱key問題,指的就是快取叢集中的某個key在瞬間被數萬甚至十萬的併發請求打爆。大value問題,指的是某個key對應的value可能有gb級別的大小,導致查詢value的時候會引發網路相關的故障問題。這裡說一下熱key問題。

為什麼要使用快取叢集

簡單來說,假設你手頭上有個系統,它本身是叢集部署的,然後後面有一套快取叢集,這個叢集不管你用Redis Cluster,還是Memcached,或者是公司自研快取叢集,都可以。

那麼這套系統用快取叢集做什麼呢?很簡單,在快取放一些平時不怎麼變動的資料,然後使用者在查詢大量的、平時不怎麼變動的資料的時候,就可以直接訪問快取而不需要訪問資料庫了。快取叢集的併發能力很強,而且讀快取的效能也很高。舉個例子,假設每秒鐘有2萬的請求,但是其中的90%都是讀請求,那麼每秒鐘1.8萬的請求都是在讀一些不太變化的資料,而不是寫資料。此時,如果你把資料都放在資料庫裡,然後每秒鐘傳送2萬個請求到資料庫上讀寫資料,顯然是不合適的,因為如果要資料庫能承載每秒2萬個請求的話,很可能就需要搞分庫分表+讀寫分離。比如,你需要分出來3個主庫去承載每秒2000的寫入請求,然後每個主庫掛3個從庫,一個9個從庫去承載每秒1.8萬的讀請求。那麼這樣你就需要一共是12臺高配置的資料庫伺服器,成本非常高,而且很不合適。

因此,此時你就可以完全把平時不太變化的資料放在快取叢集裡,快取叢集可以採用2主2從,主節點用來寫入快取,從節點用來讀取快取。以快取叢集的效能,2個從節點完全可以用來承載每秒1.8萬的大量讀,然後3個主庫就只要承載每秒2000的寫請求和少量的其他讀就可以了。這樣,耗費的機器瞬間變成了4臺快取機器+3臺數據庫機器=7臺機器,比起前面的12臺機器減少了很大的資源開銷。事實上,快取是系統架構裡非常重要的一個組成部分。很多的時候,對於哪些很少變化但是大量高併發讀的資料,通過快取叢集來抗高併發讀,是非常合適的。

以上的機器數量、併發請求量只是一個簡單的示例,實際的情況要複雜得多,通過這個例子就能大概理解在系統中為什麼要使用快取叢集來承載讀寫請求。

20萬用戶同時訪問一個熱點快取的問題

做一個假設,現在有10個快取節點來抗大量的讀請求。正常情況下,讀請求應該是均勻地落在10個快取節點上的(負載均衡),那麼這10個快取節點,每秒承載1萬個請求是差不多的。然後我們來做一個假設,一個節點承載2萬個請求是極限,所以一般就限制一個節點正常承載1萬個請求就ok了,因為要稍微留一些buffer出來。所謂的熱點快取問題,就是突然因為莫名的原因,出現大量的使用者訪問同一條快取資料。舉個例子就是,某個明星突然宣佈跟某某結婚,這個時候就可能會引發短時間內每秒有數十萬的使用者去檢視這條結婚的新聞。假設這條新聞是一個快取,然後對應就是一個快取key,就存在一臺快取機器上,假設這時候有20萬個請求一起奔向這臺快取機器上的一個快取key上,就會引發熱點快取問題。

通過上圖就很明顯看出問題來了。我們剛才假設的是一個快取Slave節點最多每秒接受2萬的請求(當然了,實際的快取單機承載5萬~10萬的都請求也是可能的),此時每秒卻突然奔過來20萬的請求到這臺機器上,那麼這臺機器就會被這20萬請求弄宕機。一旦快取叢集開始出現機器的宕機,那麼讀請求發現讀不到資料,就會從資料庫裡面去提取原始資料,然後將這些資料放到剩餘的其他快取機器裡面去。但是接踵而來的每秒20萬的請求還是會接著壓垮其他的快取機器,周而復始,最終導致快取叢集全盤奔潰,引發系統的整體宕機。

基於流式計算技術的快取熱點自動發現

這裡關鍵的一點,就是對於這種熱點快取,系統需要能夠在熱點快取突然發生的時候,直接發現它,然後瞬間立馬實現毫秒級的自動負載均衡。那麼如何實現自動發現熱點快取問題呢?首先,一般出現快取熱點的時候,每秒併發肯定是很高的,可能每秒都幾十萬甚至上百萬的請求量過來,多是可能的。所以此時完全可以基於大資料領域的流式計算技術來進行實時資料訪問次數的統計,比如storm、spark streaming或flink,這些技術都是可以的。然後一旦在實時資料訪問次數統計的過程中,比如發現一秒之內,某條資料突然訪問次數超過了1000,就直接立刻把這條資料判定為是熱點資料,可以將這個發現出來的熱點資料寫入比如zookeeper中。當然,系統如何判定熱點資料可以根據自己的業務還有經驗值來。

那麼流式計算系統在進行資料訪問次數統計的時候,會不會也存在說單臺機器被請求每秒幾十萬次的問題呢?答案是否,因為流式計算技術,尤其是storm這種系統,可以做到同一條資料的請求過來,先分散在很多機器裡進行本地計算,最後再彙總區域性計算結果到一臺機器進行全域性彙總。所以幾十萬的請求可以先分散在比如100臺機器上,每臺機器統計了這條資料的幾千次請求。然後100條區域性計算好的結果彙總到一臺機器做全域性計算即可,所以基於流式計算技術來進行統計是不會有熱點問題的。

熱點快取自動載入為JVM本地快取

現在系統可以對zookeeper指定的熱點快取對應的znode進行監聽了,如果有變化,系統立馬就感知到了。這時,系統層就可以立馬把相關的快取資料從資料庫加載出來,然後直接放在自己系統內部的本地快取即可。這個本地快取,用ehcach,hashmap都可以,具體看業務需求,主要就是要將快取叢集裡的集中式快取直接變成每個系統自己本地實現的快取即可,每個系統自己本地是無法快取過多資料的。因為一般這種普通系統單例項,部署機器可能就是一個4核8G的機器,留給本地快取的空間是很少的,所以用來放這種熱點資料的本地快取是最合適的。

假設系統層叢集部署了100臺機器,這時這100臺機器瞬間在本地都會有一份熱點快取的副本。然後接下來對熱點快取的讀操作,直接系統本地快取都出來就會返回了,不需要再走快取叢集了。這樣的話,也不可能允許每秒20萬的讀請求到達快取機器的一臺機器上讀一個熱點快取了,而是變成100臺機器每臺機器承載數千個請求,這數千請求直接從機器的本地快取返回資料。

限流熔斷保護

除此之外,在每個系統內部,其實還應該專門加一個對熱點資料訪問的限流熔斷保護措施。每個系統例項的內部,都可以加一個熔斷保護機制,假設快取叢集最多每秒承載4萬讀請求,那麼一共有100個系統例項。這時候就要限制好,每個系統例項每秒最多請求快取叢集讀操作不超過400次,一超過就可以熔斷掉,不讓請求快取叢集,直接返回一個空白資訊,然後使用者稍後會自行再次重新重新整理頁面之類的。通過系統層自己直接加限流熔斷保護措施,就可以很好地保護後面的快取叢集、資料庫叢集之類的不會被打死。

總結

具體要不要在系統裡面實現這種複雜的快取熱點優化架構,要看系統有沒有這種場景。如果系統有熱點快取問題,那麼就要實現類似的複雜熱點快取支撐架構。但是如果沒有的話,也別過度設計,系統可能並不需要這麼複雜的架構,反而會成為拖累。

"人這一生,都是命。你的稟賦就決定了你90分的人生。別人的幫助可以決定9分,你自己的努力只佔1分。但是隻為了這1分,你就要拼盡全力,因為這1分是你唯一能夠爭取的,這1分彌足珍貴。"

【原創】談談redis的熱key問題如何解決

引言

講了幾天的資料庫系列的文章,大家一定看煩了,其實還沒講完。。。(以下省略一萬字)。
今天我們換換口味,來寫redis方面的內容,談談熱key問題如何解決。
其實熱key問題說來也很簡單,就是瞬間有幾十萬的請求去訪問redis上某個固定的key,從而壓垮快取服務的情情況。
其實生活中也是有不少這樣的例子。比如XX明星結婚。那麼關於XX明星的Key就會瞬間增大,就會出現熱資料問題。
ps:hot key和big key問題,大家一定要有所瞭解。
本文預計分為如下幾個部分

  • 熱key問題
  • 如何發現
  • 業內方案

正文

熱Key問題

上面提到,所謂熱key問題就是,突然有幾十萬的請求去訪問redis上的某個特定key。那麼,這樣會造成流量過於集中,達到物理網絡卡上限,從而導致這臺redis的伺服器宕機。
那接下來這個key的請求,就會直接懟到你的資料庫上,導致你的服務不可用。

怎麼發現熱key

方法一:憑藉業務經驗,進行預估哪些是熱key
其實這個方法還是挺有可行性的。比如某商品在做秒殺,那這個商品的key就可以判斷出是熱key。缺點很明顯,並非所有業務都能預估出哪些key是熱key。
方法二:在客戶端進行收集
這個方式就是在操作redis之前,加入一行程式碼進行資料統計。那麼這個資料統計的方式有很多種,也可以是給外部的通訊系統傳送一個通知資訊。缺點就是對客戶端程式碼造成入侵。
方法三:在Proxy層做收集
有些叢集架構是下面這樣的,Proxy可以是Twemproxy,是統一的入口。可以在Proxy層做收集上報,但是缺點很明顯,並非所有的redis叢集架構都有proxy。

方法四:用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後,如何解決呢?

如何解決

目前業內的方案有兩種
(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請求進來的時候,我們就在有備份的redis上隨機選取一臺,進行訪問取值,返回資料。
假設redis的叢集數量為N,步驟如下圖所示

注:不一定是2N,你想取3N,4N都可以,看要求。
虛擬碼如下

const M = N * 2
//生成隨機數
random = GenRandom(0, M)
//構造備份新key
bakHotKey = hotKey + “_” + random
data = redis.GET(bakHotKey)
if data == NULL {
    data = GetFromDB()
    redis.SET(bakHotKey, expireTime + GenRandom(0,5))
}

業內方案

OK,其實看完上面的內容,大家可能會有一個疑問。

煙哥,有辦法在專案執行過程中,自動發現熱key,然後程式自動處理麼?

嗯,好問題,那我們來講講業內怎麼做的。其實只有兩步
(1)監控熱key
(2)通知系統做處理
正巧,前幾天有贊出了一篇《有贊透明多級快取解決方案(TMC)》,裡頭也有提到熱點key問題,我們剛好藉此說明
(1)監控熱key
在監控熱key方面,有贊用的是方式二:在客戶端進行收集
在《有贊透明多級快取解決方案(TMC)》中有一句話提到

TMC 對原生jedis包的JedisPool和Jedis類做了改造,在JedisPool初始化過程中整合TMC“熱點發現”+“本地快取”功能Hermes-SDK包的初始化邏輯。

也就說人家改寫了jedis原生的jar包,加入了Hermes-SDK包。
那Hermes-SDK包用來幹嘛?
OK,就是做熱點發現本地快取
從監控的角度看,該包對於Jedis-Client的每次key值訪問請求,Hermes-SDK 都會通過其通訊模組將key訪問事件非同步上報給Hermes服務端叢集,以便其根據上報資料進行“熱點探測”。

當然,這只是其中一種方式,有的公司在監控方面用的是方式五:自己抓包評估
具體是這麼做的,先利用flink搭建一套流式計算系統。然後自己寫一個抓包程式抓redis監聽埠的資料,抓到資料後往kafka裡丟。
接下來,流式計算系統消費kafka裡的資料,進行資料統計即可,也能達到監控熱key的目的。

(2)通知系統做處理
在這個角度,有贊用的是上面的解決方案一:利用二級快取進行處理。
有贊在監控到熱key後,Hermes服務端叢集會通過各種手段通知各業務系統裡的Hermes-SDK,告訴他們:"老弟,這個key是熱key,記得做本地快取。"
於是Hermes-SDK就會將該key快取在本地,對於後面的請求。Hermes-SDK發現這個是一個熱key,直接從本地中拿,而不會去訪問叢集。

除了這種通知方式以外。我們也可以這麼做,比如你的流式計算系統監控到熱key了,往zookeeper裡頭的某個節點裡寫。然後你的業務系統監聽該節點,發現節點資料變化了,就代表發現熱key。最後往本地快取裡寫,也是可以的。

通知方式各種各樣,大家可以自由發揮。本文只是提供一個思路。

總結

希望通過本文,大家明白如何處理生產上遇到的熱key問題。