天天看點

“分庫分表

資料庫中間件之分庫分表

恭喜你,貴公司終于成長到一定規模,需要考慮高可用,甚至分庫分表了。但你是否知道分庫分表需要哪些要素?拆分過程是複雜的,提前計劃,不要等真正開工,各種意外的工作接踵而至,以至失控。

本文意圖打開資料庫中間件的廣度,而不考慮實作深度,至于庫表垂直和水準分的概念和緣由,不做過多解釋。是以此文面向的是有一定研發經驗,正在尋找選型和拆分流程的專業人士。

切入層次

以下,範圍界定在java和mysql中。我們首先來看一下分庫分表切入的層次。

在同一個項目中建立多個資料源,采用if else的方式,直接根據條件在代碼中路由。spring中有動态切換資料源的抽象類,具體參見abstractroutingdatasource。

如果項目不是很龐大,使用這種方式能夠快速的進行分庫。但缺點也是顯而易見的,需要編寫大量的代碼,照顧到每個分支。當涉及跨庫查詢、聚合,需要循環計算結果并合并的場景,工作量巨大。

如果項目裂變,此類代碼大多不能共用,大多通過拷貝共享。長此以往,碼将不碼。

這種情況适合公司orm架構統一的情況,但在很多情況下不太現實。主要是修改或增強現有orm架構的功能,在sql中增加一些自定義原語或者hint來實作。

通過實作一些攔截器(比如mybatis的interceptor接口),增加一些自定義解析來控制資料的流向,效果雖然較好,但會改變一些現有的程式設計經驗。

很多情況要修改架構源碼,不推薦。

基于在編碼層和架構層切入的各種缺點,真正的資料庫中間件起碼要從驅動層開始。什麼意思呢?其實就是重新編寫了一個jdbc的驅動,在記憶體中維護一個路由清單,然後将請求轉發到真正的資料庫連接配接中。

像tddl、shardingjdbc等,都是在此層切入。

包括mysql connector/j的failover協定

(具體指“load balancing”、“replication”、“farbic”等),

也是直接在驅動上進行修改。

請求流向一般是這樣的:

代理層的資料庫中間件,将自己僞裝成一個資料庫,接受業務端的連結。然後負載業務端的請求,解析或者轉發到真正的資料庫中。

像mysql router、mycat等,都是在此層切入。

sql特殊版本支援,如mysql cluster本身就支援各種特性,mariadb galera cluster支援對等雙主,greenplum支援分片等。

需要換存儲,一般是解決方案,就不在讨論之列了。

技術最終都會趨于一緻,選擇任何一種、都是可行的。但最終選型,受開發人員熟悉度、社群活躍度、公司切合度、官方維護度、擴充性,以及公司現有的資料庫産品等多方位因素影響。選擇或開發一款合适的,小夥伴們會幸福很多。

驅動層和代理層對比

通過以上層次描述,很明顯,我們選擇或開發中間件,就集中在驅動層和代理層。在這兩層,能夠對資料庫連接配接和路由進行更強的控制和更細緻的管理。但它們的差別也是明顯的。

驅動層中間件僅支援java一種開發語言,但支援所有後端關系型資料庫。如果你的開發語言固定,後端資料源類型豐富,推薦使用此方案。

驅動層中間件要維護很多資料庫連接配接。比如一個分了10個 庫 的表,每個java中的connection要維護10個資料庫連接配接。如果項目過多,則會出現連接配接爆炸(我們算一下,如果每個項目6個執行個體,連接配接池中minidle等于5,3個項目的連接配接總數是 10*6*5*3 = 900 個)。像postgres這種每個連接配接對應一個程序的資料庫,壓力會很大。

資料聚合,比如count sum等,是通過多次查詢,然後在業務執行個體的記憶體中進行聚合。

路由表存在于業務方執行個體記憶體中,通過輪詢或者被動通知的途徑更新路由表即可。

所有叢集的配置管理都集中在一個地方,運維負擔小,dba即可完成相關操作。

代理層中間件正好相反。僅支援一種後端關系型資料庫,但支援多種開發語言。如果你的系統是異構的,并且都有同樣的sla要求,則推薦使用此方案。

代理層需要維護資料庫連接配接數量有限(mysql router那種粘性連接配接除外)。但作為一個獨立的服務,既要考慮單獨部署,又要考慮高可用,會增加很多額外節點,更别提用了影子節點的公司了。

另外,代理層是請求唯一的入口,穩定性要求極高,一旦有高耗記憶體的聚合查詢把節點搞崩潰了,都是災難性的事故。

篇幅有限,不做過多讨論。通路各中間件宣傳頁面,能夠看到長長的feature清單,也就是白名單;也能看到長長的限制清單,也就是黑名單。限定了你怎麼玩,在增強了分布式能力後,分庫分表本身就是一個閹割的資料庫。

