天天看點

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

作者:Civen

從最初開設《全解MySQL專欄》到現在,共計撰寫了二十個大章節詳細講到了MySQL各方面的進階技術點,從最初的資料庫架構開始,到SQL執行流程、庫表設計範式、索引機制與原理、事務與鎖機制剖析、日志與記憶體詳解、常用指令與進階特性、線上調優與故障排查.....,似乎涉及到了MySQL的方方面面。但到此為止就黔驢技窮了嗎?答案并非如此,以《MySQL特性篇》為分割線,整個MySQL專欄從此會進入“高可用”階段的分析,即從上篇之後會開啟MySQL的新内容,主要講述分布式、高可用、高性能方面的講解。

接下來的資料庫專欄内容,主要會講解不同高并發場景下的MySQL架構設計方案,也包括對于各類大流量/大資料該如何優雅的處理,也包括架構調整後帶來的後患又該如何解決?其中内容會涵蓋庫内分表、主從複制、讀寫分離、雙主熱備、垂直分庫、水準叢集、分庫分表實踐、分布式事務、分布式ID、資料一緻性探讨......等内容。

話歸正題,分庫分表這個概念基本上碰過資料庫的小夥伴都有聽說過,但很多小夥伴對這塊具體該如何落地并不清楚,是以接下來這篇會先闡述MySQL分庫分表的方法論,以及詳細講解分庫分表後産生的後患問題,但在此之前先送上一句話,請牢記:

不要為了分庫分表而分庫分表!!引入SOA架構中的一句話:架構不是一蹶而起的,而是慢慢演進的。隻有真正需要分庫分表來解決問題時,才去真正的做拆分,否則會導緻很多不必要的麻煩産生,這點在《阿裡Java開發規範手冊》中有明确的寫出:

一、為什麼需要分庫分表?

在講為什麼需要分庫分表之前,咱們先來講一個故事:在很久很久之前有一位名喚竹子的美男子,最初由于年幼并未娶妻生子,是以出行時都隻需要一匹馬來拉車,但随着年齡漸長,慢慢的開始娶妻納妾,每次出行時的人數也會直線增長,而之前負責拉車的那匹老馬卻苦不堪言,因為随着日子一天一天的過,自身的壓力也随之增加,終于有一天,老馬扛不住了,累到在了大街上。

随即竹子也慢慢發現了這個問題,由于每次出行的人口越來越多,是以老馬的能力無法滿足出行需求,這時竹子為了解決出行問題,是以花費重金托人從西域購入了一匹身強體壯的汗血寶馬,以此來尋求解決所遇到的困擾。

當商人将名貴的汗血寶馬交給竹子時,這匹馬的确比之前的老馬能力強太多太多了,拖動一輛承載幾人的馬車完全不在話下,同時竹子有了之前的前車之鑒,是以對其也格外看重,每天都吩咐人給寶馬喂好料、做保養......,但好景不長,随着時間推移,男人三妻四妾放在當時也并非罕事,是以這時這匹來自西域的汗血寶馬也對此無能為力,慢慢的出現乏力的情況。

此時竹子也再次察覺到了這個情況,于是再次找到當初幫忙購置汗血寶馬的商人,想要再花重金買入一匹能力更強、體力更盛的千裡馬!但商人卻道:“目前你手中的汗血寶馬已屬世間極品,想要找到比它更強且能代替它的少之又少,貴客您這需求恐怕老夫是難以完成咯”!

這時問題似乎陷入僵局,但随之大行商便道:“雖然我無法替您尋找到更為優良的馬匹,但我有一個萬全之策能解你燃眉之急”!那這個萬全之策到底是什麼呢?此時商人口中緩緩道出:“當一匹馬無法解決你的出行問題時,與其尋找更好的馬匹,為何不選擇用更多的馬匹來拉車呢”?

此時竹子一拍大腿,立馬稱贊道:所言極是,言之有理,于是大手一揮立馬又購置了六匹良馬,與之前的兩匹舊馬,組成了八匹馬拉車的馬隊,自從之後,竹子再也沒有遇到過出行問題。
MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

