1. 程式人生 > >提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

 

Java全棧技術 2018-12-21 08:45:32

點選關注,快速進階高階架構師

作者:兔龍象

我們公司前幾年的核心繫統,平均80人左右開發至今已經8年多了,至今還在維護,在全國20多個省部署了上千個點的執行,執行六七年後資料量上來了,結果平均每天都要出現宕機情況,90%以上都是資料庫的原因,客戶對我們的滿意度急劇下降,可見資料庫效能前期如果不設計好,後來帶來的問題真的是災難性的,雖然近些年各種儲存技術層出不窮,但關係型資料庫還是各種業務系統的核心,這篇文章詳細講講我們在資料庫效能方面如何實現線性擴充套件。

一、讀寫分離

最典型的場景就是單一資料庫儲存全部資料,資料量少、併發量少的時候沒問題,資料量和併發量上來了就出現了問題,由於寫(增刪改)操作會對資料庫上鎖,資料量大導致寫入較慢,或寫入操作較多時候,導致讀操作阻塞時間較長,從而引起效能下降。最常見、最有效、也是最容易實施的方案就是讀寫分離,講讀操作和寫操作分離。

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

讀寫分離有2個關鍵點,一個是資料複製延遲,一個是應用訪問:

1、資料複製延遲,主資料庫寫入資料後通過複製實現和從資料庫同步,期間一定會有延遲,比較快的也就秒級同步,1s延遲都算快的,資料量大甚至可能達到分鐘級別,所以對實時性要求特別特別高的應用就要好好考慮了,小心出現剛註冊完,登入時提示沒註冊的現象。

應對複製延遲也有很多方法,比如對實時性要求高的操作在主庫上進行,實時要求不高的放到從庫上,也就是部分讀寫分離;再比如從庫讀失敗了再從主庫讀一次等。

2、應用訪問的便捷性,原來一個數據源的時候,應用直接寫sql執行就行了,現在資料庫叢集了,不可能讓應用自行分辨去查詢哪個資料庫,這時候要加一層資料訪問層了,一般有兩種方式,簡單點的是程式碼中直接寫,比如用hibernate訪問資料庫,簡單封裝一下hibernate就行,或者用一些元件(如淘寶的TDDL)等,如下圖:

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

複雜一點的,就是使用資料庫中介軟體,資料庫中介軟體是一套獨立的系統,業務系統無需自行管理讀取哪個庫,正常傳送sql執行就行了,中介軟體將sql路由到要執行的資料庫上,不過通常中介軟體比較複雜,能不能符合自己的需求還要自行測試,如下圖:

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

讀寫分離後,拓展了資料庫對讀的處理能力,整體上也大大提高了資料庫的讀寫能力,而且由於庫分離後,可以針對庫做不同的優化,比如在寫庫上減少索引,在讀庫上增加索引等。讀寫分離比較適合讀多寫少的操作,隨著訪問量增大,讀的庫可以水平擴充套件,大大提高了讀寫操作的壓力,但卻沒有分散儲存的壓力,當資料量達到千萬或億條時候,單臺數據庫儲存就會成為瓶頸,寫操作就會極慢,索引維護也會時間很長,備份恢復也會很耗時間等。

二、分庫

資料量大了,最常見,最直接的辦法就是拆分庫表,將一個大庫拆成多個小庫,一個大表拆成多個小表,這樣效能瓶頸就降下來了,拆分也是有很多原則和方法的,最常見的就是按業務拆分庫,這個粒度通常也是比較大的,就是按一定規則(通常是按業務)將表分類,放入不同的庫中:

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

最大的好處就是分散了儲存和訪問的壓力,拆分後業務清晰,專庫專用維護簡單,按業務擴充套件也容易,缺點也有不少,其中有3個重要的要考慮

1、跨庫join,這個基本是所有分庫分表都會涉及到的,聯表查詢就要修改成逐個查詢了;

2、事務問題,資料庫拆分後,分散式事務是最頭痛的問題,我見過很多團隊因為這個問題難以解決,他們的拆分原則就是把有事務的放到一個庫中。

3、維護複雜,成本較高,資料庫多了,肯定比但資料庫維護複雜,建議資料量上來了,或者隨著業務發展再拆庫,避免上來就拆,運維複雜。

