在之前的文章中坡椒,我介紹了分庫分表的幾種表現(xiàn)形式和玩法,也重點介紹了垂直分庫所帶來的問題和解決方法宪萄。本篇中谱净,我們將繼續(xù)聊聊水平分庫分表的一些技巧。
分片技術(shù)的由來
關(guān)系型數(shù)據(jù)庫本身比較容易成為系統(tǒng)性能瓶頸噪舀,單機(jī)存儲容量魁淳、連接數(shù)、處理能力等都很有限与倡,數(shù)據(jù)庫本身的“有狀態(tài)性”導(dǎo)致了它并不像Web和應(yīng)用服務(wù)器那么容易擴(kuò)展界逛。在互聯(lián)網(wǎng)行業(yè)海量數(shù)據(jù)和高并發(fā)訪問的考驗下,聰明的技術(shù)人員提出了分庫分表技術(shù)(有些地方也稱為Sharding蒸走、分片)仇奶。同時貌嫡,流行的分布式系統(tǒng)中間件(例如MongoDB比驻、ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小異的岛抄。
分布式全局唯一ID
在很多中小項目中别惦,我們往往直接使用數(shù)據(jù)庫自增特性來生成主鍵ID,這樣確實比較簡單夫椭。而在分庫分表的環(huán)境中掸掸,數(shù)據(jù)分布在不同的分片上,不能再借助數(shù)據(jù)庫自增長特性直接生成蹭秋,否則會造成不同分片上的數(shù)據(jù)表主鍵會重復(fù)扰付。簡單介紹下使用和了解過的幾種ID生成算法。
Twitter的Snowflake(又名“雪花算法”)
UUID/GUID(一般應(yīng)用程序和數(shù)據(jù)庫均支持)
MongoDB ObjectID(類似UUID的方式)
Ticket Server(數(shù)據(jù)庫生存方式仁讨,F(xiàn)lickr采用的就是這種方式)
其中羽莺,Twitter 的Snowflake算法是筆者近幾年在分布式系統(tǒng)項目中使用最多的,未發(fā)現(xiàn)重復(fù)或并發(fā)的問題洞豁。該算法生成的是64位唯一Id(由41位的timestamp+ 10位自定義的機(jī)器碼+ 13位累加計數(shù)器組成)盐固。這里不做過多介紹,感興趣的讀者可自行查閱相關(guān)資料丈挟。
常見分片規(guī)則和策略
分片字段該如何選擇
在開始分片之前刁卜,我們首先要確定分片字段(也可稱為“片鍵”)。很多常見的例子和場景中是采用ID或者時間字段進(jìn)行拆分曙咽。這也并不絕對的蛔趴,我的建議是結(jié)合實際業(yè)務(wù),通過對系統(tǒng)中執(zhí)行的sql語句進(jìn)行統(tǒng)計分析例朱,選擇出需要分片的那個表中最頻繁被使用孝情,或者最重要的字段來作為分片字段之拨。
常見分片規(guī)則
常見的分片策略有隨機(jī)分片和連續(xù)分片這兩種,如下圖所示:
當(dāng)需要使用分片字段進(jìn)行范圍查找時咧叭,連續(xù)分片可以快速定位分片進(jìn)行高效查詢蚀乔,大多數(shù)情況下可以有效避免跨分片查詢的問題。后期如果想對整個分片集群擴(kuò)容時菲茬,只需要添加節(jié)點即可吉挣,無需對其他分片的數(shù)據(jù)進(jìn)行遷移。但是婉弹,連續(xù)分片也有可能存在數(shù)據(jù)熱點的問題睬魂,就像圖中按時間字段分片的例子,有些節(jié)點可能會被頻繁查詢壓力較大镀赌,熱數(shù)據(jù)節(jié)點就成為了整個集群的瓶頸氯哮。而有些節(jié)點可能存的是歷史數(shù)據(jù),很少需要被查詢到商佛。
隨機(jī)分片其實并不是隨機(jī)的喉钢,也遵循一定規(guī)則。通常良姆,我們會采用Hash取模的方式進(jìn)行分片拆分肠虽,所以有些時候也被稱為離散分片。隨機(jī)分片的數(shù)據(jù)相對比較均勻玛追,不容易出現(xiàn)熱點和并發(fā)訪問的瓶頸税课。但是,后期分片集群擴(kuò)容起來需要遷移舊的數(shù)據(jù)痊剖。使用一致性Hash算法能夠很大程度的避免這個問題韩玩,所以很多中間件的分片集群都會采用一致性Hash算法。離散分片也很容易面臨跨分片查詢的復(fù)雜問題陆馁。
數(shù)據(jù)遷移找颓,容量規(guī)劃,擴(kuò)容等問題
很少有項目會在初期就開始考慮分片設(shè)計的氮惯,一般都是在業(yè)務(wù)高速發(fā)展面臨性能和存儲的瓶頸時才會提前準(zhǔn)備叮雳。因此,不可避免的就需要考慮歷史數(shù)據(jù)遷移的問題妇汗。一般做法就是通過程序先讀出歷史數(shù)據(jù)帘不,然后按照指定的分片規(guī)則再將數(shù)據(jù)寫入到各個分片節(jié)點中。
此外杨箭,我們需要根據(jù)當(dāng)前的數(shù)據(jù)量和QPS等進(jìn)行容量規(guī)劃寞焙,綜合成本因素,推算出大概需要多少分片(一般建議單個分片上的單表數(shù)據(jù)量不要超過1000W)。
如果是采用隨機(jī)分片捣郊,則需要考慮后期的擴(kuò)容問題辽狈,相對會比較麻煩。如果是采用的范圍分片呛牲,只需要添加節(jié)點就可以自動擴(kuò)容刮萌。
跨分片技術(shù)問題
跨分片的排序分頁
一般來講,分頁時需要按照指定字段進(jìn)行排序娘扩。當(dāng)排序字段就是分片字段的時候着茸,我們通過分片規(guī)則可以比較容易定位到指定的分片,而當(dāng)排序字段非分片字段的時候琐旁,情況就會變得比較復(fù)雜了涮阔。為了最終結(jié)果的準(zhǔn)確性,我們需要在不同的分片節(jié)點中將數(shù)據(jù)進(jìn)行排序并返回灰殴,并將不同分片返回的結(jié)果集進(jìn)行匯總和再次排序敬特,最后再返回給用戶。如下圖所示:
上面圖中所描述的只是最簡單的一種情況(取第一頁數(shù)據(jù))牺陶,看起來對性能的影響并不大伟阔。但是,如果想取出第10頁數(shù)據(jù)义图,情況又將變得復(fù)雜很多减俏,如下圖所示:
有些讀者可能并不太理解召烂,為什么不能像獲取第一頁數(shù)據(jù)那樣簡單處理(排序取出前10條再合并碱工、排序)。其實并不難理解奏夫,因為各分片節(jié)點中的數(shù)據(jù)可能是隨機(jī)的怕篷,為了排序的準(zhǔn)確性,必須把所有分片節(jié)點的前N頁數(shù)據(jù)都排序好后做合并酗昼,最后再進(jìn)行整體的排序廊谓。很顯然,這樣的操作是比較消耗資源的麻削,用戶越往后翻頁蒸痹,系統(tǒng)性能將會越差。
跨分片的函數(shù)處理
在使用Max呛哟、Min叠荠、Sum、Count之類的函數(shù)進(jìn)行統(tǒng)計和計算的時候扫责,需要先在每個分片數(shù)據(jù)源上執(zhí)行相應(yīng)的函數(shù)處理榛鼎,然后再將各個結(jié)果集進(jìn)行二次處理,最終再將處理結(jié)果返回。如下圖所示:
跨分片join
Join是關(guān)系型數(shù)據(jù)庫中最常用的特性者娱,但是在分片集群中抡笼,join也變得非常復(fù)雜。應(yīng)該盡量避免跨分片的join查詢(這種場景黄鳍,比上面的跨分片分頁更加復(fù)雜推姻,而且對性能的影響很大)。通常有以下幾種方式來避免:
全局表
全局表的概念之前在“垂直分庫”時提過框沟∈奥担基本思想一致,就是把一些類似數(shù)據(jù)字典又可能會產(chǎn)生join查詢的表信息放到各分片中街望,從而避免跨分片的join校翔。
ER分片
在關(guān)系型數(shù)據(jù)庫中,表之間往往存在一些關(guān)聯(lián)的關(guān)系灾前。如果我們可以先確定好關(guān)聯(lián)關(guān)系防症,并將那些存在關(guān)聯(lián)關(guān)系的表記錄存放在同一個分片上,那么就能很好的避免跨分片join問題哎甲。在一對多關(guān)系的情況下蔫敲,我們通常會選擇按照數(shù)據(jù)較多的那一方進(jìn)行拆分。如下圖所示:
這樣一來炭玫,Data Node1上面的訂單表與訂單詳細(xì)表就可以直接關(guān)聯(lián)奈嘿,進(jìn)行局部的join查詢了,Data Node2上也一樣吞加∪褂蹋基于ER分片的這種方式,能夠有效避免大多數(shù)業(yè)務(wù)場景中的跨分片join問題衔憨。
內(nèi)存計算
隨著spark內(nèi)存計算的興起叶圃,理論上來講,很多跨數(shù)據(jù)源的操作問題看起來似乎都能夠得到解決践图〔艄冢可以將數(shù)據(jù)丟給spark集群進(jìn)行內(nèi)存計算,最后將計算結(jié)果返回码党。
跨分片事務(wù)問題
跨分片事務(wù)也分布式事務(wù)德崭,想要了解分布式事務(wù),就需要了解“XA接口”和“兩階段提交”揖盘。值得提到的是眉厨,MySQL5.5x和5.6x中的xa支持是存在問題的,會導(dǎo)致主從數(shù)據(jù)不一致扣讼。直到5.7x版本中才得到修復(fù)缺猛。Java應(yīng)用程序可以采用Atomikos框架來實現(xiàn)XA事務(wù)(J2EE中JTA)。感興趣的讀者可以自行參考《分布式事務(wù)一致性解決方案》,鏈接地址:
http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency
我們的系統(tǒng)真的需要分庫分表嗎
讀完上面內(nèi)容荔燎,不禁引起有些讀者的思考耻姥,我們的系統(tǒng)是否需要分庫分表嗎?
其實這點沒有明確的判斷標(biāo)準(zhǔn)有咨,比較依賴實際業(yè)務(wù)情況和經(jīng)驗判斷琐簇。依照筆者個人的經(jīng)驗,一般MySQL單表1000W左右的數(shù)據(jù)是沒有問題的(前提是應(yīng)用系統(tǒng)和數(shù)據(jù)庫等層面設(shè)計和優(yōu)化的比較好)座享。當(dāng)然婉商,除了考慮當(dāng)前的數(shù)據(jù)量和性能情況時,作為架構(gòu)師渣叛,我們需要提前考慮系統(tǒng)半年到一年左右的業(yè)務(wù)增長情況丈秩,對數(shù)據(jù)庫服務(wù)器的QPS、連接數(shù)淳衙、容量等做合理評估和規(guī)劃蘑秽,并提前做好相應(yīng)的準(zhǔn)備工作。如果單機(jī)無法滿足箫攀,且很難再從其他方面優(yōu)化肠牲,那么說明是需要考慮分片的。這種情況可以先去掉數(shù)據(jù)庫中自增ID靴跛,為分片和后面的數(shù)據(jù)遷移工作提前做準(zhǔn)備缀雳。
很多人覺得“分庫分表”是宜早不宜遲,應(yīng)該盡早進(jìn)行梢睛,因為擔(dān)心越往后公司業(yè)務(wù)發(fā)展越快肥印、系統(tǒng)越來越復(fù)雜、系統(tǒng)重構(gòu)和擴(kuò)展越困難…這種話聽起來是有那么一點道理扬绪,但我的觀點恰好相反竖独,對于關(guān)系型數(shù)據(jù)庫來講,我認(rèn)為“能不分片就別分片”挤牛,除非是系統(tǒng)真正需要,因為數(shù)據(jù)庫分片并非低成本或者免費的种蘸。
這里筆者推薦一個比較靠譜的過渡技術(shù)–“表分區(qū)”墓赴。主流的關(guān)系型數(shù)據(jù)庫中基本都支持。不同的分區(qū)在邏輯上仍是一張表航瞭,但是物理上卻是分開的诫硕,能在一定程度上提高查詢性能,而且對應(yīng)用程序透明刊侯,無需修改任何代碼章办。筆者曾經(jīng)負(fù)責(zé)優(yōu)化過一個系統(tǒng),主業(yè)務(wù)表有大約8000W左右的數(shù)據(jù),考慮到成本問題藕届,當(dāng)時就是采用“表分區(qū)”來做的挪蹭,效果比較明顯,且系統(tǒng)運行的很穩(wěn)定休偶。
小結(jié)
最后梁厉,有很多讀者都想了解當(dāng)前社區(qū)中有沒有開源免費的分庫分表解決方案,畢竟站在巨人的肩膀上能省力很多踏兜。當(dāng)前主要有兩類解決方案:
基于應(yīng)用程序?qū)用娴腄DAL(分布式數(shù)據(jù)庫訪問層)
比較典型的就是淘寶半開源的TDDL词顾,當(dāng)當(dāng)網(wǎng)開源的Sharding-JDBC等。分布式數(shù)據(jù)訪問層無需硬件投入碱妆,技術(shù)能力較強(qiáng)的大公司通常會選擇自研或參照開源框架進(jìn)行二次開發(fā)和定制肉盹。對應(yīng)用程序的侵入性一般較大,會增加技術(shù)成本和復(fù)雜度疹尾。通常僅支持特定編程語言平臺(Java平臺的居多)垮媒,或者僅支持特定的數(shù)據(jù)庫和特定數(shù)據(jù)訪問框架技術(shù)(一般支持MySQL數(shù)據(jù)庫,JDBC航棱、MyBatis睡雇、Hibernate等框架技術(shù))。
數(shù)據(jù)庫中間件饮醇,比較典型的像mycat(在阿里開源的cobar基礎(chǔ)上做了很多優(yōu)化和改進(jìn)它抱,屬于后起之秀,也支持很多新特性)朴艰,基于Go語言實現(xiàn)kingSharding观蓄,比較老牌的Atlas(由360開源)等。這些中間件在互聯(lián)網(wǎng)企業(yè)中大量被使用祠墅。另外侮穿,MySQL 5.x企業(yè)版中官方提供的Fabric組件也號稱支持分片技術(shù),不過國內(nèi)使用的企業(yè)較少毁嗦。
中間件也可以稱為“透明網(wǎng)關(guān)”亲茅,大名鼎鼎的mysql_proxy大概是該領(lǐng)域的鼻祖(由MySQL官方提供,僅限于實現(xiàn)“讀寫分離”)狗准。中間件一般實現(xiàn)了特定數(shù)據(jù)庫的網(wǎng)絡(luò)通信協(xié)議克锣,模擬一個真實的數(shù)據(jù)庫服務(wù),屏蔽了后端真實的Server腔长,應(yīng)用程序通常直接連接中間件即可袭祟。而在執(zhí)行SQL操作時,中間件會按照預(yù)先定義分片規(guī)則捞附,對SQL語句進(jìn)行解析巾乳、路由您没,并對結(jié)果集做二次計算再最終返回。引入數(shù)據(jù)庫中間件的技術(shù)成本更低胆绊,對應(yīng)用程序來講侵入性幾乎沒有氨鹏,可以滿足大部分的業(yè)務(wù)。增加了額外的硬件投入和運維成本辑舷,同時喻犁,中間件自身也存在性能瓶頸和單點故障問題,需要能夠保證中間件自身的高可用何缓、可擴(kuò)展肢础。
總之,不管是使用分布式數(shù)據(jù)訪問層還是數(shù)據(jù)庫中間件碌廓,都會帶來一定的成本和復(fù)雜度传轰,也會有一定的性能影響。所以谷婆,還需讀者根據(jù)實際情況和業(yè)務(wù)發(fā)展需要慎重考慮和選擇慨蛙。
作者介紹
丁浪,技術(shù)架構(gòu)師纪挎。關(guān)注高并發(fā)期贫、高可用的架構(gòu)設(shè)計,對系統(tǒng)服務(wù)化异袄、分庫分表通砍、性能調(diào)優(yōu)等方面有深入研究和豐富實踐經(jīng)驗。熱衷于技術(shù)研究和分享烤蜕。
推薦一個談架構(gòu)的會議
7月深圳封孙,是怎樣的技術(shù)觸動技術(shù)的心弦?不管是共享經(jīng)濟(jì)下各巨頭同臺競技還是大數(shù)據(jù)上中外大牛各顯神通讽营,一切都將在12月北京更進(jìn)一步:立足經(jīng)典虎忌,雙十一技術(shù)競技、高可用架構(gòu)橱鹏、大數(shù)據(jù)膜蠢、移動等,精彩依舊蚀瘸;擁抱熱點狡蝶,視頻直播、新聞資訊贮勃、Fin-Tech等,引爆架構(gòu)前沿苏章。更精彩的技術(shù)盡在ArchSummit北京2016寂嘉。詳情點擊閱讀原文奏瞬。