在上面這個故事中,大家應該能夠感受出來,當單匹馬無法拉動馬車時,不要試圖找到一匹更好的馬來代替,而是應該選擇使用多匹馬來拉車!這個故事的内在意義放在程式設計中同樣如此,對一個節點做性能優化、更新硬體配置就是再試圖尋找一匹更強壯的馬,但一匹馬的力量再強也是有限的,是以這時選擇使用更多的馬匹(伺服器/節點)來解決問題才是王道!

程式設計裡面有句話叫做:加一台伺服器的收益勝過千萬次調優,畢竟機器數量才是真理!

那麼接着咱們也回到問題本身,來一起聊一聊MySQL為什麼需要分庫分表?

1.1、請求數太高

在高并發情況下,大量讀寫請求落入資料庫處理,最終會導緻資料庫的活躍連接配接數增加,進而逼近甚至達到資料庫可承載活躍連接配接數的門檻值。在業務Service層來看就是,可用資料庫連接配接銳減甚至無連接配接可用,接下來面臨的就是并發量急劇增加、吞吐量嚴重下降、連接配接出現異常、資料庫時常當機、系統經常崩潰一系列後患問題。

1.2、資料查詢慢

  • 一、單表或單庫資料量過大,導緻資料檢索的效率直線降低。
  • 二、單庫整體并發連接配接數接近系統門檻值,進而導緻此請求擷取不到連接配接數,一直處于等待擷取連接配接的狀态。
  • 三、已經擷取但由于并發過高導緻CPU被打滿,就算SQL所查詢的表資料行很少,也同樣因為沒有CPU資源無法執行,是以一直處于阻塞狀态,最終出現查詢過慢的現象。

1.3、資料量太大

  • ①當一個庫的資料存儲量太大時,就算每張表的并發數不多,但是因為是海量資料,單庫中存在大量的資料表,每張表都有一部分并發請求,導緻最終單庫的連接配接數門檻值成為資料庫的瓶頸。
  • ②當一張表資料太多時,導緻單表查詢速度嚴重下降,雖然InnoDB存儲引擎的表允許的最大行數為10億,但是如果一張表的資料行記錄達到上億級别,那就算通過索引去查詢一條資料,它也需要至少經過上十次到幾十次磁盤IO,進而導緻單表查詢速度直線下降;一般一張表的資料行數在800~1200W左右最合适。

1.4、單體架構的通病

單庫中某張表遇到問題需要修複時,會影響了整個庫中所有資料,因為有些嚴重的情況下需要停機優化後重新上線,這時其它一些沒有出現問題的表,也會是以受到影響。

這就好比團隊中一個人沒完成好工作,是以導緻整個團隊一起陪同加班,這無疑很令人糟心。

1.5、MySQL資料庫瓶頸

上面聊到的各類問題,本質上都是一些資料庫瓶頸,一般程式的性能瓶頸都源自于硬體問題,而問題歸根到底都屬于IO、CPU瓶頸,接下來聊一聊IO、CPU瓶頸可以細分成哪些呢?

2.1、IO瓶頸

IO瓶頸主要分為兩方面,一方面是磁盤IO瓶頸,另一方面則是網絡IO瓶頸,具體如下:

磁盤IO瓶頸

①在之前《MySQL記憶體篇》中曾詳細講到過,MySQL為了提升讀寫性能,通常都會将一些經常使用的熱點資料放入緩沖區,避免每次讀寫請求都走磁盤IO的方式去操作資料,但當整庫資料資料太多時,可能會出現大量的熱點資料,此時記憶體緩沖區中又無法全部放下,是以會導緻大量的讀寫請求産生磁盤IO,通過讀寫磁盤的方式去完成資料讀寫,進而導緻查詢速度下降。

②一次查詢資料的過程中,由于涉及到的資料過多,導緻無法全部在記憶體中完成資料檢索,如分組、排序、關聯查詢等場景,記憶體中相應的緩沖區無法載入要操作的全部資料,是以隻能通過分批的方式處理資料,此時又需要經過大量磁盤IO後才得到最終資料集。

