如何設計每秒十萬查詢的高併發架構?列克美食2019-06-11 12:05:49

如何設計每秒十萬查詢的高併發架構?

首先回顧一下,整個架構右側部分演進到的那個程度,其實已經非常的不錯了,因為百億流量,每秒十萬級併發寫入的場景,使用MQ限流削峰、分散式KV叢集給抗住了。

接著使用了計算與儲存分離的架構,各個Slave計算節點會負責提取資料到記憶體中,基於自研的SQL記憶體計算引擎完成計算。同時採用了資料動靜分離的架構,靜態資料全部快取,動態資料自動提取,保證了儘可能把網路請求開銷降低到最低。

另外,透過自研的分散式系統架構,包括資料分片和計算任務分散式執行、彈性資源排程、分散式高容錯機制、主備自動切換機制,都能保證整套系統的任意按需擴容,高效能、高可用的的執行。

下一步,咱們得來研究研究架構裡左側的部分了。

二、日益膨脹的離線計算結果

其實大家會注意到,在左側還有一個MySQL,那個MySQL就是用來承載實時計算結果和離線計算結果放在裡面彙總的。

終端的商家使用者就可以隨意的查詢MySQL裡的資料分析結果,支撐自己的決策,他可以看當天的資料分析報告,也可以看歷史上任何一段時期內的資料分析報告。

但是那個MySQL在早期可能還好一些,因為其實存放在這個MySQL裡的資料量相對要小一些,畢竟是計算後的一些結果罷了。但是到了中後期,這個MySQL可是也岌岌可危了。

如何設計每秒十萬查詢的高併發架構?

給大家舉一個例子,離線計算鏈路裡,如果每天增量資料是1000萬,那麼每天計算完以後的結果大概只有50萬,每天50萬新增資料放入MySQL,其實還是可以接受的。

但是如果每天增量資料是10億,那麼每天計算完以後的結果大致會是千萬級,你可以算他是計算結果有5000萬條資料吧,每天5000萬增量資料寫入左側的MySQL中,你覺得是啥感覺?

可以給大家說說系統當時的情況,基本上就是,單臺MySQL伺服器的磁碟儲存空間很快就要接近滿掉,而且單表資料量都是幾億、甚至十億的級別。

這種量級的單表資料量,你覺得使用者查詢資料分析報告的時候,體驗能好麼?基本當時一次查詢都是幾秒鐘的級別。很慢。

更有甚者,出現過使用者一次查詢要十秒的級別,甚至幾十秒,上分鐘的級別。很崩潰,使用者體驗很差,遠遠達不到付費產品的級別。

所以解決了右側的儲存和計算的問題之後,左側的查詢的問題也迫在眉睫。

新一輪的重構,勢在必行!

三、分庫分表 + 讀寫分離

首先就是老一套,分庫分表 + 讀寫分離,這個基本是基於MySQL的架構中,必經之路了,畢竟實施起來難度不是特別的高,而且速度較快,效果比較顯著。

整個的思路和之前第一篇文章(《大型系統架構演進之如何支撐百億級資料的儲存與計算》)講的基本一致。

說白了,就是分庫後,每臺主庫可以承載部分寫入壓力,單庫的寫併發會降低;其次就是單個主庫的磁碟空間可以降低負載的資料量,不至於很快就滿了;

而分表之後,單個數據表的資料量可以降低到百萬級別,這個是支撐海量資料以及保證高效能的最佳實踐,基本兩三百萬的單表資料量級還是合理的。

然後讀寫分離之後,就可以將單庫的讀寫負載壓力分離到主庫和從庫多臺機器上去,主庫就承載寫負載,從庫就承載讀負載,這樣避免單庫所在機器的讀寫負載過高,導致CPU負載、IO負載、網路負載過高,最後搞得資料庫機器宕機。

首先這麼重構一下資料庫層面的架構之後,效果就好的多了。因為單表資料量降低了,那麼使用者查詢的效能得到很大的提升,基本可以達到1秒以內的效果。

四、每秒10萬查詢的高併發挑戰

上面那套初步的分庫分表+讀寫分離的架構確實支撐了一段時間,但是慢慢的那套架構又暴露出來了弊端出來了,因為商家使用者都是開了資料分析頁面之後,頁面上有js指令碼會每隔幾秒鐘就傳送一次請求到後端來載入最新的資料分析結果。

此時就有一個問題了,漸漸的查詢MySQL的壓力越來越大,基本上可預見的範圍是朝著每秒10級別去走。

但是我們分析了一下,其實99%的查詢,都是頁面JS指令碼自動發出重新整理當日資料的查詢。只有1%的查詢是針對昨天以前的歷史資料,使用者手動指定查詢範圍後來查詢的。

但是現在的這個架構之下,我們是把當日實時資料計算結果(代表了熱資料)和歷史離線計算結果(代表了冷資料)都放在一起的,所以大家可以想象一下,熱資料和冷資料放在一起,然後對熱資料的高併發查詢佔到了99%,那這樣的架構還合理嗎?

當然不合理,

我們需要再次重構系統架構。

五、 資料的冷熱分離架構

針對上述提到的問題,很明顯要做的一個架構重構就是

冷熱資料分離。

也就是說,將今日實時計算出來的熱資料放在一個MySQL集群裡,將離線計算出來的冷資料放在另外一個MySQL集群裡。

然後開發一個數據查詢平臺,封裝底層的多個MySQL叢集,根據查詢條件動態路由到熱資料儲存或者是冷資料儲存。

