數(shù)據(jù)庫中間件詳解(精品長文)

我們在《

“分庫分表" ?選型和流程要慎重娄周,否則會失控

》這篇文章中涕侈,在廣度上和流程上總結了數(shù)據(jù)庫中間件的一些特征。而本篇長文煤辨,會在深度上解析數(shù)據(jù)庫中間件需要考慮的裳涛,一些非常重要的知識點,推薦收藏細讀众辨。如果你正在調研或者使用數(shù)據(jù)庫中間件端三,交叉閱讀會獲得較好的效果。


1?數(shù)據(jù)庫拆分過程及挑戰(zhàn)

互聯(lián)網(wǎng)當下的數(shù)據(jù)庫拆分過程基本遵循的順序是:垂直拆分鹃彻、讀寫分離技肩、分庫分表(水平拆分)。每個拆分過程都能解決業(yè)務上的一些問題浮声,但同時也面臨了一些挑戰(zhàn)。?

1.1 垂直拆分

剛開始旋奢,可能公司的技術團隊規(guī)模比較小泳挥,所有的數(shù)據(jù)都位于一個庫中。隨著公司業(yè)務的發(fā)展至朗,技術團隊人員也得到了擴張屉符,劃分為不同的技術小組,不同的小組負責不同的業(yè)務模塊锹引。例如A小組負責用戶模塊矗钟,B小組負責產(chǎn)品模塊。此時數(shù)據(jù)庫也迎來了第一次拆分:垂直拆分嫌变。

這里的垂直拆分吨艇,指的是將一個包含了很多表的數(shù)據(jù)庫,根據(jù)表的功能的不同腾啥,拆分為多個小的數(shù)據(jù)庫东涡,每個庫包含部分表。下圖演示將上面提到的db_eshop庫倘待,拆分為db_user庫和db_product庫疮跑。

通常來說,垂直拆分凸舵,都是根據(jù)業(yè)務來對一個庫中的表進行拆分的祖娘。關于垂直拆分,還有另一種說法啊奄,將一個包含了很多字段的大表拆分為多個小表渐苏,每個表包含部分字段掀潮,這種情況在實際開發(fā)中基本很少遇到。

垂直拆分的另一個典型應用場景是服務化(SOA)改造整以。在服務化的背景下胧辽,除了業(yè)務上需要進行拆分,底層的存儲也需要進行隔離公黑。?垂直拆分會使得單個用戶請求的響應時間變長邑商,原因在于,在單體應用的場景下凡蚜,所有的業(yè)務都可以在一個節(jié)點內部完成人断,而垂直拆分之后,通常會需要進行RPC調用朝蜘。然后雖然單個請求的響應時間增加了恶迈,但是整個服務的吞吐量卻會大大的增加。

1.2 讀寫分離

?隨著業(yè)務的不斷發(fā)展谱醇,用戶數(shù)量和并發(fā)量不斷上升暇仲。這時如果僅靠單個數(shù)據(jù)庫實例來支撐所有訪問壓力,幾乎是在?自尋死路?。以產(chǎn)品庫為例副渴,可能庫中包含了幾萬種商品奈附,并且每天新增幾十種,而產(chǎn)品庫每天的訪問了可能有幾億甚至幾十億次煮剧。數(shù)據(jù)庫讀的壓力太大斥滤,單臺mysql實例扛不住,此時大部分 Mysql DBA 就會將數(shù)據(jù)庫設置成?讀寫分離狀態(tài)?勉盅。也就是一個 Master 節(jié)點(主庫)對應多個 Salve 節(jié)點(從庫)佑颇。可以將slave節(jié)點的數(shù)據(jù)理解為master節(jié)點數(shù)據(jù)的全量備份草娜。

master節(jié)點接收用戶的寫請求挑胸,并寫入到本地二進制文件(binary log)中。slave通過一個I/O線程與Master建立連接宰闰,發(fā)送binlog dump指令嗜暴。Master會將binlog數(shù)據(jù)推送給slave,slave將接收到的binlog保存到本地的中繼日志(relay log)中议蟆,最后闷沥,slave通過另一個線程SQL thread應用本地的relay log,將數(shù)據(jù)同步到slave庫中咐容。

? ? ? ??關于mysql主從復制舆逃,內部包含很多細節(jié)。例如binlog 格式分為statement、row和mixed路狮,binlog同步方式又可以劃分為:異步半同步同步涂籽。復制可以基于binlogFile+position评雌,也可以基于GTID景东。通常,這些都是DBA負責維護的奔誓,業(yè)務RD無感知斤吐。

在DBA將mysql配置成主從復制集群的背景下,開發(fā)同學所需要做的工作是:當更新數(shù)據(jù)時厨喂,應用將數(shù)據(jù)寫入master主庫和措,主庫將數(shù)據(jù)同步給多個slave從庫。當查詢數(shù)據(jù)時蜕煌,應用選擇某個slave節(jié)點讀取數(shù)據(jù)派阱。

1.2.1 讀寫分離的優(yōu)點

這樣通過配置多個slave節(jié)點,可以有效的避免過大的訪問量對單個庫造成的壓力幌绍。

1.2.1 讀寫分離的挑戰(zhàn)

1 對于DBA而言,多了很多集群運維工作

例如集群搭建故响、主從切換傀广、從庫擴容、縮容等彩届。例如master配置了多個slave節(jié)點伪冰,如果其中某個slave節(jié)點掛了,那么之后的讀請求樟蠕,我們應用將其轉發(fā)到正常工作的slave節(jié)點上贮聂。另外,如果新增了slave節(jié)點耻警,應用也應該感知到腮恩,可以將讀請求轉發(fā)到新的slave節(jié)點上募判。