這種情況在單表查詢時也存在,如果單表中字段過多,導緻每一行資料的體積都比較大,是以會超出MySQL磁盤IO每次讀取16KB的這個限制,因而也會出現檢索單表資料時,一條資料就需要經過多次IO才能拿完。

簡單來說磁盤IO瓶頸有兩種情況,一種是磁盤IO次數過多,導緻IO使用率持續居高不下,另一種情況就是每次讀取資料都超出單次IO的最大限制,是以會引發多次IO,進而又演變成第一種情況。

網絡IO瓶頸

當一個請求的生成的SQL語句執行後,由于這條語句得到的結果集資料太多,進而會導緻相應時的資料包體積過大,這時如果網絡帶寬不夠,就會出現傳輸過慢的問題,因為底層需要對大的資料包做拆包,然後分批傳回,這時也會阻塞其它讀寫資料的請求,網絡帶寬就會成為新的瓶頸。

其實網絡IO瓶頸的情況也比較好了解,網絡帶寬就好比一條馬路,由于一條SQL傳回的結果集過大,是以裝載資料包的卡車遠遠超出了馬路的寬度,這時就需要将資料拆分到多輛能通行的小卡車裝載,但雖然這樣做能夠讓資料包“變窄”成功傳回,但是以也會造成這個大資料包會變長,進而占滿整個帶寬通道,是以其它網絡資料包需要等這個大包傳輸完成後,才能繼續通行。

2.2、CPU瓶頸

上面簡單的了解IO瓶頸後,再來看看機器本身的CPU瓶頸,CPU瓶頸也會分為兩種,一種是運算密集型瓶頸,另一種則是阻塞密集型瓶頸,但想要弄清楚這兩種瓶頸,咱們首先得了解CPU的工作原理。

CPU工作原理

有深入研究過多線程程式設計的小夥伴,對于CPU的工作原理應該并不陌生,線程是作業系統最小的執行單元,是以CPU工作時本質上就是以線程作為載體,然後執行線程任務中的一條條指令,一般情況下單核CPU在同一時刻隻能夠支撐單挑線程運作,但随着2002年超線程技術的釋出後,如今的CPU通常具備兩組ALU執行單元,也就是大家常聽到的單核雙線程、四核八線程.....

因為超線程技術的存在,是以一核CPU在同一時刻可支撐兩條線程一起運作,這種結構的CPU在現在也被稱為大核架構,即一核心具備兩邏輯處理器。而傳統的單核單線程的CPU則被稱為小核架構,目前最新的12/13代CPU還有大小核異構的架構。

但無論是小核、大核、大小核架構的CPU,本質上在機器運作期間,往往整個系統内所有程式的線程數,加起來之後都會遠超于CPU核數,如下:

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

那在這種情況下是如何工作的呢?相信有相關知識儲備的小夥伴第一時間就會想到一個詞彙:時間片切換執行,也就是有限的CPU核心會在所有線程之前來回切換,以此來確定系統和程式的正常運轉。

其中的原理簡單了解起來也并不難,因為每條線程的指令在執行時都需要資料作為基礎,是以CPU在執行某條指令後,執行新指令時需要加載資料,此時會去載入資料,而作業系統這時就會将CPU資源切換給其它的線程執行,等這條線程的資料準備好了再切換回來。

淺顯層面的原理如上,再深入一些會涉及總線、I/O裝置原理、活躍程序算法、資源切換原理......等一系列技術了,這裡先就此打住,我們隻需要了解到這裡就夠用啦!接着再回去聊聊所說的兩種CPU瓶頸。

運算密集型瓶頸

使用者請求生成的SQL語句中包含大量join聯表查詢、group by分組查詢、order by排序等之類的聚合操作,同時這些操作要基于特别大的資料集做運算,導緻執行時消耗大量CPU資源,CPU占用率直達100%+,是以無法再給其它線程提供執行所需的CPU資源。

阻塞密集型瓶頸

