大型網站架構改進歷程:存儲的瓶頸(6)

在講數據庫水平拆分時候,我列出了水平拆分數據庫需要解決的兩個難題,它們分別是主鍵的設計問題和單表查詢的問題,主鍵問題前文已經做了比較詳細的講述了,但是第二個問題我沒有講述,今天我將會講講如何解決數據表被垂直拆分後的單表查詢問題。

  要解決數據表被水平拆分後的單表查詢問題,我們首先要回到問題的源頭,我們爲什麼需要將數據庫的表進行水平拆分。下面我們來推導下我們最終下定決心做水平拆分表的演進過程,具體如下:

  第一個演進過程:進行了讀寫分離的表在數據增長後需要進行水平拆分嗎?回答這個疑問我們首先要想想進行讀寫分離操作的表真的是因爲數據量大嗎?答案其實是否定的。最基本的讀寫分離的目的是爲了解決數據庫的某張表讀寫比率嚴重失衡的問題,舉個例子,有一張表每天會增加1萬條數據,也就是說我們的系統每天會向這張表做1萬次寫的操作,當然也有可能我們還會更新或者刪除這張表的某些已有的記錄,這些操作我們把它歸併到寫操作,那麼這張表一天我們隨意定義個估值吧2萬5千次寫操作,其實這種表的數據量並不大,一年下來也就新增的幾百萬條數據,一個大型的商業級別的關係數據庫,當我們爲表建立好索引和分區後,查詢幾百萬條數據它的效率並不低,這麼說來查詢的效率問題還不一定是讀寫分離的源頭。其實啊,這張表除了寫操作每天還承受的讀操作可能會是10萬,20萬甚至更高,這個時候問題來了,像oracle和mysql這樣鼎鼎大名的關係數據庫默認的最大連接數是100,一般上了生產環境我們可能會設置爲150或者200,這些連接數已經到了這些關係數據庫的最大極限了,如果再加以提升,數據庫性能會嚴重下降,最終很有可能導致數據庫由於壓力過大而變成了一個巨鎖,最終導致系統發生503的錯誤,如是我們就會想到採用讀寫分離方案,將數據庫的讀操作遷移到專門的讀庫裏,如果系統的負載指標和我列舉的例子相仿,那麼遷移的讀庫甚至不用做什麼垂直拆分就能滿足實際的業務需求,因爲我們的目的只是爲了減輕數據庫的連接壓力。

  第二個演進過程:隨着公司業務的不斷增長,系統的運行的壓力也越來越大了,我們已經瞭解了系統的第一個瓶頸是從存儲開始了,如是我們開始談論方案如何解決存儲的問題,這時我們發現我們已經做了讀寫分離,也使用了緩存,甚至連搜索技術也用上了,那麼下個階段就是垂直拆分了,垂直拆分很簡單就是把表從數據庫裏拆出來,單獨建庫建表,但是這種直截了當的方案想想就能感到這樣的做法似乎沒有打中系統的痛點,那麼系統的痛點到底是什麼呢?根據數據庫本身的特性,我們會發現痛點主要是三個方面組成:

  第一個方面:數據庫的連接數的限制。原庫的某些表可能承擔數據庫80%的連接,極端下甚至可以超過90%的連接,而且這些表的業務操作十分的頻繁,當其他小衆業務的表需要進行操作時候,搞不好因爲連接數被全部佔用而不得不排隊等待空閒連接的出現,那麼這個時候我們就會考慮把這張表做垂直拆分,這樣就減輕了原數據庫連接的壓力,使得數據庫連接負載變得比較均衡。

  第二個方面是數據庫的讀操作,第三個方面是數據庫的寫操作,雖然把讀和寫分成兩個方面,但是這兩個方面在我們做垂直拆分時候要結合起來考慮。首先我們要分析下數據庫的寫操作,單獨的寫操作效率都是很高的,不管我們的寫是單條記錄的寫操作,還是批量的寫操作,這些寫操作的數據量就是我們要去寫的數據的大小,因此控制寫的數據量的大小是一件很容易很天然的操作,所以這些操作不會造成數據庫太大負擔,詳細點的話,對於數據庫而言,新增操作無非是在原來數據後面追加些記錄,而修改操作或者刪除操作一般都是通過建立了高效索引的字段來定位數據後再進行的操作,因此它的性能也是非常高的。而讀操作看起來比寫操作簡單(例如:讀操作不存在像事務這些烏七八糟因素的干擾),但是當讀操作面對海量數據時候就嚴重挑戰着數據庫和硬盤的極限能力,因此讀操作很容易產生瓶頸問題,而且這個瓶頸不管問題表是否讀寫失衡都會面臨的。前文裏我詳細列舉了一個交易表設計的案例,其中我們可以看到數據庫垂直拆分在實際應用裏的運用,在例子裏我們首先根據業務特點將交易表分成了實時交易表和歷史交易表,這個做法其實就是將原交易表的讀和寫進行分離,但是這種分離和純粹的讀寫分離相比會更加有深意,這個深意就是拆分實時和歷史交易表也就是在分拆原表的讀寫操作的關聯性,換句話說,如果我們不這麼做的話,那麼交易表的每次寫和每次讀幾乎等價,這樣我們沒法單獨解決讀的性能問題,分出了歷史交易表後我們再對歷史交易表來做讀的優化,那麼這也不會影響到寫操作,這樣把問題的複雜度給降低了。在案例裏我們對歷史交易表進行了業務級別的水平拆分,但是這個拆分是以如何提升讀的效率進行的,因此前文講到的水平拆分裏主鍵設計方案基本上派不上用場,因爲這兩種水平拆分的出發點是不同的,那麼使用的手段和達到效果也將不一樣。

  由上所述,我們可以把數據庫的水平拆分重新定義下,我在這幾篇文章裏一直講述的水平拆分本質是從數據庫技術來定義的,我把它們稱爲狹義的水平拆分,與狹義相對的就是廣義的水平拆分,例如上文例子裏把交易表根據業務特性分爲實時交易表和歷史交易表,這種行爲也是一種水平拆分,但是這個拆分不會遵守我前面講到主鍵設計方案,但是它的確達到水平拆分的目的,所以這樣的水平拆分就屬於廣義的水平拆分了。

  第三個演進過程:到了三個演進過程我們就會考慮到真正的水平拆分了,也就是上面提到的狹義的水平拆分了,狹義的水平拆分執行的理由有兩個,一個那就是數據量太大了,另一個是數據表的讀寫的關聯性很難進行拆分了,這點和垂直拆分有所不同,做垂直拆分的考慮不一定是因爲數據量過大,例如某種表數據量不大,但是負載過重,很容易讓數據庫達到連接的極限值,我們也會採取垂直拆分手段來解決問題,此外,我們想減輕寫操作和讀操作的關聯性,從而能單獨對有瓶頸的寫操作或讀操作做優化設計,那麼我們也會考慮到垂直拆分,當然數據量實在是太大的表我們想優化,首先也會考慮到垂直拆分,因爲垂直拆分是針對海量數據優化的起始手段,但是垂直拆分可不一定能解決海量數據的問題。

  狹義水平拆分的使用的前提是因爲數據量太大,到底多大了,我們舉個例子來說明下,假如某個電商平臺一天的交易筆數有2億筆,我們用來存儲數據的關係數據庫單表記錄到了5千萬條後,查詢性能就會嚴重下降,那麼如果我們把這兩億條數據全部存進這個數據庫,那麼隨着數據的累積,實時交易查詢基本已經沒法正常完成了,這個時候我們就得考慮把實時交易表進行狹義的水平拆分,狹義的水平拆分首先碰到的難點就是主鍵設計的問題,主鍵設計問題也就說明狹義水平拆分其實解決的是海量數據寫的問題,如果這張表讀操作很少,或者基本沒有,這個水平拆分是很好設計的,但是一張表只寫不讀,對於作爲業務系統的後臺數據庫那基本是非常罕見的,。

  前文講到的主鍵設計方案其實基本沒有什麼業務上的意義,它解決的主要問題是讓寫入的數據分佈均勻,從而能合理使用存儲資源,但是這個合理分佈式存儲資源卻會給查詢操作帶來極大的問題,甚至有時可以說狹義水平拆分後數據查詢變得困難就是由這種看起來合理的主鍵設計方案所致。

  我們還是以實時交易表的實例來說明問題,一個電商平臺下會接入很多不同的商戶,但是不同的商戶每天產生的交易量是不同,也就是說商戶的維度會讓我們使交易數據變得嚴重的不均衡,可能電商平臺下不到5%的商戶完成了全天交易量的80%,而其他95%的商戶僅僅完成20%的交易量,但是作爲業務系統的數據表,進行讀操作首先被限制和約束的條件就是商戶號,如果要爲我們設計的實時交易表進行狹義的水平拆分,做拆分前我們要明確這個拆分是由交易量大的少量商戶所致,而不是全部的商戶所致的。如果按照均勻分佈主鍵的設計方案,不加商戶區分的分佈數據,那麼就會發生產生少量交易數據的商戶的查詢行爲也要承受交易量大的商戶數據的影響,而能產生大量交易數據的商戶也沒有因爲自己的貢獻度而得到應有的高級服務,碰到這個問題其實非常好解決,就是在做狹義水平拆分前,我們先做一次廣義的水平拆分,把交易量大的商戶交易和交易量小的商戶交易拆分出來,交易量小的商戶用一張表記錄,這樣交易量小的商戶也會很happy的查詢出需要的數據,心裏也是美滋滋的。接下來我們就要對交易量大的商戶的交易表開始做狹義的水平拆分了,爲這些重點商戶做專門的定製化服務。

  做狹義水平拆分前,我們有個問題需要過一下,在狹義水平拆分前我們需要先做一下廣義的水平拆分嗎?這個我這裏不好說,具體要看實際的業務場景,但是針對我列舉的實時交易的例子而言,我覺得沒那個必要,因此拆分出的重點商戶交易量本來就很大,每個都在挑戰數據庫讀能力的極限,更重要的是實時交易數據的時間粒度已經很小了,再去做廣義水平拆分難度很大,而且很難做好,所以這個時候我們還是直接使用狹義的水平拆分。拆分完畢後我們就要解決查詢問題了。

  做實時查詢的標準做法就是分頁查詢了,在講述如何解決分頁查詢前,我們看看我們在淘寶裏搜索【衣服】這個條件的分頁情況,如下圖所示:

 

 

  我們看到一共才100頁,淘寶上衣服的商品最多了,居然搜索出來的總頁數只有100頁,這是不是在挑戰我們的常識啊,淘寶的這個做法也給我們在實現水平拆分後如何做分頁查詢一種啓迪。要說明這個啓迪前我們首先要看看傳統的分頁是如何做的,傳統分頁的做法是首先使用select count(1) form table這樣的語句查詢出需要查詢數據的總數,然後再根據每頁顯示的記錄條數,查詢出需要顯示的記錄,然後頁面根據記錄總數,每頁的條數,和查詢的結果來完成分頁查詢。回到我們的交易表實例裏,有一個重要商戶在做實時交易查詢,可是這個時候該商戶已經產生了1千萬筆交易了,假如每頁顯示10條,記錄那麼我們就要分成100萬頁,這要是真顯示在頁面上,絕對能讓我們這些開發人員像哥倫布發現新大陸那樣驚奇,反正我見過的最多分頁也就是200多頁,還是在百度搜索發現的。其實當數據庫一張表的數據量非常大的時候,select的count查詢效率就非常低下,這個查詢有時也會近似個全表檢索,所以count查詢還沒結束我們就會失去等待結果的耐心了,更不要是說等把數據查詢出來了,所以這個時候我們可以學習下淘寶的做法,當商戶第一次查詢我們准許他查詢有限的數據。我自己所做的一個項目的做法就是這樣的,當某個商戶的交易量實在是很大時候我們其實不會計算數據的總筆數,而是一次性查詢出1000條數據,這1000條數據查詢出來後存入到緩存裏,頁面則只分100頁,當用戶一定要查詢100頁後的數據,我們再去追加查詢,不過實踐下來,商戶基本很少會查詢100頁後的數據,常常看了5,6頁就會停止查詢了。不過商戶也時常會有查詢全部數據的需求,但是商戶有這種需求的目的也不是想在分頁查詢裏看的,一般都是爲了比對數據使用的,這個時候我們一般是提供一個發起下載查詢全部交易的功能頁面,商戶根據自己的條件先發起這樣的需求,然後我們系統會在後臺單獨起個線程查詢出全部數據,生成一個固定格式的文件,最後通過一些有效手段通知商戶數據生成好了,讓商戶下載文件即可。

  對於進行了狹義水平拆分的表做分頁查詢我們通常都不會是全表查詢,而是抽取全局的數據的一部分結果呈現給用戶,這個做法其實和很多市場調查的方式類似,市場調查我們通常是找一些樣本採集相關數據,通過分析這些樣本數據推導出全局的一個發展趨勢,那麼這些樣本選擇的合理性就和最終的結論有很大關係,回到狹義水平拆分的表做分頁查詢,我們爲了及時滿足用戶需求,我們只是取出了全部數據中的一部分,但是這一部分數據是否滿足用戶的需求,這個問題是很有學問的,如果是交易表,我們往往是按時間先後順序查詢部分數據,所以這裏其實使用到了一個時間的維度,其他業務的表可能這個維度會不一樣,但肯定是有個維度約束我們到底返回那些部分的數據。這個維度可以用一個專有的名詞指代那就是排序,具體點就是要那個字段進行升序還是降序查詢,看到這裏肯定有人會有異議,那就是這種抽樣式的查詢,肯定會導致查詢的命中率的問題,即查出來的數據不一定全部都是我們要的,其實要想讓數據排序正確,最好就是做全量排序,但是一到全量排序那就是全表查詢,做海量數據的全表排序查詢對於分頁這種場景是無法完成的。回到淘寶的例子,我們相信淘寶肯定沒有返回全部數據,而是抽取了部分數據分頁,也就是淘寶查詢時候加入了維度,每個淘寶的店家都希望自己的商戶放在搜索的前列,那麼淘寶就可以讓商家掏錢,付了錢以後淘寶改變下商家在這個維度裏的權重,那麼商家的商品就可以排名靠前了。

  狹義水平拆分的本身對排序也有很大的影響,水平拆分後我們一個分頁查詢可能要從不同數據庫不同的物理表裏去取數據,單表下我們可以先通過數據庫的排序算法得到一定的數據,但是局部的排序到了全局可能就不正確了,這個又該怎麼辦了?其實由上面內容我們可以知道要滿足對海量數據的所有查詢限制是非常難的,時常是根本就無法滿足,我們只能做到儘量多滿足些查詢限制,也就是海量查詢只能做到儘量接近查詢限制的條件,而很難完全滿足,這個時候我前面提到的主鍵分佈方案就能起到作用了,我們在設計狹義水平拆分表主鍵分佈時候是儘量保持數據分佈均衡,那麼如果我們查詢要從多張不同物理表裏取的時候,例如我們要查1000條數據,而狹義水平拆分出了兩個物理數據庫,那麼我們就可以每個數據庫查詢500條,然後在服務層歸併成1000條數據,在服務層排序,這種場景下如果我們的主鍵設計時候還包含點業務意義,那麼這個排序的精確度就會得到很大提升。假如用戶對排序不敏感,那就更好做了,分頁時候如果每頁規定顯示10條,我們可以把10條數據平均分配給兩個數據庫,也就是顯示10條A庫的數據,再顯示5條B庫的數據。

  看到這裏有些細心的朋友可能還會有疑問,那就是居然排序是分頁查詢的痛點,那麼我們可以不用數據庫查詢,而使用搜索技術啊,NoSql數據庫啊,的確這些技術可以更好的解決分頁問題,但是關係數據庫過渡到搜索引擎和NoSql數據庫首先需要我們轉化數據,而狹義的水平拆分的數據表本身數據量很大,這個轉化過程我們是沒法快速完成的,如果我們對延時容忍度那麼高,其實我們就沒必要去做數據庫的狹義水平拆分了。這個問題反過來說明了使用狹義拆分數據表的業務場景,那就是:針對數據量很大的表同時該表的讀寫的關聯性是沒法有效拆分的。

  最後我要講的是,如果系統到了狹義水平拆分都沒法解決時候,我們就要拋棄傳統的關係數據方案了,將該業務全部使用NoSql數據庫解決或者像很多大型互聯網公司那樣,改寫開源的mysql數據庫。文章寫道這裏,我還是想說一個觀點,如果一個系統有很強烈需求去做狹義的水平拆分,那麼這個公司的某個業務那肯定是非常的大了,所以啊,這個方案以公司爲單位應該有點小衆了。

  好了,今天寫到這裏,祝大家晚安,生活愉快。

發佈了23 篇原創文章 · 獲贊 3 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章