2 對于開發(fā)人員而言

  • 基本讀寫分離功能:對sql類型進行判斷内颗,如果是select等讀請求,就走從庫找前,如果是insert、update槽惫、delete等寫請求合冀,就走主庫峭判。

  • 主從數(shù)據(jù)同步延遲問題:因為數(shù)據(jù)是從master節(jié)點通過網(wǎng)絡同步給多個slave節(jié)點,因此必然存在延遲治宣。因此有可能出現(xiàn)我們在master節(jié)點中已經(jīng)插入了數(shù)據(jù),但是從slave節(jié)點卻讀取不到的問題铝宵。對于一些強一致性的業(yè)務場景,要求插入后必須能讀取到,因此對于這種情況百拓,我們需要提供一種方式角撞,讓讀請求也可以走主庫扁远,而主庫上的數(shù)據(jù)必然是最新的并闲。

  • 事務問題:如果一個事務中同時包含了讀請求(如select)和寫請求(如insert)纹冤,如果讀請求走從庫,寫請求走主庫靠瞎,由于跨了多個庫,那么本地事務已經(jīng)無法控制神凑,屬于分布式事務的范疇爱榕。而分布式事務非常復雜且效率較低。因此對于讀寫分離趴酣,目前主流的做法是柜蜈,事務中的所有sql統(tǒng)一都走主庫,由于只涉及到一個庫狸吞,本地事務就可以搞定。

  • 感知集群信息變更:如果訪問的數(shù)據(jù)庫集群信息變更了威始,例如主從切換了,寫流量就要到新的主庫上木西;又例如增加了從庫數(shù)量叼丑,流量需要可以打到新的從庫上星立;又或者某個從庫延遲或者失敗率比較高火焰,應該將這個從庫進行隔離纯赎,讀流量盡量打到正常的從庫上?

  • 1.3 分庫分表

    ?? ?? ??經(jīng)過垂直分區(qū)后的 Master/Salve 模式完全可以承受住難以想象的高并發(fā)訪問操作,但是否可以永遠 高枕無憂 了犬金?答案是否定的念恍,一旦業(yè)務表中的數(shù)據(jù)量大了,從維護和性能角度來看晚顷,無論是任何的 CRUD 操作峰伙,對于數(shù)據(jù)庫而言都是一件極其耗費資源的事情词爬。即便設置了索引囊咏, 仍然無法掩蓋因為數(shù)據(jù)量過大從而導致的數(shù)據(jù)庫性能下降的事實 ,因此這個時候 Mysql DBA 或許就該對數(shù)據(jù)庫進行 水平分區(qū) (sharding朱沃,即分庫分表?)。經(jīng)過水平分區(qū)設置后的業(yè)務表,必然能夠將原本一張表維護的海量數(shù)據(jù)分配給 N 個子表進行存儲和維護建炫。

    ????????水平分表從具體實現(xiàn)上又可以分為3種:只分表稳捆、只分庫剃幌、分庫分表,下圖展示了這三種情況:


    ?只分表:

    ?? ?? ? 將db庫中的user表拆分為2個分表晨汹,user_0和user_1,這兩個表還位于同一個庫中霞捡。適用場景:如果庫中的多個表中只有某張表或者少數(shù)表數(shù)據(jù)量過大婶博,那么只需要針對這些表進行拆分,其他表保持不變。

    只分庫:

    ?? ?? ? 將db庫拆分為db_0和db_1兩個庫,同時在db_0和db_1庫中各自新建一個user表发魄,db_0.user表和db_1.user表中各自只存原來的db.user表中的部分數(shù)據(jù)。

    分庫分表:

    ?? ??? ?將db庫拆分為db_0和db_1兩個庫蟹演,db_0中包含user_0风钻、user_1兩個分表,db_1中包含user_2酒请、user_3兩個分表骡技。下圖演示了在分庫分表的情況下,數(shù)據(jù)是如何拆分的:假設db庫的user表中原來有4000W條數(shù)據(jù)羞反,現(xiàn)在將db庫拆分為2個分庫db_0和db_1布朦,user表拆分為user_0、user_1苟弛、user_2喝滞、user_3四個分表阁将,每個分表存儲1000W條數(shù)據(jù)膏秫。


    1.3.1 分庫分表的好處

    如果說讀寫分離實現(xiàn)了數(shù)據(jù)庫讀能力的水平擴展,那么分庫分表就是實現(xiàn)了寫能力的水平擴展做盅。???

    1 存儲能力的水平擴展

    在讀寫分離的情況下缤削,每個集群中的master和slave基本上數(shù)據(jù)是完全一致的,從存儲能力來說吹榴,在存在海量數(shù)據(jù)的情況下亭敢,可能由于磁盤空間的限制,無法存儲所有的數(shù)據(jù)图筹。而在分庫分表的情況下帅刀,我們可以搭建多個mysql主從復制集群,每個集群只存儲部分分片的數(shù)據(jù)远剩,實現(xiàn)存儲能力的水平擴展扣溺。

    2 寫能力的水平擴展

    在讀寫分離的情況下,由于每個集群只有一個master瓜晤,所有的寫操作壓力都集中在這一個節(jié)點上锥余,在寫入并發(fā)非常高的情況下,這里會成為整個系統(tǒng)的瓶頸痢掠。

    而在分庫分表的情況下驱犹,每個分片所屬的集群都有一個master節(jié)點,都可以執(zhí)行寫入操作足画,實現(xiàn)寫能力的水平擴展雄驹。此外減小建立索引開銷,降低寫操作的鎖操作耗時等淹辞,都會帶來很多顯然的好處荠医。?

    1.3.2 分庫分表的挑戰(zhàn)

    分庫分表的挑戰(zhàn)主要體現(xiàn)在4個方面:基本的數(shù)據(jù)庫增刪改功能,分布式id,分布式事務彬向,動態(tài)擴容兼贡,下面逐一進行講述。?

    挑戰(zhàn)1:基本的數(shù)據(jù)庫增刪改功能? ?

    對于開發(fā)人員而言娃胆,雖然分庫分表的遍希,但是其還是希望能和單庫單表那樣的去操作數(shù)據(jù)庫。例如我們要批量插入四條用戶記錄里烦,并且希望根據(jù)用戶的id字段凿蒜,確定這條記錄插入哪個庫的哪張表。例如1號記錄插入user1表胁黑,2號記錄插入user2表废封,3號記錄插入user3表,4號記錄插入user0表丧蘸,以此類推漂洋。sql如下所示:

    insert?into?user(id,name)?values?(1,”tianshouzhi”),(2,”huhuamin”),?(3,”wanghanao”),(4,”luyang”)

    ?這樣的sql明顯是無法執(zhí)行的,因為我們已經(jīng)對庫和表進行了拆分,這種sql語法只能操作mysql的單個庫和單個表力喷。所以必須將sql改成4條如下所示刽漂,然后分別到每個庫上去執(zhí)行。

    insert?into?user0(id,name)?values??(4,”luyang”)

    insert?into?user1(id,name)?values?(1,”tianshouzhi”)

    insert?into?user2(id,name)?values?(2,”huhuamin”)

    insert?into?user3(id,name)?values?(3,”wanghanao”)

    具體流程可以用下圖進行描述:

    解釋如下:

    ????sql解析:首先對sql進行解析弟孟,得到需要插入的四條記錄的id字段的值分別為1,2,3,4

    ????sql路由:sql路由包括庫路由和表路由贝咙。庫路由用于確定這條記錄應該插入哪個庫,表路由用于確定這條記錄應該插入哪個表拂募。

    ??? sql改寫:因為一條記錄只能插入到一個庫中庭猩,而上述批量插入的語法將會在 每個庫中都插入四條記錄,明顯是不合適的陈症,因此需要對sql進行改寫蔼水,每個庫只插入一條記錄。

    ????sql執(zhí)行:一條sql經(jīng)過改寫后變成了多條sql爬凑,為了提升效率應該并發(fā)的到不同的庫上去執(zhí)行徙缴,而不是按照順序逐一執(zhí)行

    ????結果集合并:每個sql執(zhí)行之后,都會有一個執(zhí)行結果嘁信,我們需要對分庫分表的結果集進行合并于样,從而得到一個完整的結果。

    挑戰(zhàn)2:分布式id

    在分庫分表后潘靖,我們不能再使用mysql的自增主鍵穿剖。因為在插入記錄的時候,不同的庫生成的記錄的自增id可能會出現(xiàn)沖突卦溢。因此需要有一個全局的id生成器糊余。目前分布式id有很多種方案秀又,其中一個比較輕量級的方案是twitter的snowflake算法。

    挑戰(zhàn)3:分布式事務

    分布式事務是分庫分表繞不過去的一個坎贬芥,因為涉及到了同時更新多個分片數(shù)據(jù)吐辙。例如上面的批量插入記錄到四個不同的庫,如何保證要么同時成功蘸劈,要么同時失敗昏苏。關于分布式事務,mysql支持XA事務威沫,但是效率較低贤惯。柔性事務是目前比較主流的方案,柔性事務包括:最大努力通知型棒掠、可靠消息最終一致性方案以及TCC兩階段提交孵构。但是無論XA事務還是柔性事務,實現(xiàn)起來都是非常復雜的烟很。

    挑戰(zhàn)4:動態(tài)擴容

    動態(tài)擴容指的是增加分庫分表的數(shù)量颈墅。例如原來的user表拆分到2個庫的四張表上。現(xiàn)在我們希望將分庫的數(shù)量變?yōu)?個溯职,分表的數(shù)量變?yōu)?個精盅。這種情況下一般要伴隨著數(shù)據(jù)遷移帽哑。例如在4張表的情況下谜酒,id為7的記錄,7%4=3妻枕,因此這條記錄位于user3這張表上僻族。但是現(xiàn)在分表的數(shù)量變?yōu)榱?個,而7%8=0屡谐,而user0這張表上根本就沒有id=7的這條記錄述么,因此如果不進行數(shù)據(jù)遷移的話,就會出現(xiàn)記錄找不到的情況愕掏。本教程后面將會介紹一種在動態(tài)擴容時不需要進行數(shù)據(jù)遷移的方案度秘。

    1.4 小結

    在上面我們已經(jīng)看到了,讀寫分離和分庫分表帶來的好處饵撑,但是也面臨了極大的挑戰(zhàn)剑梳。如果由業(yè)務開發(fā)人員來完成這些工作,難度比較大滑潘。因此就有一些公司專門來做一些數(shù)據(jù)庫中間件垢乙,對業(yè)務開發(fā)人員屏蔽底層的繁瑣細節(jié),開發(fā)人員使用了這些中間件后语卤,不論是讀寫分離還是分庫分表追逮,都可以像操作單庫單表那樣去操作酪刀。

    ? ? 下面,我們將介紹 主流的數(shù)據(jù)庫中間件設計方案和實現(xiàn)钮孵。?

    2 主流數(shù)據(jù)庫中間件設計方案

    ?? ??? ?數(shù)據(jù)庫中間件的主要作用是向應用程序開發(fā)人員屏蔽讀寫分離和分庫分表面臨的挑戰(zhàn)骂倘,并隱藏底層實現(xiàn)細節(jié),使得開發(fā)人員可以像操作單庫單表那樣去操作數(shù)據(jù)巴席。在介紹分庫分表的主流設計方案前稠茂,我們首先回顧一下在單個庫的情況下,應用的架構情妖,可以用下圖進行描述:?

    可以看到在操作單庫單表的情況下睬关,我們是直接在應用中通過數(shù)據(jù)連接池(connection pool)與數(shù)據(jù)庫建立連接,進行讀寫操作毡证。而對于讀寫分離和分庫分表电爹,應用都要操作多個數(shù)據(jù)庫實例,在這種情況下料睛,我們就需要使用到數(shù)據(jù)庫中間件丐箩。

    2.1 設計方案

    ?? ?典型的數(shù)據(jù)庫中間件設計方案有2種:proxy、smart-client恤煞。下圖演示了這兩種方案的架構:

    ?? ?可以看到不論是proxy還是smart-client屎勘,底層都操作了多個數(shù)據(jù)庫實例。不論是分庫分表居扒,還是讀寫分離概漱,都是在數(shù)據(jù)庫中間件層面對業(yè)務開發(fā)同學進行屏蔽。

    2.1.1 proxy模式

    我們獨立部署一個代理服務喜喂,這個代理服務背后管理多個數(shù)據(jù)庫實例瓤摧。而在應用中,我們通過一個普通的數(shù)據(jù)源(c3p0玉吁、druid照弥、dbcp等)與代理服務器建立連接,所有的sql操作語句都是發(fā)送給這個代理进副,由這個代理去操作底層數(shù)據(jù)庫这揣,得到結果并返回給應用。在這種方案下影斑,分庫分表和讀寫分離的邏輯對開發(fā)人員是完全透明的给赞。

    優(yōu)點:

    1?多語言支持。也就是說鸥昏,不論你用的php塞俱、java或是其他語言,都可以支持吏垮。以mysql數(shù)據(jù)庫為例障涯,如果proxy本身實現(xiàn)了mysql的通信協(xié)議罐旗,那么你可以就將其看成一個mysql 服務器。mysql官方團隊為不同語言提供了不同的客戶端卻動唯蝶,如java語言的mysql-connector-java九秀,python語言的mysql-connector-python等等。因此不同語言的開發(fā)者都可以使用mysql官方提供的對應的驅動來與這個代理服務器建通信粘我。

    2?對業(yè)務開發(fā)同學透明鼓蜒。由于可以把proxy當成mysql服務器,理論上業(yè)務同學不需要進行太多代碼改造征字,既可以完成接入都弹。

    缺點:

    1 實現(xiàn)復雜。因為proxy需要實現(xiàn)被代理的數(shù)據(jù)庫server端的通信協(xié)議匙姜,實現(xiàn)難度較大畅厢。通常我們看到一些proxy模式的數(shù)據(jù)庫中間件,實際上只能代理某一種數(shù)據(jù)庫氮昧,如mysql框杜。幾乎沒有數(shù)據(jù)庫中間件,可以同時代理多種數(shù)據(jù)庫(sqlserver袖肥、PostgreSQL咪辱、Oracle)。

    2 proxy本身需要保證高可用椎组。由于應用本來是直接訪問數(shù)據(jù)庫油狂,現(xiàn)在改成了訪問proxy,意味著proxy必須保證高可用庐杨。否則选调,數(shù)據(jù)庫沒有宕機夹供,proxy掛了灵份,導致數(shù)據(jù)庫無法正常訪問,就尷尬了哮洽。?

    3 租戶隔離填渠。可能有多個應用訪問proxy代理的底層數(shù)據(jù)庫鸟辅,必然會對proxy自身的內存氛什、網(wǎng)絡、cpu等產(chǎn)生資源競爭匪凉,proxy需要需要具備隔離的能力枪眉。


    2.1.2 smart-client模式

    ????????業(yè)務代碼需要進行一些改造,引入支持讀寫分離或者分庫分表的功能的sdk再层,這個就是我們的smart-client贸铜。通常smart-client是在連接池或者driver的基礎上進行了一層封裝堡纬,smart-client內部與不同的庫建立連接。應用程序產(chǎn)生的sql交給smart-client進行處理蒿秦,其內部對sql進行必要的操作烤镐,例如在讀寫分離情況下,選擇走從庫還是主庫棍鳖;在分庫分表的情況下炮叶,進行sql解析、sql改寫等操作渡处,然后路由到不同的分庫镜悉,將得到的結果進行合并,返回給應用医瘫。

    優(yōu)點:

    1 實現(xiàn)簡單积瞒。proxy需要實現(xiàn)數(shù)據(jù)庫的服務端協(xié)議,但是smart-client不需要實現(xiàn)客戶端通信協(xié)議登下。原因在于茫孔,大多數(shù)據(jù)數(shù)據(jù)庫廠商已經(jīng)針對不同的語言提供了相應的數(shù)據(jù)庫驅動driver,例如mysql針對java語言提供了mysql-connector-java驅動被芳,針對python提供了mysql-connector-python驅動缰贝,客戶端的通信協(xié)議已經(jīng)在driver層面做過了。因此smart-client模式的中間件畔濒,通常只需要在此基礎上進行封裝即可剩晴。

    2 天然去中心化。smart-client的方式侵状,由于本身以sdk的方式赞弥,被應用直接引入,隨著應用部署到不同的節(jié)點上趣兄,且直連數(shù)據(jù)庫绽左,中間不需要有代理層。因此相較于proxy而言艇潭,除了網(wǎng)絡資源之外拼窥,基本上不存在任何其他資源的競爭,也不需要考慮高可用的問題蹋凝。只要應用的節(jié)點沒有全部宕機鲁纠,就可以訪問數(shù)據(jù)庫。(這里的高可用是相比proxy而言鳍寂,數(shù)據(jù)庫本身的高可用還是需要保證的)

    缺點:

    1 通常僅支持某一種語言改含。例如tddl、zebra迄汛、sharding-jdbc都是使用java語言開發(fā)捍壤,因此對于使用其他語言的用戶刃唤,就無法使用這些中間件。如果其他語言要使用白群,那么就要開發(fā)多語言客戶端尚胞。

    2 版本升級困難。因為應用使用數(shù)據(jù)源代理就是引入一個jar包的依賴帜慢,在有多個應用都對某個版本的jar包產(chǎn)生依賴時笼裳,一旦這個版本有bug,所有的應用都需要升級粱玲。而數(shù)據(jù)庫代理升級則相對容易躬柬,因為服務是單獨部署的,只要升級這個代理服務器抽减,所有連接到這個代理的應用自然也就相當于都升級了允青。


    2.2 業(yè)界產(chǎn)品

    無論是proxy,還是smart-client卵沉,二者的作用都是類似的颠锉。以下列出了這兩種方案目前已有的實現(xiàn)以及各自的優(yōu)缺點:

    proxy實現(xiàn)

    目前的已有的實現(xiàn)方案有:

  • 阿里巴巴開源的cobar

  • 阿里云上的drds

  • mycat團隊在cobar基礎上開發(fā)的mycat

  • mysql官方提供的mysql-proxy

  • 奇虎360在mysql-proxy基礎開發(fā)的atlas(只支持分表,不支持分庫)

  • 當當網(wǎng)開源的sharing-sphere

  • 目前除了mycat史汗、sharing-sphere琼掠,其他幾個開源項目基本已經(jīng)沒有維護,sharing-sphere前一段時間已經(jīng)進去了Apache 軟件基金會孵化器停撞。

    smart-client實現(xiàn)

    目前的實現(xiàn)方案有:

  • 阿里巴巴開源的tddl瓷蛙,已很久沒維護

  • 大眾點評開源的zebra,大眾點評的zebra開源版本代碼已經(jīng)很久沒有更新戈毒,不過最近美團上市艰猬,重新開源大量內部新的功能特性,并計劃長期維持埋市。

  • 當當網(wǎng)開源的sharding-jdbc冠桃,目前算是做的比較好的,文檔資料比較全恐疲。和sharding-sphere一起進入了Apache孵化器腊满。

  • 螞蟻金服的zal

  • 等等

  • 3 讀寫分離核心要點

    3.1 基本路由功能

    基本路由路功能主要是解決,在讀寫分離的情況下培己,如何實現(xiàn)一些基本的路由功能,這個過程通撑呙冢可以通過下圖進行描述:

    3.1.1 sql類型判斷

    主要是判斷出來sql是讀還是寫sql省咨,將讀sql到從庫上去執(zhí)行,寫sql去主庫上執(zhí)行

    write語句:insert玷室、update零蓉、delete笤受、create、alter敌蜂、truncate…

    query語句:select箩兽、show、desc章喉、explain…?

    3.1.2?強制走主庫

    有的時候汗贫,對于一些強一致性的場景,需要寫入后秸脱,必須能讀取到數(shù)據(jù)落包。由于主從同步存在延遲墓造,可能會出現(xiàn)主庫寫入模她,而從庫查不到的情況。這次時候鸭栖,我們需要使用強制走主庫的功能巷查。具體實現(xiàn)上有2種方案:hint 或API

    ? ??hint有序,就是開發(fā)人員在sql上做一些特殊的標記,數(shù)據(jù)庫中間件識別到這個標記岛请,就知道這個sql需要走主庫笔呀,如:?

    /*master*/select?*?from?table_xx

    這里的/*master*/就是一個hint,表示需要走主庫髓需。不同的數(shù)據(jù)庫中間件強制走主庫的hint可能不同许师,例如zebra的hint為/*zebra:w+*/,hint到底是什么樣是無所謂的僚匆,其作用僅僅就是一個標記而已微渠。之所以將hint寫在/*…*/中,是因為這是標準的sql注釋語法咧擂。即使數(shù)據(jù)庫中間件未能識別這個hint逞盆,也不會導致sql語法錯誤。

    api:主要是通過代碼的方式來添加sql走主庫的標識松申,hint通常只能加在某個sql上云芦。如果我們希望多個sql同時都走主庫,也不希望加hint贸桶,則可以通過api的方式舅逸,其內部主要利用語言的thread local線程上下文特性,如:

    ForceMasterHelper.forceMaster()    //…執(zhí)行多條sqlForceMasterHelper.clear()

    在api標識范圍內執(zhí)行的sql皇筛,都會走主庫琉历。具體API到底應該是什么樣,如何使用,也是由相應的數(shù)據(jù)庫中間件來決定的旗笔。

    特別的彪置,對于一些特殊的sql,例如 select last_insert_id蝇恶;或者select @@identity等拳魁,這類sql總是需要走主庫。這些sql是要獲得最后一個插入記錄的id撮弧,插入操作只可能發(fā)生在主庫上潘懊。

    3.2 從庫路由策略

    通常在一個集群中,只會有一個master想虎,但是有多個slave卦尊。當判斷是一個讀請求時,如何判斷選擇哪個slave呢舌厨?

    一些簡單的選擇策略包括:

  • 隨機選擇(random)

  • 按照權重進行選擇(weight)

  • 或者輪訓(round-robin)

  • 特別的岂却,對于一些跨IDC(數(shù)據(jù)中心)部署的數(shù)據(jù)庫集群,通常需要有就近路由的策略裙椭,如下圖:?

    圖中躏哩,在IDC2部署了一個master,在IDC1和IDC2各部署了一個slave揉燃,應用app部署在IDC1扫尺。顯然當app接收到一個查詢請求時,應該優(yōu)先查詢與其位于同一個數(shù)據(jù)中心的slave1炊汤,而不是跨數(shù)據(jù)中心去查詢slave2正驻,這就是就近路由的概念。

    ? ? ? 當然一個數(shù)據(jù)中心內抢腐,可能會部署多個slave姑曙,也需要進行選擇,因此就近路由通常和一些基本的路由策略結合使用迈倍。另外伤靠,對于就近路由,通常也會有一個層級啼染,例如同機房宴合、同中心、同區(qū)域迹鹅、跨區(qū)域等卦洽。?

    3.3 HA、Scalable相關

    數(shù)據(jù)庫中間件除了需要具備上述提到的讀寫分離功能來訪問底層的數(shù)據(jù)庫集群徒欣。也需要一套支持高可用逐样、動態(tài)擴展的體系:

  • 從HA的角度來說,例如主庫宕機了打肝,那么應該從從庫選擇一個作為新的主庫脂新。開源的MHA可以幫助我們完成這個事;然而粗梭,MHA只能在主庫宕機的情況下争便,完成主從切換,對于僅僅是一個從庫宕機的情況下断医,MHA通常是無能為力的滞乙。因此,通常都會在MHA進行改造鉴嗤,使其支持更多的HA能力要求斩启。

  • 從Scalable角度來說,例如讀qps實在太高醉锅,需要加一些從庫兔簇,來分擔讀流量。

  • ?? ??? ?事實上硬耍,無論是HA垄琐,還是Scalable,對于數(shù)據(jù)庫中間件(不論是proxy或者smart-client)來說经柴,只是配置信息發(fā)生了變更狸窘。?

    ? ? ? ? 因此,通常我們會將所有的配置變更信息寫到一個配置中心坯认,然后配置心中監(jiān)聽這個配置的變更翻擒,例如主從切換,只需要把最新的主從信息設置到配置中心牛哺;增加從庫陋气,把新從庫ip、port等信息放到配置中心荆隘。數(shù)據(jù)庫中間件通過對這些配置信息變更進行監(jiān)聽恩伺,當配置發(fā)生變更時,實時的應用最新的配置信息即可椰拒。

    ?? ?? ? 因此晶渠,一個簡化的數(shù)據(jù)庫中間件的高可用架構通常如下所示:?

    監(jiān)控服務對集群進行監(jiān)控,當發(fā)生變更時燃观,將變更的信息push到配置中心中褒脯,數(shù)據(jù)庫中間件(proxy或smart-client)接收到配置變更,應用最新的配置缆毁。而整個過程番川,對于業(yè)務代碼基本是無感知的。

    對于配置中心的選擇,有很多颁督,例如百度的disconf践啄、阿里的diamond、點評開源的lion沉御、攜程開源的apollo等屿讽,也可以使用etcd、consul吠裆。通常如果沒有歷史包袱的話伐谈,建議使用攜程開源的apollo。

    特別需要注意的一點是试疙,通常監(jiān)控服務監(jiān)控到集群信息變更诵棵,推送到配置中心,再到數(shù)據(jù)庫中間件祝旷,必然存在一些延遲履澳。對于一些場景,例如主從切換缓屠,沒有辦法做到徹底的業(yè)務無感知奇昙。當然,對于多個從庫中敌完,某個從庫宕機的情況下储耐,是可以做到業(yè)務無感知的。例如滨溉,某個從庫失敗什湘,數(shù)據(jù)庫中間件,自動從其他正常的從庫進行重試晦攒。

    另外闽撤,上圖中的HA方案強依賴于配置中心,如果某個數(shù)據(jù)庫集群上建立了很多庫脯颜,這個集群發(fā)生變更時哟旗,將會存在大量的配置信息需要推送。又或者栋操,如果數(shù)據(jù)庫集群是多機房部署的闸餐,在某個機房整體宕機的情況下(例如光纖被挖斷了,或者機房宕機演練)矾芙,也會存在大量的配置信息需要推送舍沙。如果配置中心,推送有延遲剔宪,業(yè)務會有非常明顯的感知拂铡。

    因此壹无,通常我們會在客戶端進行一些輕量級的HA保障。例如感帅,根據(jù)數(shù)據(jù)庫返回異常的sqlstate和vendor code斗锭,判斷異常的嚴重級別,確定數(shù)據(jù)庫實例能否正常提供服務留瞳,如果不能正常提供服務拒迅,則自動將其進行隔離骚秦,并啟動異步線程進行檢測數(shù)據(jù)庫實例是否恢復她倘。

    最后,很多數(shù)據(jù)庫中間件作箍,也會提供一些限流降級的功能硬梁,計算sql的唯一標識(有些稱之為sql指紋),對于一些爛sql胞得,導致數(shù)據(jù)庫壓力變大的情況荧止,可以實時的進行攔截,直接拋出異常阶剑,不讓這些sql打到后端數(shù)據(jù)庫上去跃巡。?

    4 分庫分表核心要點

    從業(yè)務開發(fā)的角度來說,其不關心底層是否是分庫分表了牧愁,其還是希望想操作單個數(shù)據(jù)庫實例那樣編寫sql素邪,那么數(shù)據(jù)庫中間件就需要對其屏蔽所有底層的復雜邏輯。

    下圖演示了一個數(shù)據(jù)庫表(user表)在分庫分表情況下猪半,數(shù)據(jù)庫中間件內部是如何執(zhí)行一個批量插入sql的:

    數(shù)據(jù)庫中間件主要對應用屏蔽了以下過程:

  • sql解析:首先對sql進行解析兔朦,得到抽象語法樹,從語法樹中得到一些關鍵sql信息

  • sql路由:sql路由包括庫路由和表路由磨确。庫路由用于確定這條記錄應該操作哪個分庫沽甥,表路由用于確定這條記錄應該操作哪個分表。

  • sql改寫:將sql改寫成正確的執(zhí)行方式乏奥。例如摆舟,對于一個批量插入sql,同時插入4條記錄邓了。但實際上用戶希望4個記錄分表存儲到一個分表中恨诱,那么就要對sql進行改寫成4條sql,每個sql都只能插入1條記錄驶悟。

  • sql執(zhí)行:一條sql經(jīng)過改寫后可能變成了多條sql胡野,為了提升效率應該并發(fā)的去執(zhí)行,而不是按照順序逐一執(zhí)行

  • 結果集合并:每個sql執(zhí)行之后痕鳍,都會有一個執(zhí)行結果硫豆,我們需要對分庫分表的結果集進行合并龙巨,從而得到一個完整的結果。?

  • 4.1 SQL解析

    ?? ?? ? 用戶執(zhí)行只是一條sql熊响,并傳入相關參數(shù)旨别。數(shù)據(jù)庫中間件內部需要通過sql解析器,對sql進行解析汗茄〗粘冢可以將sql解析,類比為xml解析洪碳,xml解析的最終結果是得到一個document對象递览,而sql解析最終得到一個抽象語法樹(AST)。通過這個語法樹瞳腌,我們可以很簡單的獲取到sql的一些執(zhí)行绞铃,例如當前執(zhí)行的sql類型,查詢了那些字段嫂侍,數(shù)據(jù)庫表名儿捧,where條件,sql的參數(shù)等一系列信息挑宠。

    ?? ??? ?通常來說菲盾,對于sql解析,內部需要經(jīng)過詞法(lex)解析和語法(Syntax)解析兩個階段各淀,最終得到一個語法樹懒鉴。?

    SQL解析器的內部實現(xiàn)原理對業(yè)務同學是屏蔽的,業(yè)務同學也感知不到揪阿。一些數(shù)據(jù)庫中間件采用了第三方開源的sql解析器疗我,也有一些自研sql解析器。例如mycat南捂、zebra采用的都是druid解析器吴裤,shard-jdbc一開始也用的是druid解析器,后面自研了解析器溺健。目前較為流行的sql解析器包括:

  • FoundationDB SQL Parser

  • Jsqlparser

  • Druid SQL Parser

  • ?? ??? ?其中麦牺,其中Fdbparser和jsqlparser都是基于javacc實現(xiàn)的。

    ?? ??? ?mycat團隊曾經(jīng)做過一個性能測試鞭缭,druid解析器的解析性能通常能達到基于javacc生成的sql解析器10~20倍剖膳。本人也進行過類似的測試,得出的結論基本一致岭辣。

    ?? ?? ? 如何對比不同的sql解析器的好壞呢吱晒?主要是考慮以下兩點:

    解析性能:druid最好。

    druid采用的是預測分析法沦童,它只需要從字符的第一個到最后一個遍歷一遍仑濒,就同時完成了詞法解析和語法解析叹话,語法樹也已經(jīng)構造完成。

    數(shù)據(jù)庫方言:druid支持的最多墩瞳。

    SQL-92驼壶、SQL-99等都是標準SQL,mysql/oracle/pg/sqlserver/odps等都是方言喉酌,sql-parser需要針對不同的方言進行特別處理热凹。Druid的sql parser是目前支持各種數(shù)據(jù)語法最完備的SQL Parser。

    注:這里說的僅僅是基于Java實現(xiàn)的SQL解析器泪电,druid是比較好的般妙。大部分同學可能知道druid是一個為監(jiān)控而生的連接池,事實上歪架,druid另一大特性股冗,就是它的SQL解析器。很多開源的數(shù)據(jù)庫中間件和蚪,例如zebra、sharding-jdbc等烹棉,都使用了druid解析器攒霹。(sharding-jdbc后來自研了解析器)。雖然SQL解析是druid的一大亮點浆洗,不過github上也因為SQL解析的bug催束,收到了不少issue。

    4.2?SQL路由

    ?? ??? ?路由規(guī)則是分庫分表的基礎伏社,其規(guī)定了數(shù)據(jù)應該按照怎樣的規(guī)則路由到不同的分庫分表中抠刺。對于一個數(shù)據(jù)庫中間件來說,通常是支持用戶自定義任何路由規(guī)則的摘昌。路由規(guī)則本質上是一個腳本表達式速妖,數(shù)據(jù)庫中間件通過內置的腳本引擎對表達式進行計算,確定最終要操作哪些分庫聪黎、分表罕容。常見的路由規(guī)則包括哈希取模,按照日期等稿饰。

    ?? ?? ? 下圖展示了user表進行分庫分表后(2個分庫锦秒,每個分庫2個分表),并如何根據(jù)id進行路由的規(guī)則:?

    路由分則分為:

  • 庫規(guī)則:用于確定到哪一個分庫

  • 表規(guī)則:用于確定到哪一個分表

  • 在上例中喉镰,我們使用id來作為計算分表旅择、分表,因此把id字段就稱之為路由字段侣姆,或者分區(qū)字段生真。

    ?? ??? ?需要注意的是脖咐,不管執(zhí)行的是INSERT、UPDATE汇歹、DELETE屁擅、SELECT語句,SQL中都應該包含這個路由字段产弹。否則派歌,對于插入語句來說,就不知道插入到哪個分庫或者分表痰哨;對于UPDATE胶果、DELETE、SELECT語句而言斤斧,則更為嚴重早抠,因為不知道操作哪個分庫分表,意味著必須要對所有分表都進行操作撬讽。SELECT聚合所有分表的內容蕊连,極容易內存溢出,UPDATE游昼、DELETE更新甘苍、刪除所有的記錄,非常容易誤更新烘豌、刪除數(shù)據(jù)载庭。因此,一些數(shù)據(jù)庫中間件廊佩,對于SQL可能有一些限制囚聚,例如UPDATE、DELETE必須要帶上分區(qū)字段标锄,或者指定過濾條件顽铸。?

    4.3?SQL改寫

    前面已經(jīng)介紹過,如一個批量插入語句鸯绿,如果記錄要插入到不同的分庫分表中跋破,那么就需要對SQL進行改寫。?例如瓶蝴,將以下SQL

    insert into user(id,name) values (1,”tianshouzhi”),(2,”huhuamin”), (3,”wanghanao”),(4,”luyang”)

    ?? ? ? ?改寫為:

    insert?into?user_1(id,name)?values?(1,”tianshouzhi”)insert?into?user_2(id,name)?values?(2,”huhuamin”)insert?into?user_3(id,name)?values?(3,”wanghanao”)insert into user_0(id,name) values  (4,”luyang”)

    這里只是一個簡單的案例毒返,通常對于INSERT、UPDATE舷手、DELETE等拧簸,改寫相對簡單。比較復雜的是SELECT語句的改寫男窟,對于一些復雜的SELECT語句盆赤,改寫過程中會進行一些優(yōu)化贾富,例如將子查詢改成JOIN,過濾條件下推等牺六。因為SQL改寫很復雜颤枪,所以很多數(shù)據(jù)庫中間件并不支持復雜的SQL(通常有一個支持的SQL),只能支持一些簡單的OLTP場景淑际。

    當然也有一些數(shù)據(jù)庫中間件畏纲,不滿足于只支持OLTP,在邁向OLAP的方向上進行了更多的努力春缕。例如阿里的TDDL盗胀、螞蟻的Zdal、大眾點評的zebra锄贼,都引入了apache calcite票灰,嘗試對復雜的查詢SQL(例如嵌套子查詢,join等)進行支持宅荤,通過過濾條件下推屑迂,流式讀取,并結合RBO(基于規(guī)則的優(yōu)化)膘侮、CBO(基于代價的優(yōu)化)來對一些簡單的OLAP場景進行支持屈糊。

    4.4?SQL執(zhí)行

    當經(jīng)過SQL改寫階段后,會產(chǎn)生多個SQL琼了,需要到不同的分片上去執(zhí)行,通常我們會使用一個線程池夫晌,將每個SQL包裝成一個任務雕薪,提交到線程池里面并發(fā)的去執(zhí)行,以提升效率晓淀。

    ? ? 這些執(zhí)行的SQL中所袁,如果有一個失敗,則整體失敗凶掰,返回異常給業(yè)務代碼燥爷。?

    4.5?結果集合并

    結果集合并,是數(shù)據(jù)庫中間件的一大難點懦窘,需要case by case的分析前翎,主要是考慮實現(xiàn)的復雜度,以及執(zhí)行的效率問題畅涂,對于一些復雜的SQL港华,可能并不支持。例如:

    ?? ?? ? ?對于查詢條件:大部分中間件都支持=午衰、IN作為查詢條件立宜,且可以作為分區(qū)字段冒萄。但是對于NIT IN、BETWEEN…AND橙数、LIKE,NOT LIKE等尊流,只能作為普通的查詢條件,因為根據(jù)這些條件灯帮,無法記錄到底是在哪個分庫或者分表崖技,只能全表掃描。

    聚合函數(shù):大部分中間件都支持MAX施流、MIN响疚、COUNT、SUM瞪醋,但是對于AVG可能只是部分支持忿晕。另外,如果是函數(shù)嵌套银受、分組(GROUP BY)聚合践盼,可能也有一些數(shù)據(jù)庫中間件不支持。

    ?? ?? ? 子查詢:分為FROM部分的子查詢和WHERE部分的子查詢宾巍。大部分中對于子查詢的支持都是非常有限咕幻,例如語法上兼容,但是無法識別子查詢中的分區(qū)字段顶霞,或者要求子查詢的表名必須與外部查詢表名相同肄程,又或者只能支持一級嵌套子查詢。

    ?? ?? ? JOIN:對于JOIN的支持通常很復雜选浑,如果做不到過濾條件下推和流式讀取蓝厌,在中間件層面,基本無法對JOIN進行支持古徒,因為不可能把兩個表的所有分表拓提,全部拿到內存中來進行JOIN,內存早就崩了隧膘。當然也有一些取巧的辦法代态,一個是Binding Table,另外一個是小表廣播(見后文)疹吃。

    ?? ?? ? 分頁排序:通常中間件都是支持ORDER BY和LIMIT的蹦疑。但是在分庫分表的情況下,分頁的效率較低互墓。例如對于limit 100必尼,10 ORDER BY id。表示按照id排序,從第100個位置開始取10條記錄判莉。那么豆挽,大部分數(shù)據(jù)庫中間件實際上是要從每個分表都查詢110(100+10)條記錄,拿到內存中進行重新排序券盅,然后取出10條帮哈。假設有10個分表,那么實際上要查詢1100條記錄锰镀,而最終只過濾出了10記錄娘侍。因此,在分頁的情況下泳炉,通常建議使用"where id > ? limit 10”的方式來進行查詢憾筏,應用記住每次查詢的最大的記錄id。之后查詢時花鹅,每個分表只需要從這個id之后氧腰,取10條記錄即可,而不是取offset + rows條記錄刨肃。?


    關于JOIN的特屬說明:

    Binding Table:

    ?? ?? ? 適用于兩個表之間存在關聯(lián)關系古拴,路由規(guī)則相同。例如真友,有user表和user_account表黄痪,由于user_account與user表強關聯(lián),我們可以將這兩個表的路由規(guī)則設置為完全一樣盔然,那么對于某個特定用戶的信息桅打,其所在的user分表和user_account分表必然唯一同一個分庫下嘀倒,后綴名相同的分表中撩匕。在join時坷衍,某一個分庫內的join丧叽,就可以拿到這個用戶以及賬號的完整信息,而不需要進行跨庫join砸民,這樣就不需要把用戶的數(shù)據(jù)庫拿到內存中來進行join。?

    小表廣播:

    ?? ? ? 小表廣播通常是某一個表的數(shù)據(jù)量比較少,?例如部門表department崇众。另外一個表數(shù)據(jù)量比較大,例如user航厚。此時user需要進行分庫分表顷歌,但是department不需要進行分庫分表。為了達到JOIN的目的幔睬,我們可以將 department表在每個分庫內都實時同步一份完整的數(shù)據(jù)眯漩。這樣,在JOIN的時候,數(shù)據(jù)庫中間件只需要將分庫JOIN的結果進行簡單合并即可赦抖。

    ?? ?? ? 下圖演示了小表廣播的流程舱卡,用戶在更新department表時,總是更新分庫db0的department表队萤,同步組件將變更信息同步到其他分庫中轮锥。?

    注:圖中的同步組件指的是一般是偽裝成數(shù)據(jù)庫的從庫,解析源庫binlog要尔,插入目標庫舍杜。有一些開源的組件,如canal赵辕、puma可以實現(xiàn)這個功能既绩,當然這些組件的應用場景非常廣泛,不僅限于此还惠。筆者曾寫過一個系列的canal源碼解析文章饲握,目前完成了大部分。

    4.6?二級索引

    ?? ?? ? 通常情況下吸重,分庫分表的時候互拾,分區(qū)字段只有一個。例如對于用戶表user嚎幸,按照user_id字段進行分區(qū)颜矿,那么之后查詢某個用戶的信息,只能根據(jù)user_id作為分區(qū)字段嫉晶。使用其他字段骑疆,則需要掃描所有分表,效率很低替废。但是又有根據(jù)其他字段查詢某個用戶信息的需求箍铭,例如根據(jù)手機號phone_id。

    ?? ?? ? 此時椎镣,我們可以將按照user_id插入的數(shù)據(jù)诈火,進行一份全量拷貝。通過同步組件状答,重新按照phone_id插入到另一個分庫分表集群中冷守,這個集群就成為二級索引,或者叫輔維度同步惊科。此后拍摇,對于根據(jù)user_id的操作,就在原來的分庫分表集群中進行操作馆截;根據(jù)phone_id的操作充活,就到二級索引集群中去進行操作蜂莉。

    ?? ?? ? 需要注意的是,對于更新操作混卵,只能操作原集群映穗,二級索引集群只能執(zhí)行查詢操作。原集群的增量數(shù)據(jù)變更信息淮菠,實時的通過同步組件男公,同步到二級索引集群中。?

    注:這是一個很常見的面試題合陵。阿里的一些面試官枢赔,比較喜歡問。一些面試者拥知,可能自己想到了這個方案踏拜,因為考慮到這樣比較浪費資源,就自行排除了低剔。事實上速梗,這點資源相對于滿足業(yè)務需求來說,都不是事襟齿。

    4.7 分布式id生成器

    在分庫分表的情況下姻锁,數(shù)據(jù)庫的自增主鍵已經(jīng)無法使用。所以要使用一個分布式的id生成器猜欺。分布式事務id生成器要滿足以下條件:唯一位隶、趨勢遞增(減少落庫時的索引開銷)、高性能开皿、高可用涧黄。

    目前主流的分布式id生成方案都有第三方組件依賴,如:

  • 基于zk

  • 基于mysql

  • 基于緩存

  • twitter的snowflake算法是一個完全去中心化的分布式id算法赋荆,但是限制workid最多能有1024笋妥,也就是說,應用規(guī)模不能超過1024窄潭。雖然可以進行細微的調整春宣,但是總是有數(shù)量的限制。?

    另外嫉你,美團之前在github開源了一個leaf組件信认,是用于生成分布式id的,感興趣的讀者可以研究一下均抽。

    這里提出一種支持動態(tài)擴容的去中心化分布式id生成方案,此方案的優(yōu)勢其掂,除了保證唯一油挥、趨勢遞增,沒有第三方依賴,支持存儲的動態(tài)擴容之外深寥,還具有以下優(yōu)勢:

  • 支持按照時間范圍查詢攘乒,或者 時間范圍+ip查詢,可以直接走主鍵索引惋鹅;

  • 每秒的最大序列id就是某個ip的qps等

    ?12位日期+10位IP+6位序列ID+4位數(shù)據(jù)庫擴展位

    其中:

    12位日期:格式為yyMMddHHmmss则酝,意味著本方案的id生成策略可以使用到2099年,把時間部分前置闰集,從而保證趨勢遞增沽讹。

    10位ip:利用ip to decimal算法將12位的ip轉為10進制數(shù)字。通過ip地址武鲁,來保證全局唯一爽雄。如果ip地址被回收重復利用了,也不用擔心id的唯一性沐鼠,因為日期部分還在變化挚瘟。

    6位序列id:意味著每秒最多支持生成100百萬個id(0~999999)。不足6位前置補0饲梭,如000123乘盖。

    4位數(shù)據(jù)庫擴展位:為了實現(xiàn)不遷移數(shù)據(jù)的情況下,實現(xiàn)動態(tài)擴容憔涉,其中2位表示DB订框,2位表示TB,最多可擴容到10000張表监氢。假設每張表存儲1000萬數(shù)據(jù)布蔗,則總共可以支持存儲1000億條數(shù)據(jù)。?

    關于數(shù)據(jù)庫擴展位實現(xiàn)動態(tài)擴容圖解:

    首先明確一點浪腐,路由策略始終根據(jù)數(shù)據(jù)庫最后四位纵揍,確定某一條記錄要到哪個分庫的哪個分表中。例如xxxx0001议街,意味著這條記錄肯定是在00分庫的01分表上泽谨。

    接著,就要在id的生成策略上做文章特漩。

    假設初始狀態(tài)為兩個分庫db_00,db_01吧雹,每個分庫里面有10張分表,tb_00~tb_09涂身。此時雄卷,業(yè)務要保證生成id的時候,始終保證db的兩位在00~01之間蛤售,tb的兩位始終在00~09之間丁鹉。路由策略根據(jù)這些id妒潭,可以找到正確的分庫分表。

    現(xiàn)在需要擴容到10個分庫揣钦,每個分表10個分表雳灾。那么DBA首先將新增的分庫:db_02~db_09創(chuàng)建好,每個分庫里面再創(chuàng)建10個分表:tb_01~tb_09冯凹。業(yè)務同學在此基礎上谎亩,將id生成策略改成:db的兩位在00~09之間,tb的兩位規(guī)則維持不變(只是分庫數(shù)變了宇姚,每個分庫的分表數(shù)沒變)匈庭。而由于路由從策略是根據(jù)最后四位確定到哪個分庫,哪個分表空凸,當這些新的分庫分表擴展位id出現(xiàn)時嚎花,自然可以插入到新的分庫分表中。也就實現(xiàn)了動態(tài)擴容呀洲,而無需遷移數(shù)據(jù)紊选。

    當然,新的分庫分表中道逗,一開始數(shù)據(jù)是沒有數(shù)據(jù)的兵罢,所以數(shù)據(jù)是不均勻的,可以調整id擴展位中db和tb生成某個值的概率滓窍,使得落到新的分庫分表中的概率相對大一點點(不宜太大)卖词,等到數(shù)據(jù)均勻后,再重新調整成完全隨機吏夯。

    此方案的核心思想是此蜈,預分配未來的可能使用到的最大資源數(shù)量。通常噪生,100個分庫裆赵,每個分庫100張分表,能滿足絕大部分應用的數(shù)據(jù)存儲跺嗽。如果100個分庫都在不同的mysql實例上战授,假設每個mysql實例都是4T的磁盤,那么可以存儲400T的數(shù)據(jù)桨嫁,基本上可以滿足絕大部分業(yè)務的需求植兰。

    當然,這個方案不完美璃吧。如果超過這個值楣导,這種方案可能就不可行了。然而畜挨,通常一個技術方案爷辙,可以保證在5~10年之間不需要在架構上做變動彬坏,應該就算的上一個好方案了。如果你追求的是完美的方案膝晾,可能類似于TIDB這種可以實現(xiàn)自動擴容的數(shù)據(jù)庫產(chǎn)品更適合,不過目前來說务冕,TIDB等類似產(chǎn)品還是無法取代傳統(tǒng)的關系型數(shù)據(jù)庫的血当。說不定等到5~10年后,這些產(chǎn)品更成熟了禀忆,你再遷移過去也不遲臊旭。

    4.7?分布式事務

    ?? ??? ?在分庫分表的情況下,由于操作多個分庫箩退,此時就涉及到分布式事務离熏。例如執(zhí)行一個批量插入SQL,如果記錄要插入到不同的分庫中戴涝,就無法保證一致性滋戳。因此,通常情況下啥刻,數(shù)據(jù)庫中間件奸鸯,只會保證單個分庫的事務,也就是說可帽,業(yè)務方在創(chuàng)建一個事務的時候娄涩,必須要保證事務中的所有操作,必須最終都在一個分庫中執(zhí)行映跟。

    ?? ?? ? 事實上蓄拣,在微服務的架構下,事務的問題更加復雜努隙,如下圖

    ? ? ? ? Service A在執(zhí)行某個操作時球恤,需要操作數(shù)據(jù)庫,同時調用Service B和Service C剃法,Service B底層操作的數(shù)據(jù)庫是分庫分表的碎捺,Service C也要操作數(shù)據(jù)庫。

    ?? ?? ? 這種場景下贷洲,保證事務的一致性就非常麻煩收厨。一些常用的一致性算法如:paxios協(xié)議、raft協(xié)議也無法解決這個問題优构,因為這些協(xié)議都是資源層面的一致性诵叁。在微服務架構下,已經(jīng)將事務的一致性上升到了業(yè)務的層面钦椭。

    ?? ?? ? 如果僅僅考慮分庫分表拧额,一些同學可能會想到XA碑诉,但是性能很差,對數(shù)據(jù)庫的版本也有要求侥锦,例如必須使用mysql 5.7进栽,官方還建議將事務隔離級別設置為串行化,這是無法容忍的恭垦。

    ?? ?? ? 由于分布式事務的應用場景快毛,并不是僅僅分庫分表,因此通常都是會有一個專門的團隊來做分布式事務番挺,并不一定是數(shù)據(jù)庫中間件團隊來做唠帝。例如,sharding-jdbc就使用了華為開源的一套微服務架構解決方案service comb中的saga組件玄柏,來實現(xiàn)分布式事務最終一致性襟衰。阿里也有類似的組件,在內部叫TXC粪摘,在阿里云上叫GTS瀑晒,最近開源到了GitHub上叫fescar(Fast & Easy Commit And Rollback)。螞蟻金服也有類似的組件赶熟,叫DTX瑰妄,支持FMT模式和TCC模式。其中FMT模式就類似于TXC映砖。

    總體來說间坐,實際上TCC更能滿足業(yè)務的需求,雖然接入更加復雜邑退。關于fescar竹宋,最近比較火,這是java寫的地技,具體可以參考:https://github.com/alibaba/fescar蜈七。?



    ?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
    • 序言:七十年代末,一起剝皮案震驚了整個濱河市莫矗,隨后出現(xiàn)的幾起案子飒硅,更是在濱河造成了極大的恐慌,老刑警劉巖作谚,帶你破解...
      沈念sama閱讀 217,657評論 6 505
    • 序言:濱河連續(xù)發(fā)生了三起死亡事件三娩,死亡現(xiàn)場離奇詭異,居然都是意外死亡妹懒,警方通過查閱死者的電腦和手機雀监,發(fā)現(xiàn)死者居然都...
      沈念sama閱讀 92,889評論 3 394
    • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人会前,你說我怎么就攤上這事好乐。” “怎么了瓦宜?”我有些...
      開封第一講書人閱讀 164,057評論 0 354
    • 文/不壞的土叔 我叫張陵蔚万,是天一觀的道長。 經(jīng)常有香客問我临庇,道長笛坦,這世上最難降的妖魔是什么? 我笑而不...
      開封第一講書人閱讀 58,509評論 1 293
    • 正文 為了忘掉前任苔巨,我火速辦了婚禮,結果婚禮上废离,老公的妹妹穿的比我還像新娘侄泽。我一直安慰自己,他們只是感情好蜻韭,可當我...
      茶點故事閱讀 67,562評論 6 392
    • 文/花漫 我一把揭開白布悼尾。 她就那樣靜靜地躺著,像睡著了一般肖方。 火紅的嫁衣襯著肌膚如雪闺魏。 梳的紋絲不亂的頭發(fā)上,一...
      開封第一講書人閱讀 51,443評論 1 302
    • 那天俯画,我揣著相機與錄音析桥,去河邊找鬼。 笑死艰垂,一個胖子當著我的面吹牛泡仗,可吹牛的內容都是我干的。 我是一名探鬼主播猜憎,決...
      沈念sama閱讀 40,251評論 3 418
    • 文/蒼蘭香墨 我猛地睜開眼娩怎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胰柑?” 一聲冷哼從身側響起截亦,我...
      開封第一講書人閱讀 39,129評論 0 276
    • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎柬讨,沒想到半個月后崩瓤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
      沈念sama閱讀 45,561評論 1 314
    • 正文 獨居荒郊野嶺守林人離奇死亡姐浮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
      茶點故事閱讀 37,779評論 3 335
    • 正文 我和宋清朗相戀三年谷遂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卖鲤。...
      茶點故事閱讀 39,902評論 1 348
    • 序言:一個原本活蹦亂跳的男人離奇死亡肾扰,死狀恐怖畴嘶,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情集晚,我是刑警寧澤窗悯,帶...
      沈念sama閱讀 35,621評論 5 345
    • 正文 年R本政府宣布,位于F島的核電站偷拔,受9級特大地震影響蒋院,放射性物質發(fā)生泄漏。R本人自食惡果不足惜莲绰,卻給世界環(huán)境...
      茶點故事閱讀 41,220評論 3 328
    • 文/蒙蒙 一欺旧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛤签,春花似錦辞友、人聲如沸。這莊子的主人今日做“春日...
      開封第一講書人閱讀 31,838評論 0 22
    • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至戳晌,卻和暖如春鲫尊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沦偎。 一陣腳步聲響...
      開封第一講書人閱讀 32,971評論 1 269
    • 我被黑心中介騙來泰國打工疫向, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扛施。 一個月前我還...
      沈念sama閱讀 48,025評論 2 370
    • 正文 我出身青樓鸿捧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疙渣。 傳聞我的和親對象是個殘疾皇子匙奴,可洞房花燭夜當晚...
      茶點故事閱讀 44,843評論 2 354

    推薦閱讀更多精彩內容