一張或多張表的資料量特别大,此時基于這些大表做資料檢索時,需要掃描的資料行太多,雖然這些SQL語句不會大量消耗CPU資源,但由于資料量過大,會導緻長時間占用CPU資源,進而造成其它線程無法擷取CPU資源執行。

上面聊到的兩種CPU瓶頸中,一種屬于大量運算導緻CPU資源耗盡,一種屬于大表檢索導緻長時間占用CPU資源,兩種情況都會導緻CPU遇到瓶頸,進而無法給其它線程提供運作所需的資源。有人也許會說,似乎這是SQL不合理導緻的呀?其實不然,因為往往很多正常的SQL也會出現大量消耗CPU、或檢索大表資料長時間占用CPU的情況。

無論是IO瓶頸,還是CPU瓶頸,都可以通過更新硬體配置的方式來解決,比如更新磁盤材質、加大網絡帶寬、增多CPU核數等,但前面講到過,這種方式面對高并發大流量的沖擊,治标不治本!如果用戶端流量過大,這時不應該再試圖尋找更強壯的馬來代替,而是應該選擇多匹馬的方案來解決。

其實這也是一種節省成本的做法,無限制更新硬體也不是長久之道,而且越到後期,配置越高的硬體成本越高,四顆八核十六線程的CPU,成本價可能還會比一顆32Core 64Thread的CPU要便宜。

1.6、再聊為何需要分庫分表

其實不管是并發過高、或通路變慢、亦或資料量過大,本質上都屬于資料庫遭遇到了瓶頸,但隻不過根據情況不同,分為不同類型的資料庫瓶頸,但是最終對于用戶端而言,就是資料庫不可用了或者變慢了。

而導緻資料庫出現此類問題的原因,實則就是随着業務的發展,系統的資料不斷增多、使用者量不斷增長、并發量不斷變大,是以對于資料再進行CRUD操作的開銷也會越來越大,再加上實體伺服器的CPU、磁盤、記憶體、IO等資源有限,最終也會限制資料庫所能承載的最大資料量、資料處理能力。

當出現上述這類問題,并且無法通過更新硬體、版本、調優等手段解決時,或者隻能臨時解決,卻無法保障未來業務增長的可用性時,此刻就需要合理的設計資料庫架構來滿足不斷增長的業務,這就是分庫分表誕生的初衷,目的就是為了避免單庫由于壓力過高,導緻出現之前所說的一系列問題,合理的設計架構能最大限度上提高資料庫的整體吞吐量。

下面為了能夠更好的講透徹分庫分表的方法論,我将以一個真實案例給大家闡述分庫分表的架構演進過程。

二、傳統單庫架構到分庫分表的演進史

早些年我司新開一條業務線切入金融領域,最開始的因為擔心風險過大,是以并未投入太多的成本,處于一個試錯階段,最初就把所有業務都怼入一個war包,所有業務共享一個庫資源,結構大緻如下:

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

而在當時那段時間,金融領域快速發展,慢慢的,Java搭建的金融核心系統開始出現響應變慢,甚至時不時當機,部署整個金融核心系統的單台Tomcat很快遇到了瓶頸,後來實在因為Tomcat三天倆頭當機重新開機,迫于無奈開始了業務架構的改進,如下:

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

因為當時考慮到業務發展速度,并沒有使用Nginx對Tomcat進行橫向拓展做水準叢集,因為如果僅僅隻是通過Nginx來做,可能以後還是需要對架構進行更新,進一步按業務拆分成分布式系統,是以經讨論後一緻決定直接引入分布式架構對系統進行改造。

經過改造後的業務架構,Java應用這邊的确可以抗住每天的流量,但當時因為在做Java程式架構更新的時候,隻引入了Redis、MQ降低資料庫并發,并沒有去對資料庫做太多的拓展,是以當時還是所有業務共享一個庫。

随着時間的慢慢推移,雖然用MQ、Redis做了流量的削峰,但是也擋不住當時的流量請求,做過金融業務的小夥伴應該清楚,它不像其他業務領域中讀多寫少,金融業務中讀多寫也多,同時還需要每日對賬、跑批、統計報表.....,是以對資料庫的讀寫操作相當多,而金融業務又要求資料實時性,是以很多操作無法走MQ異步完成,也不能放入Redis做資料緩存,Why?