確定資料均衡 拆分資料庫的資料盡量均勻,比如按省份分user庫不均勻,按userid取模會比較均勻不用深分頁 不帶切分鍵的深分頁,會取出所有庫所取頁數之前的所有資料在記憶體排序計算。容易造成記憶體溢出。減少子查詢 子查詢會造成sql解析紊亂,解析錯誤的情況,盡量減少sql的子查詢。事務最小原則 盡量縮小單機事務涉及的庫範圍,即盡可能減少誇庫操作,将同類操作的庫/表分在一起資料均衡原則 拆分資料庫的資料盡量均勻,比如按省份分user庫不均勻,按userid取模會比較均勻特殊函數 distinct、having、union、in、or等,一般不被支援。或者被支援,使用之後會增加風險,需要改造。

建議聚焦在mycat和shardingjdbc上。另外,還有大量其他的中間件,不熟悉建議不要妄動。

資料庫中間件不好維護,你會發現大量半死不活的項目。

以下清單,排名不分先後,有幾個是隻有ha功能,沒有拆分功能的:

atlas、kingshard、dbproxy、mysql router、maxscale、58 oceanus、arkproxy、ctrip dal、tsharding、youtube vitess、網易ddb、heisenberg、proxysql、mango、ddal、datahekr、mtatlas、mtddl、zebra、cobar、cobar

汗、幾乎每個大廠都有自己的資料庫中間件(還發現了幾個喜歡拿開源元件加公司字首作為産品的),隻不過不給咱用罷了。

流程解決方案

無論是采用哪個層面切入進行分庫分表,都面臨以下工作過程。

項目範圍越大,分庫難度越高。有時候,一句複雜的sql能夠涉及四五個業務方,這種sql都是需要重點關注的。

确定分庫分表的規模,是隻分其中的幾張表,還是全部涉及。分的越多,工作量越大,幾乎是線性的。

還有一些項目是牽一發動全身的。舉個例子,下面這個過程,影響的鍊路就不僅是分庫這麼簡單了。

除了分庫分表元件的技術支援人員,最應該參與的是對系統、對現有代碼最熟悉的幾個人。隻有他們能夠确定哪些sql該廢棄掉、sql的影響面等。

确定分庫分表的次元和切分鍵。切分鍵(就是路由資料的column)一旦确定,是不允許修改的,是以在前期架構設計上,應該首先将其确立下來,才能進行後續的工作;資料次元多意味着有不同的切分鍵,達到不同條件查詢的效果。這涉及到資料的備援(多寫、資料同步),會更加複雜。

庫表結構不滿足需求,需要提前規整。比如,切分鍵的字段名稱不同或者類型各異。在實施分庫分表政策時,這些個性會造成政策過大不好維護。

将項目中所有的sql掃描出來,逐個判斷是否能夠按照切分鍵正常運作。

在判斷過程中肯定會有大量不合規的sql,則都需要給出改造方案,這是主要的工作量之一。

直接在原有項目上進行改動和驗證是可行的,但會遇到諸多問題,主要是效率太低。我傾向于首先設計一些驗證工具,輸入要驗證的sql或者清單,然後列印路由資訊和結果進行判斷。

建議以下提到的各個點,都找一個例子體驗一下,然後根據自己的團隊預估難度。

以下:

中間件所有不支援的sql類型

整理容易造成崩潰的注意事項

不支援的sql給出處理方式

考慮一個通用的主鍵生成器

考慮沒有切分鍵的sql如何處理

考慮定時任務等掃全庫的如何進行周遊

考慮跨庫跨表查詢如何改造

準備一些工具集

分庫分表會重新影響資料的分布,無論是全量還是增量,都會涉及到資料遷移,是以databus是必要的。

一種理想的狀态是所有的增删改都是消息,可以通過訂閱mq進行雙寫。

但一般情況下,仍然需要去模拟這個狀态,比如使用canal元件。

怎麼保證資料安全的切換,我們分其他章節進行讨論。

分庫分表必須經過充足的測試,每一句sql都要經過嚴格的驗證。如果有單元測試或者自動化測試工具,完全的覆寫是必要的。一旦有資料進行了錯誤的路由,尤其是增删改,将會創造大量的麻煩。

在測試階段,将驗證過程輸出到單獨的日志檔案,充足測試後review日志檔案是否有錯誤的資料流向。

強烈建議統一進行一次sql複驗。主要是根據功能描述,确定sql的正确性,也就是通常說的review。

在非線上環境多次對方案進行演練,確定萬無一失。

分庫分表以後,項目中的sql就加了枷鎖,不能夠随意書寫了。很多平常支援的操作,在拆分環境下就可能運作不了了。是以在上線前,涉及的sql都應該有一個确認過程,即使已經經過了充足的測試。

題外話

沒有支援的活别接,幹不成。

分庫分表是戰略性的技術方案,很多情況無法回退或者回退方案複雜。如果要拆分的庫表涉及多個業務方,公司技術人員複雜,cto要親自挂帥進行協調,并有專業仔細的架構師進行監督。沒有授權的協調人員會陷入尴尬的境地,導緻流程失控項目難産。

真正經曆過的人,會知道它的痛!

繼續閱讀