透過這個步驟的重構,我們就可以有效的將熱資料儲存中單表的資料量降低到更少更少,有的單表資料量可能就幾十萬,因為將離線計算的大量資料結果從表裡剝離出去了,放到另外一個集群裡去。此時大家可想而知,效果當然是更好了。

因為熱資料的單表資料量減少了很多,當時的一個最明顯的效果,就是使用者99%的查詢都是針對熱資料儲存發起的,

效能從原來的1秒左右降低到了200毫秒以內

,使用者體驗提升,大家感覺更好了。

如何設計每秒十萬查詢的高併發架構?

六、自研Elasticsearch+HBase+純記憶體的查詢引擎

架構演進到這裡,看起來好像還不錯,但是其實問題還是很多。因為到了這個階段,系統遇到了另外一個較為嚴重的問題:冷資料儲存,如果完全用MySQL來承載是很不靠譜的。冷資料的資料量是日增長不斷增加,而且增速很快,每天都新增幾千萬。

因此你的MySQL伺服器將會面臨不斷的需要擴容的問題,而且如果為了支撐這1%的冷資料查詢請求,不斷的擴容增加高配置的MySQL伺服器,大家覺得靠譜麼?

肯定是不合適的!

要知道,大量分庫分表後,MySQL大量的庫和表維護起來是相當麻煩的,修改個欄位?加個索引?這都是一場麻煩事兒。

此外,因為對冷資料的查詢,一般都是針對大量資料的查詢,比如使用者會選擇過去幾個月,甚至一年的資料進行分析查詢,此時如果純用MySQL還是挺災難性的。

因為當時明顯發現,針對海量資料場景下,一下子查詢分析幾個月或者幾年的資料,效能是極差的,還是很容易搞成幾秒甚至幾十秒才出結果。

因此針對這個冷資料的儲存和查詢的問題,我們最終選擇了自研一套基於NoSQL來儲存,然後基於NoSQL+記憶體的SQL計算引擎。

具體來說,我們會將冷資料全部採用ES+HBase來進行儲存,ES中主要存放要對冷資料進行篩選的各種條件索引,比如日期以及各種維度的資料,然後HBase中會存放全量的資料欄位。

因為ES和HBase的原生SQL支援都不太好,因此我們直接自研了另外一套SQL引擎,專門支援這種特定的場景,就是基本沒有多表關聯,就是對單個數據集進行查詢和分析,然後支援NoSQL儲存+記憶體計算。

這裡有一個先決條件,就是如果要做到對冷資料全部是單表類的資料集查詢,必須要在冷資料進入NoSQL儲存的時候,全部基於ES和HBase的特性做到多表入庫關聯,進資料儲存就全部做成大寬表的狀態,將資料關聯全部上推到入庫時完成,而不是在查詢時進行。

對冷資料的查詢,我們自研的SQL引擎首先會根據各種where條件先走ES的分散式高效能索引查詢,ES可以針對海量資料高效能的檢索出來需要的那部分資料,這個過程用ES做是最合適的。

接著就是將檢索出來的資料對應的完整的各個資料欄位,從HBase裡提取出來,拼接成完成的資料。

然後就是將這份資料集放在記憶體裡,進行復雜的函式計算、分組聚合以及排序等操作。

上述操作,全部基於自研的針對這個場景的查詢引擎完成,底層基於Elasticsearch、HBase、純記憶體來實現。

七、實時資料儲存引入快取叢集

好了,到此為止,冷資料的海量資料儲存、高效能查詢的問題,就解決了。接著回過頭來看看當日實時資料的查詢,其實實時資料的每日計算結果不會太多,而且寫入併發不會特別特別的高,每秒上萬也就差不多了。

因此這個背景下,就是用MySQL分庫分表來支撐資料的寫入、儲存和查詢,都沒問題。

但是有一個小問題,就是說每個商家的實時資料其實不是頻繁的變更的,在一段時間內,可能壓根兒沒變化,因此不需要高併發請求,每秒10萬級別的全部落地到資料庫層面吧?要全都落地到資料庫層面,那可能要給每個主庫掛載很多從庫來支撐高併發讀。

因此這裡我們引入了一個快取叢集,實時資料每次更新後寫入的時候,都是寫資料庫叢集同時還寫快取叢集的,是雙寫的方式。

然後查詢的時候是優先從快取叢集來走,此時基本上90%以上的高併發查詢都走快取叢集了,然後只有10%的查詢會落地到資料庫叢集。

八、階段性總結

好了,到此為止,這個架構基本左邊也都重構完畢:

熱資料基於快取叢集+資料庫叢集來承載高併發的每秒十萬級別的查詢

冷資料基於ES+HBase+記憶體計算的自研查詢引擎來支撐海量資料儲存以及高效能查詢。

經實踐,整個效果非常的好。使用者對熱資料的查詢基本多是幾十毫秒的響應速度,對冷資料的查詢基本都是200毫秒以內的響應速度。

九、下一階段的展望

其實架構演進到這裡已經很不容易了,因為看似這麼一張圖,裡面涉及到無數的細節和技術方案的落地,需要一個團隊耗費至少1年的時間才能做到這個程度。

但是接下來,我們要面對的,就是

高可用的問題

,因為付費級的產品,我們必須要保證超高的可用性,99。99%的可用性,甚至是99。999%的可用性。

但是越是複雜的系統,越容易出現問題,對應的高可用架構就越是複雜無比