好比拿股市中最基本的買入賣出為例,原本客戶看到的是5$一股,然後使用者選擇了買入,因為資料放在緩存裡沒有及時更新,結果最新價格成了10$一股,此時使用者買入10000$,按使用者的預估應該是會買入2000股左右,結果最終買成了1000股,平白無故導緻使用者追漲。

也包括大量的寫操作也無法走MQ異步完成,比如使用者以5$的成本價買入,現在看到了最新的價格為10$一股,然後選擇了全倉賣出,這時你将賣出操作發給了MQ異步執行,結果MQ中的賣出消息并未立馬被消費,而是到了一小時後價格降到了2.5$一股時,才真正被消費,這回導緻原本使用者能賺100點,最後反變為倒虧50點。

雖然當時手上的項目并非交易所類型的金融業務,但無論是哪類金融業務,基本上對資料的實時性要求特别高,是以MQ、Redis基本上隻能分擔很小一部分的流量,其它大部分的流量依舊會需要落庫處理。也正因如此,最終資料庫成了整個系統的瓶頸口,為了去解決這個問題,最終選用服務獨享庫的方案進行更新(也就是後續要說的垂直分庫模式),如下:

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

而資料庫這邊經過拆分之後,相較于之前的單庫架構,整個資料庫系統的穩定性和可用性明顯得到改善,但由于某些庫是經常需要被通路到的(資金庫、信審庫、背景庫),是以這些核心庫以單節點方式去承載流量還是顯得有點吃力(吞吐量下降、響應速度變慢),最終又對核心業務庫進行橫向擴容,架構如下:

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

最終,根據服務不同的業務規模,拆成了規模不同、業務不同的庫,但是這其中的拆分規則到底是什麼呢?以及拆分的依據又是啥?接着一起來聊一聊!

三、分庫分表正确的拆分手段

現在你的手裡有一個西瓜,吃的時候切法有兩種,一種是以垂直方向豎切,另一種是以水準方向橫切,如下:

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

這種切割方式在分庫分表中也存在,分庫分表的拆分規則也可分為:水準、垂直 兩個次元。

但水準、垂直該怎麼拆?什麼場景下拆?拆完會出現的問題又該怎麼去解決呢?那麼接着來一步步分析到底怎麼拆,拆完的問題怎麼去解決~

注意:分庫、分表是兩個概念,兩者并不是同一個名詞,是以這裡需要牢記!按拆分的粒度來排序,共計可分為四種方案:垂直分表、水準分表、垂直分庫、水準分庫。

3.1、不同場景下的分表方案

分表大多是在單表字段過多或資料過多的場景下,會選擇的一種優化方案,當一個表字段過多時,應當考慮垂直分表方案,将多餘的字段拆分到不同的表中存儲。當一個表的資料過多時,或者資料增長速率過快時,應當考慮通過水準分表方案,來降低單表的資料行數。

3.1.1、垂直分表:結構不同,資料不同(表級别)

當一張表由于字段過多時,會導緻表中每行資料的體積變大,而之前不僅一次聊到過:單行資料過大帶來的後患,一方面會導緻磁盤IO次數增多,影響資料的讀寫效率;同時另一方面結果集響應時還會占用大量網絡帶寬,影響資料的傳輸效率;再從記憶體次元來看,單行資料越大,緩沖區中能放下的熱點資料頁會越少,當讀寫操作無法在記憶體中定位到相應的資料頁,進而又會産生大量的磁盤IO。

從上述的幾點原因可明顯感受到,當單表的字段數量過多時,會導緻資料檢索效率變低、網絡響應速度變慢、資料庫吞吐量下降等問題,面對于這種場景時,就可以考慮垂直分表。

例:現在有一張表,總共43個字段,但是對于程式來說,一般經常使用的字段不過其中的十餘個,而這些經常使用的字段則被稱之為熱點字段,假設此時這張表中的熱點字段為18個,剩下的冷字段為25個,那麼我們就可以根據冷熱字段來對表進行拆分,如下:

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

