使用緩存集群的時候,最怕的就是熱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分彌足珍貴。"