關於大型網站技術演進的思考(六)--存儲的瓶頸(6)

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

  要解決數據表被水平拆分後的單表查詢問題,咱們首先要回到問題的源頭,咱們爲何須要將數據庫的表進行水平拆分。下面咱們來推導下咱們最終下定決心作水平拆分表的演進過程,具體以下:算法

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

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

  第一個方面:數據庫的鏈接數的限制。原庫的某些表可能承擔數據庫80%的鏈接,極端下甚至能夠超過90%的鏈接,並且這些表的業務操做十分的頻繁,當其餘小衆業務的表須要進行操做時候,搞很差由於鏈接數被所有佔用而不得不排隊等待空閒鏈接的出現,那麼這個時候咱們就會考慮把這張表作垂直拆分,這樣就減輕了原數據庫鏈接的壓力,使得數據庫鏈接負載變得比較均衡。緩存

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

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

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

  狹義水平拆分的使用的前提是由於數據量太大,到底多大了,咱們舉個例子來講明下,假如某個電商平臺一天的交易筆數有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數據庫。文章寫道這裏,我仍是想說一個觀點,若是一個系統有很強烈需求去作狹義的水平拆分,那麼這個公司的某個業務那確定是很是的大了,因此啊,這個方案以公司爲單位應該有點小衆了。

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

相關文章
相關標籤/搜索