對字段過多的表做了垂直拆分後,這時就能很好的控制表中單行資料的體積,進而能夠讓經常使用的字段資料更快的被通路、更快的傳回。不過在做垂直拆分時,記得在冷字段的表中多加一個列,作為熱字段表的外鍵映射,保證在需要用到冷資料時也能找到。

對于這種垂直分表的場景在很多業務中都有實作,如使用者資料會分為users、user_infos,訂單資料會分為order、order_info......。所謂的垂直分表其實和之前《庫表設計篇》中聊到的範式設計,大緻含義是類似的,如果表結構是按照資料庫三範式設計的,基本上也無需考慮做垂直分表。

經過垂直拆分後的兩張或多張表,各自之間的表結構不同,并且各自存儲的資料也不同,這是垂直分表後的特性,以上述例子來說,熱點字段表會存儲熱資料,冷字段表會存儲冷資料,兩張表的拼接起來後會組成完整的資料。

3.1.2、水準分表:結構相同,資料不同(表級别)

前面聊到了字段過多對讀寫資料時的影響,接着再來看看資料過多時會導緻的負面影響,雖然資料庫中有索引機制,能夠確定單表在海量資料的基礎上,檢索資料的效率依舊可觀,但随着資料不斷增長,當達到千萬級别時,就會出現明顯的查詢效率下降的問題。

這裡所謂的查詢效率下降并非指單表的簡單查詢語句,而是指一些複雜的SQL語句,畢竟線上往往很多需求,都要經過複雜的SQL運算後才能得到資料,比如多張表聯查再跟了一堆分組、排序、過濾、函數處理.....語句,這種情況下再基于這種大表查詢,就算走了索引,效率也不會太高,因為其中要涉及到大量資料的處理,是以面對這種情況,就可以對表進行水準拆分。

例:現在有一張表,裡面有三千萬條資料記錄,當基于該表去執行一條在索引上的複雜SQL時,也需要一定時間,至少會比1000萬的資料表慢了好幾倍,此時可以把這張3000W的表,拆為三張1000W的表,如下:

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

對一張大表做了水準分表之後,咱們能夠很好的控制單表的資料行數,3000W條資料的表和1000W條資料的表,查詢速度其實不僅僅隻是3倍的差距,資料過了千萬級别時,資料量每向上增長一個量級,查詢的開銷也會呈直線性增長,是以做水準分表時,一般要求控制在500-1200W之間為一張表。

阿裡内部的單表資料量大概控制在500~600W一張,因為這個資料量級,就算使用分布式政策生成的分布式ID作為主鍵,也能夠很好的把索引樹高控制在3~5以内,也就意味着最多三到五次磁盤IO就一定能得到資料,進而将單表的查詢性能控制在最佳範圍内。

水準拆分之後的兩張或多張表,每張表的表、索引等結構完全相同,各表之間不同的地方在于資料,每張表中會存儲不同範圍的資料。不過拆分之後的水準表究竟會存儲哪個範圍的資料,這要根據水準分表的政策來決定,你可以按ID來以資料行分表,也可以按日期來以周、月、季、年.......分表。

PS:諸位看下來應該會發現,水準分表的方案和之前聊到過的《MySQL表分區技術》十分類似,但這不意味着表分區可以代替水準分表,答案恰恰相反,一般要用表分區的場景中可以選擇水準分表的方案來代替。

3.1.3、分表方案總結

分表方案主要是針對于單表字段過多或資料過多的情況去做的,通過垂直、水準分表的手段,能夠很好解決單表由于字段、資料量過多産生的一系列負面影響,但無論是垂直分表還是水準分表,都必須建立在單庫壓力不高,但是單表性能不夠的情況下進行的,因為它們都屬于庫内分表。

如果是資料庫整體壓力都很大的情況,進而導緻的查詢效率低下,那不管再怎麼做分表也無濟于事,畢竟連流量入口都出現了擁塞,自然分表也無法解決問題,是以分表操作隻建立在單庫壓力不高,但是單表查詢效率低下的情況适用。