三、垂直分表

垂直拆分後,解決了業務間瓶頸的問題,但是單庫表資料量大帶來的瓶頸還沒有解決,那單業務或單表遇到資料量大的瓶頸如何解決?這就涉及到拆分表了,一般有兩種拆分方式,垂直拆分和水平拆分:

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

垂直拆分,當一個表特別寬,欄位特別多的時候,每次讀寫全部對磁碟IO較大,效能容易出現瓶頸,可以將表的欄位按訪問頻率分分類,比如我們之前做的業務裡面,當事人表有300多個欄位,常用欄位大約10個,就可以拆分成當事人主表,當事人擴充套件表。還有類似有很多論壇的設計也是,使用者表通常也被拆成兩個表,一個user,一個user_ext,90%查詢user就夠了,這樣也大大提高了效能。

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

拆表原則:

1、長度較短,訪問頻率較高的屬性儘量放在主表

2、欄位較長,訪問頻率較低的屬性放在子表

3、經常一起訪問的屬性,放在一個表裡,避免join和跨表查詢

4、如果實在屬性過多,主表和擴充套件表都可以有多個,不限於一個

這裡提到了跨表查詢,當我們可以一個sql查詢一個user的全部資訊時,我們可以一個sql聯表查詢,也可以兩個簡單sql,每個sql查詢一個表,在應用中合併資料,我們怎麼選?

執行一個sql的方法是將壓力扔給了資料庫,讓資料庫進行運算,執行兩個簡單的sql,資料庫壓力較小,而且由於查詢簡單,很多快取等都可以直接使用,速度快,但應用計算壓力大些。如何取捨呢,就要看未來的業務擴充套件了,如果有拆庫拆表的可能,有資料量極大的可能,那儘量執行簡單sql,減少資料庫壓力,因為相比資料庫的壓力,應用的瓶頸是比較好解決的,而資料庫的瓶頸解決會很複雜。

四、水平分表

業務對資料的操作主要集中在某些欄位上,比較適合垂直分表,當業務對資料的操作在整個表層面較均勻分佈,那就適合水平分表了。相比垂直拆分的複雜度,水平拆分複雜度就上了一個級別,水平拆分是按照某種規則把結構相同的資料劃分到不同表或資料庫裡,這些庫表都是完全同構的,比如我們的user表有1億條資料,併發讀寫都是問題,經過測算放到64個數據庫中比較合適,每個資料庫150w條,我們根據一個規則,id取模64進行運算,平均分佈到這64個庫中,如下圖:

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

這就是一個典型的水平拆分場景,水平拆分後,未來如果資料量變化較大,可以通過動態擴充資料庫來支援效能的擴充套件,看似非常好,但也帶來一個非常大的問題,就是應用訪問的時候要考慮查詢哪個資料庫,要做一次運算,這顯然給應用帶來了極大的麻煩。通常的解決辦法就是增加一層資料庫中介軟體(和前面讀寫分離時提到的中介軟體一樣),主要做SQL路由,優化,資料聚合等工作,如下圖:

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

現在的開源資料庫中介軟體有一些,不過或多或少都有些不足,好一點的執行sql都是併發執行,也就是如圖中的3、4步驟併發執行,大大提高sql執行效率,不過這些中介軟體很多比較複雜,很多公司由於需求簡單,也經常自行開發,這些不在此次討論範疇。

我們繼續說水平拆分,拆分依據要選擇最適合的,能夠與業務能夠吻合才是最好的,常見的有根據範圍、列舉、時間、取模、雜湊、指定等很多方式。水平拆分也有很多原則:

1、拆分要儘可能平均,不均勻會產生訪問熱點問題,我之前遇到過根據省份劃分的,結果有些省資料量極大,就產生了不平均問題,效能瓶頸沒解決。

2、儘量減少事務邊界,事務邊界的意思是指儘量符合業務操作,如果拆分後,每個業務操作都要查詢全部的表,大量的跨庫join等操作,反而會導致效能下降,沒起到拆分的效果。

這兩點有時候是衝突的,很多時候我們要取捨,舉個例子,最常見的使用者、訂單兩個表,訂單資料量大,我們要水平拆分,遵守拆分平均的原則,我們設計成id自增,雜湊取模進行平均拆分,分佈到1024張表中,如下圖:

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