好比把資料庫比作一個遊樂園,而表則可以比作裡面的一個個娛樂項目,由于某些娛樂項目比較火爆,是以可以對同一類型的項目多開幾個,進而解決熱點項目顧客要排隊很久才能玩的問題。但如果是整個遊樂園的人流量都非常大,每個項目都有大量顧客排隊,這時再去對内部的娛樂項目作拓展,這種方式是行不通的,畢竟遊樂園的大門就那麼大,遊客連進大門都要排隊,再在内部作項目拓展顯然無濟于事。

3.2、不同場景下的分庫方案

經過前面的分表總結後可以得知:如果是因為庫級别的壓力較大,這時就需要考慮分庫方案,而不僅僅是分表方案,換到上面的例子中,當整個遊樂園的人流量非常大時,應該考慮的是開分園,而并非是在内部作拓展。

分庫和分表一樣,也可以按垂直和水準兩個次元來分,垂直分庫本質上就是按業務分庫,也就是現在分布式/微服務架構中,業務獨享庫的概念,而水準分庫則是對同一個節點作橫向拓展,也就是高可用叢集的概念。

3.2.1、垂直分庫:結構不同,資料不同(庫級别)

當資料庫使用單機的結構部署,在大流量/高并發情況下遇到瓶頸時,此時就可以考慮分庫方案了,首先來聊聊垂直分庫。

在項目開發過程中,一般為了友善團隊分工合作和後續管理維護,通常都會對單個項目劃分子產品,按照業務屬性的不同,會将一個大的項目拆分為不同的子產品,同時每個業務子產品也會在資料庫中建立對應的表。

而所謂的垂直分庫,就是根據業務屬性的不同,将單庫中具備同一業務屬性的表,全部單獨擰出來,放在一個單獨的庫中存儲,也就按業務特性将大庫拆分為多個業務功能單一的小庫,每個小庫隻為對應的業務提供服務,這樣能夠讓資料存儲層的吞吐量呈幾何倍增長。

例:以前面給出的金融項目來說,當單個庫無法承載整個業務系統産生的流量壓力時,比如此時單個資料庫節點的QPS上限為2000,但業務高峰期抵達資料庫的瞬時流量,造成了2W個并發請求,這時如果處理不當,資料庫基本上會被這波瞬時流量打當機。

對于前面所說的這種情況,就可以考慮根據業務屬性拆分整個大庫了,核心思想就是:既然單個節點扛不住,那就加機器用多個節點來抗,在用戶端按照不同的業務屬性,将過來的請求按照不同的業務特性做分流處理,如下:

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

原本之前單庫時,無論是查詢使用者業務相關的SQL語句,還是放款/還款之類的SQL語句,不管三七二十一統統發往同一個資料庫處理,全部都由這一個資料庫節點提供資料支援,但按業務特性做了垂直分庫後,使用者相關的讀寫請求落入使用者庫,放款/還款之類的讀寫請求會落入資金庫.....,這樣就能很好的去應對單庫面臨的負載過高問題。

垂直分庫後,每個庫中存儲的資料都不相同,因為是按照業務特性去将對應的表抽出去了組成新庫,是以庫結構也是不同的,使用者庫是由使用者相關的表組成、信審庫是由心生相關的表組成.......。

3.2.2、水準分庫:結構相同,資料不同(庫級别)

經過前面的垂直分庫後,根據不同的業務類型,将通路壓力分發到不同的庫處理後,雖然在極大程度上提升了資料層的負荷能力,但如果某類業務的并發數依舊很高,比如經過前面的業務分流後,假設平台庫需要承載5000的并發、信審庫依舊需要承載1W的并發,這也遠超出了單個資料庫節點的處理瓶頸,最終可能還是會能把對應的資料庫節點打當機,是以此時可通過水準分庫的方案,來提升某類業務庫的抗并發吞吐量。如下:

MySQL之高并發大流量情況下海量資料分庫分表的正确姿勢