這時候問題來了,使用者大部分的操作是根據使用者id查詢購買的訂單資訊,想想你在網上買東西,看的最多的就是“我的訂單”吧,所以業務上有大量的sql都是全表掃描,如下圖:

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

第③④步驟要查詢全部的表,效能消耗非常嚴重,如果資料多的時候,第⑤步的時候聚合時間較長,對cpu、記憶體消耗較大。那有什麼好辦法嗎?根據第三個原則,我們查詢較多的情況是根據使用者查詢訂單,那應該按照使用者id進行取模分庫,而不是根據訂單id。但是仔細想想,如果根據使用者id進行拆分,可能有些使用者買得東西多,有些使用者買的少,結果就是訂單就不會均勻分佈在這些表中,依然沒解決問題,那這兩個原則,我們如何平衡呢?通常來說我們以資料平均原則為主,優先考慮平均,因為解決查詢多,全表掃描等問題比較容易,有很多方案,而解決資料不均勻問題相對困難。

五、異構索引表

以平均原則為主後,如何解決跨表join、全表掃描等的場景呢?比較典型的方案就是異構索引表。就是在按訂單id分表儲存的時候,再儲存一份以使用者id為主的表,但只儲存到id層面,也就是做到索引。

簡單來說,也就是兩套水平拆分都有了,想查詢哪個就查詢哪個,不過這種對磁碟資源消耗較大,所以以訂單分割槽為主,人員分割槽只儲存常用的欄位,如id等,查詢的時候需要查詢兩次,如下圖(注意圖中的順序,先執行步驟②,根據使用者id查詢訂單id,再執行步驟⑤,根據訂單id查詢內容):

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

異構索引表的方式已經能解決90%以上的問題了,如果還不滿足,就要考慮其他專門用來查詢的方案了,如實時性要求不高可以用Hadoop,實時性要求高可以用記憶體資料庫,HBase等,這些不在咱們討論範圍,不深入講解。

據瞭解淘寶目前就是採用異構索引表的方式,不過他們不單單做了索引,而是完全兩套表,一套以訂單水平拆分,一套以使用者水平拆分,這樣應用查詢起來一個sql即可,非常簡單方便,不過代價就是資料同步的要求較高。異構索引表的同步也是一個問題,最好的方式有個資料同步服務,自動根據訂單表資訊同步索引表。

六、總結

做到資料庫的水平拆分,基本上就能夠實現資料庫效能的線性擴充套件了,未來資料量再大,通過增加節點就能夠達到。總結一下,我們從讀寫分離、分庫、分表討論到了簡單解決分表後的一些典型跨庫查詢方案,如下圖:

提高你的架構能力:資料庫拆分實現資料庫能力線性擴充套件

其實我們討論的還是很粗的,只是從大體的架構方案層面進行了討論,如果真去實現的話有大量的細節需要考慮,不是幾篇文章能夠說清楚的,這裡只是提供了通用思路,讓大家對資料庫效能設計,分庫分表,線性擴充套件有一個整體瞭解,具體什麼場景適合什麼分法,如何權衡利弊,可能就要依賴對業務的積累和長期磨練的經驗了。

補充一下,我們如果做設計,不要上來就分庫分表,分庫分表其實是最複雜的方案,往往是隨著資料量的提高而演進過來的,簡單說一下遇到效能問題我的大概思路:1、優先做硬體優化,例如從機械硬碟改成固態硬碟等,根據實際情況判斷2、資料庫層面調優操作,例如調整快取,增加索引,資料庫的很多的引數可以調整3、快取和其他技術,如redis,mongdb等,減少資料庫壓力4、程式與資料庫表優化,重構,sql優化等5、這些都不能優化效能的情況下,單表資料量千萬以上,再考慮分庫分表吧,也別上來分太多,逐漸擴大6、一定要根據業務考慮技術,根據場景,大部分的場景不需要太高實時性,不需要那麼強的一致性,我們都有很多可優化的地方,還有很多可用的新技術,拆庫拆表如果搞不好通常傷敵一千,自損八百。

最後,恭喜你看完了一篇4000多字的文章,希望能引起你思考,祝你有收穫。

https://www.jianshu.com/p/55b6abdd7b72