通過水準拆分的方案,能夠根據壓力的不同,配置設定不同的機器數量,進而使得不同庫的抗壓性都能滿足對應的業務需求,這也就類似于分布式/微服務項目中,對單個服務做叢集保證高可用的政策。

水準分庫是基于一個節點,然後直接橫向拓展,這也就意味着同一業務的資料庫,各節點之間的庫結構完全相同,但每個節點中的資料是否相同,這就要看你自己去決定了,一般情況下都是不同的,也就是不同節點的庫會存儲不同範圍的資料。

3.2.3、另類的分庫方案

前面聊清楚了分庫分表中經典的垂直分庫和水準分庫方案,但除開這兩種之外,還有一些另類的分庫方案,也就是指一些資料庫的高可用方案,例如主從複制、讀寫分離、雙主熱備等方案。

主從方案:一般會搭建讀寫分離,寫請求發往主節點處理,讀請求發往從節點處理,從節點會完全同步主節點的資料,進而實作讀寫請求分開處理的效果,能夠再一定程度上提升資料存儲層整體的并發處理能力。同時當主機挂掉時,從機也能夠在很快的時間内替換成主機,以此確定資料層的高可用。

多主方案:一般是雙主方案,兩台資料庫節點之間互為主從,互相同步各自的資料,兩台節點中都具備完整的資料,讀寫請求可以發給任意節點處理。相較于前面的主從讀寫分離架構,這種雙主雙寫架構的災備能力更強,因為當其中某個節點當機時,另一個節點可以完全接替對方的流量,不存在從機切換成主機的時間開銷,是以能夠保證資料100%不丢失。

不過無論是主從、還是多主方案,本質上都存在木桶效應問題,因為這種分庫方案中都會完全同步資料,當一個節點的資料存滿時,會導緻其他節點也不可用,對于這裡的具體原因可參考《MySQL優化篇-架構優化》。

四、分庫分表總結

分庫方案能夠在最大程度上提升資料存儲層的性能,但一般在考慮選用分庫方案時,應該先考慮使用主從、主主的方案,如果前面兩種方案依舊無法提供系統所需的吞吐量,再考慮選擇垂直分庫方案,按照業務屬性去劃分庫結構,最後才應該考慮選擇水準分庫方案(同時也要記得考慮資料的增長速率情況)。

那為什麼需要遵循這個順序呢?因為架構不能過度設計,選用主從、主主能夠滿足需求時,就選這兩種方案,因為一方面能避免很多問題産生,同時實作起來也比較簡單。同時先考慮垂直分庫,再考慮水準分庫,是因為水準分庫可以建立在垂直分庫的基礎上,進一步對存儲層作拓展,是以靈活性會更高,拓展性會更強。

同時最後再聊一下分庫之後帶來的好處:

  • ①能夠得到最大的性能收益,吞吐量會随機器數量呈直線性增長。
  • ②能夠最大程度上保障存儲層的高可用,任意節點當機都不會影響整體業務的運轉。
  • ③具備相當強的容錯率,當一個庫中的結構存在問題需要重構時,無需将所有業務停機更新。
  • ④具備高穩定性,分庫+配備完善的監控重新開機政策後,基本上能確定線上無需人工介入管理。

也就是說,分庫方案能夠讓你的存儲層真正達到高可用、高性能、高穩定的“三高”水準。

但要切記不能盲目的分庫分表,分庫分表前得先清楚性能瓶頸在哪裡,然後根據業務以及瓶頸,遵循拆分規則的順序做合理的拆分方案選擇,因為分庫分表雖然能帶來很大的好處,但是同時也産生了一系列的問題需要去解決。

如果做了分庫分表就一定要記住:既不能過度設計,也要考慮資料增長性,提前設計好擴容方案,以便于後續性能再次出現瓶頸時,能夠基于現有架構進行優雅更新,一位優秀的開發/架構必須具備前瞻性。

但關于如何設計出一套合理的擴容方案,也包括分庫分表後究竟會産生哪些後患問題,這些問題産生後又該怎麼解決,具體的内容可參考下篇:《分庫分表後患問題一站式解決方案》。

繼續閱讀