在談?wù)摂?shù)據(jù)庫架構(gòu)和數(shù)據(jù)庫優(yōu)化的時(shí)候,我們經(jīng)常會(huì)聽到“分庫分表”、“分片”、“Sharding”…這樣的關(guān)鍵詞。讓人感到高興的是,這些朋友所服務(wù)的公司業(yè)務(wù)量正在(或者即將面臨)高速增長,技術(shù)方面也面臨著一些挑戰(zhàn)。讓人感到擔(dān)憂的是,他們系統(tǒng)真的就需要“分庫分表”了嗎?“分庫分表”有那么容易實(shí)踐嗎?為此,筆者整理了分庫分表中可能遇到的一些問題,并結(jié)合以往經(jīng)驗(yàn)介紹了對應(yīng)的解決思路和建議。
垂直分表
垂直分表在日常開發(fā)和設(shè)計(jì)中比較常見,通俗的說法叫做“大表拆小表”,拆分是基于關(guān)系型數(shù)據(jù)庫中的“列”(字段)進(jìn)行的。通常情況,某個(gè)表中的字段比較多,可以新建立一張“擴(kuò)展表”,將不經(jīng)常使用或者長度較大的字段拆分出去放到“擴(kuò)展表”中,如下圖所示:
小結(jié)
在字段很多的情況下,拆分開確實(shí)更便于開發(fā)和維護(hù)(筆者曾見過某個(gè)遺留系統(tǒng)中,一個(gè)大表中包含100多列的)。某種意義上也能避免“跨頁”的問題(MySQL、MSSQL底層都是通過“數(shù)據(jù)頁”來存儲(chǔ)的,“跨頁”問題可能會(huì)造成額外的性能開銷,這里不展開,感興趣的朋友可以自行查閱相關(guān)資料進(jìn)行研究)。
拆分字段的操作建議在數(shù)據(jù)庫設(shè)計(jì)階段就做好。如果是在發(fā)展過程中拆分,則需要改寫以前的查詢語句,會(huì)額外帶來一定的成本和風(fēng)險(xiǎn),建議謹(jǐn)慎。
垂直分庫
垂直分庫在“微服務(wù)”盛行的今天已經(jīng)非常普及了。基本的思路就是按照業(yè)務(wù)模塊來劃分出不同的數(shù)據(jù)庫,而不是像早期一樣將所有的數(shù)據(jù)表都放到同一個(gè)數(shù)據(jù)庫中。如下圖:
小結(jié)
系統(tǒng)層面的“服務(wù)化”拆分操作,能夠解決業(yè)務(wù)系統(tǒng)層面的耦合和性能瓶頸,有利于系統(tǒng)的擴(kuò)展維護(hù)。而數(shù)據(jù)庫層面的拆分,道理也是相通的。與服務(wù)的“治理”和“降級”機(jī)制類似,我們也能對不同業(yè)務(wù)類型的數(shù)據(jù)進(jìn)行“分級”管理、維護(hù)、監(jiān)控、擴(kuò)展等。
眾所周知,數(shù)據(jù)庫往往最容易成為應(yīng)用系統(tǒng)的瓶頸,而數(shù)據(jù)庫本身屬于“有狀態(tài)”的,相對于Web和應(yīng)用服務(wù)器來講,是比較難實(shí)現(xiàn)“橫向擴(kuò)展”的。數(shù)據(jù)庫的連接資源比較寶貴且單機(jī)處理能力也有限,在高并發(fā)場景下,垂直分庫一定程度上能夠突破IO、連接數(shù)及單機(jī)硬件資源的瓶頸,是大型分布式系統(tǒng)中優(yōu)化數(shù)據(jù)庫架構(gòu)的重要手段。
然后,很多人并沒有從根本上搞清楚為什么要拆分,也沒有掌握拆分的原則和技巧,只是一味的模仿大廠的做法。導(dǎo)致拆分后遇到很多問題(例如:跨庫join,分布式事務(wù)等)。
水平分表
水平分表也稱為橫向分表,比較容易理解,就是將表中不同的數(shù)據(jù)行按照一定規(guī)律分布到不同的數(shù)據(jù)庫表中(這些表保存在同一個(gè)數(shù)據(jù)庫中),這樣來降低單表數(shù)據(jù)量,優(yōu)化查詢性能。最常見的方式就是通過主鍵或者時(shí)間等字段進(jìn)行Hash和取模后拆分。如下圖所示:
小結(jié)
水平分表,能夠降低單表的數(shù)據(jù)量,一定程度上可以緩解查詢性能瓶頸。但本質(zhì)上這些表還保存在同一個(gè)庫中,所以庫級別還是會(huì)有IO瓶頸。所以,一般不建議采用這種做法。
水平分庫分表
水平分庫分表與上面講到的水平分表的思想相同,唯一不同的就是將這些拆分出來的表保存在不同的數(shù)據(jù)中。這也是很多大型互聯(lián)網(wǎng)公司所選擇的做法。如下圖:
某種意義上來講,有些系統(tǒng)中使用的“冷熱數(shù)據(jù)分離”(將一些使用較少的歷史數(shù)據(jù)遷移到其他的數(shù)據(jù)庫中。而在業(yè)務(wù)功能上,通常默認(rèn)只提供熱點(diǎn)數(shù)據(jù)的查詢),也是類似的實(shí)踐。在高并發(fā)和海量數(shù)據(jù)的場景下,分庫分表能夠有效緩解單機(jī)和單庫的性能瓶頸和壓力,突破IO、連接數(shù)、硬件資源的瓶頸。當(dāng)然,投入的硬件成本也會(huì)更高。同時(shí),這也會(huì)帶來一些復(fù)雜的技術(shù)問題和挑戰(zhàn)(例如:跨分片的復(fù)雜查詢,跨分片事務(wù)等)
分庫分表的難點(diǎn)
垂直分庫帶來的問題和解決思路:
跨庫join的問題
在拆分之前,系統(tǒng)中很多列表和詳情頁所需的數(shù)據(jù)是可以通過sql join來完成的。而拆分后,數(shù)據(jù)庫可能是分布式在不同實(shí)例和不同的主機(jī)上,join將變得非常麻煩。而且基于架構(gòu)規(guī)范,性能,安全性等方面考慮,一般是禁止跨庫join的。那該怎么辦呢?首先要考慮下垂直分庫的設(shè)計(jì)問題,如果可以調(diào)整,那就優(yōu)先調(diào)整。如果無法調(diào)整的情況,下面筆者將結(jié)合以往的實(shí)際經(jīng)驗(yàn),總結(jié)幾種常見的解決思路,并分析其適用場景。
跨庫Join的幾種解決思路
全局表
所謂全局表,就是有可能系統(tǒng)中所有模塊都可能會(huì)依賴到的一些表。比較類似我們理解的“數(shù)據(jù)字典”。為了避免跨庫join查詢,我們可以將這類表在其他每個(gè)數(shù)據(jù)庫中均保存一份。同時(shí),這類數(shù)據(jù)通常也很少發(fā)生修改(甚至幾乎不會(huì)),所以也不用太擔(dān)心“一致性”問題。
字段冗余
這是一種典型的反范式設(shè)計(jì),在互聯(lián)網(wǎng)行業(yè)中比較常見,通常是為了性能來避免join查詢。
舉個(gè)電商業(yè)務(wù)中很簡單的場景:
“訂單表”中保存“賣家Id”的同時(shí),將賣家的“Name”字段也冗余,這樣查詢訂單詳情的時(shí)候就不需要再去查詢“賣家用戶表”。
字段冗余能帶來便利,是一種“空間換時(shí)間”的體現(xiàn)。但其適用場景也比較有限,比較適合依賴字段較少的情況。最復(fù)雜的還是數(shù)據(jù)一致性問題,這點(diǎn)很難保證,可以借助數(shù)據(jù)庫中的觸發(fā)器或者在業(yè)務(wù)代碼層面去保證。當(dāng)然,也需要結(jié)合實(shí)際業(yè)務(wù)場景來看一致性的要求。就像上面例子,如果賣家修改了Name之后,是否需要在訂單信息中同步更新呢?
數(shù)據(jù)同步
定時(shí)A庫中的tab_a表和B庫中tbl_b有關(guān)聯(lián),可以定時(shí)將指定的表做同步。當(dāng)然,同步本來會(huì)對數(shù)據(jù)庫帶來一定的影響,需要性能影響和數(shù)據(jù)時(shí)效性中取得一個(gè)平衡。這樣來避免復(fù)雜的跨庫查詢。筆者曾經(jīng)在項(xiàng)目中是通過ETL工具來實(shí)施的。
系統(tǒng)層組裝
在系統(tǒng)層面,通過調(diào)用不同模塊的組件或者服務(wù),獲取到數(shù)據(jù)并進(jìn)行字段拼裝。說起來很容易,但實(shí)踐起來可真沒有這么簡單,尤其是數(shù)據(jù)庫設(shè)計(jì)上存在問題但又無法輕易調(diào)整的時(shí)候。
具體情況通常會(huì)比較復(fù)雜。下面筆者結(jié)合以往實(shí)際經(jīng)驗(yàn),并通過偽代碼方式來描述。
簡單的列表查詢的情況
偽代碼很容易理解,先獲取“我的提問列表”數(shù)據(jù),然后再根據(jù)列表中的UserId去循環(huán)調(diào)用依賴的用戶服務(wù)獲取到用戶的RealName,拼裝結(jié)果并返回。
有經(jīng)驗(yàn)的讀者一眼就能看出上訴偽代碼存在效率問題。循環(huán)調(diào)用服務(wù),可能會(huì)有循環(huán)RPC,循環(huán)查詢數(shù)據(jù)庫…不推薦使用。再看看改進(jìn)后的:
這種實(shí)現(xiàn)方式,看起來要優(yōu)雅一點(diǎn),其實(shí)就是把循環(huán)調(diào)用改成一次調(diào)用。當(dāng)然,用戶服務(wù)的數(shù)據(jù)庫查詢中很可能是In查詢,效率方面比上一種方式更高。(坊間流傳In查詢會(huì)全表掃描,存在性能問題,傳聞不可全信。其實(shí)查詢優(yōu)化器都是基本成本估算的,經(jīng)過測試,在In語句中條件字段有索引的時(shí)候,條件較少的情況是會(huì)走索引的。這里不細(xì)展開說明,感興趣的朋友請自行測試)。
小結(jié)
簡單字段組裝的情況下,我們只需要先獲取“主表”數(shù)據(jù),然后再根據(jù)關(guān)聯(lián)關(guān)系,調(diào)用其他模塊的組件或服務(wù)來獲取依賴的其他字段(如例中依賴的用戶信息),最后將數(shù)據(jù)進(jìn)行組裝。
通常,我們都會(huì)通過緩存來避免頻繁RPC通信和數(shù)據(jù)庫查詢的開銷。
列表查詢帶條件過濾的情況
在上述例子中,都是簡單的字段組裝,而不存在條件過濾。看拆分前的SQL:
這種連接查詢并且還帶條件過濾的情況,想在代碼層面組裝數(shù)據(jù)其實(shí)是非常復(fù)雜的(尤其是左表和右表都帶條件過濾的情況會(huì)更復(fù)雜),不能像之前例子中那樣簡單的進(jìn)行組裝了。試想一下,如果像上面那樣簡單的進(jìn)行組裝,造成的結(jié)果就是返回的數(shù)據(jù)不完整,不準(zhǔn)確。
有如下幾種解決思路:
查出所有的問答數(shù)據(jù),然后調(diào)用用戶服務(wù)進(jìn)行拼裝數(shù)據(jù),再根據(jù)過濾字段state字段進(jìn)行過濾,最后進(jìn)行排序和分頁并返回。
這種方式能夠保證數(shù)據(jù)的準(zhǔn)確性和完整性,但是性能影響非常大,不建議使用。
查詢出state字段符合/不符合的UserId,在查詢問答數(shù)據(jù)的時(shí)候使用in/not in進(jìn)行過濾,排序,分頁等。過濾出有效的問答數(shù)據(jù)后,再調(diào)用用戶服務(wù)獲取數(shù)據(jù)進(jìn)行組裝。
這種方式明顯更優(yōu)雅點(diǎn)。筆者之前在某個(gè)項(xiàng)目的特殊場景中就是采用過這種方式實(shí)現(xiàn)。
跨庫事務(wù)(分布式事務(wù))的問題
按業(yè)務(wù)拆分?jǐn)?shù)據(jù)庫之后,不可避免的就是“分布式事務(wù)”的問題。以往在代碼中通過spring注解簡單配置就能實(shí)現(xiàn)事務(wù)的,現(xiàn)在則需要花很大的成本去保證一致性。這里不展開介紹,
感興趣的讀者可以自行參考《分布式事務(wù)一致性解決方案》,鏈接地址:
http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency
垂直分庫總結(jié)和實(shí)踐建議
本篇中主要描述了幾種常見的拆分方式,并著重介紹了垂直分庫帶來的一些問題和解決思路。讀者朋友可能還有些問題和疑惑。
1. 我們目前的數(shù)據(jù)庫是否需要進(jìn)行垂直分庫?
根據(jù)系統(tǒng)架構(gòu)和公司實(shí)際情況來,如果你們的系統(tǒng)還是個(gè)簡單的單體應(yīng)用,并且沒有什么訪問量和數(shù)據(jù)量,那就別著急折騰“垂直分庫”了,否則沒有任何收益,也很難有好結(jié)果。
切記,“過度設(shè)計(jì)”和“過早優(yōu)化”是很多架構(gòu)師和技術(shù)人員常犯的毛病。
2. 垂直拆分有沒有原則或者技巧?
沒有什么黃金法則和標(biāo)準(zhǔn)答案。一般是參考系統(tǒng)的業(yè)務(wù)模塊拆分來進(jìn)行數(shù)據(jù)庫的拆分。比如“用戶服務(wù)”,對應(yīng)的可能就是“用戶數(shù)據(jù)庫”。但是也不一定嚴(yán)格一一對應(yīng)。有些情況下,數(shù)據(jù)庫拆分的粒度可能會(huì)比系統(tǒng)拆分的粒度更粗。筆者也確實(shí)見過有些系統(tǒng)中的某些表原本應(yīng)該放A庫中的,卻放在了B庫中。有些庫和表原本是可以合并的,卻單獨(dú)保存著。還有些表,看起來放在A庫中也OK,放在B庫中也合理。
如何設(shè)計(jì)和權(quán)衡,這個(gè)就看實(shí)際情況和架構(gòu)師/開發(fā)人員的水平了。
3. 上面舉例的都太簡單了,我們的后臺(tái)報(bào)表系統(tǒng)中join的表都有n個(gè)了,
分庫后該怎么查?
有很多朋友跟我提過類似的問題。其實(shí)互聯(lián)網(wǎng)的業(yè)務(wù)系統(tǒng)中,本來就應(yīng)該盡量避免join的,如果有多個(gè)join的,要么是設(shè)計(jì)不合理,要么是技術(shù)選型有誤。請自行科普下OLAP和OLTP,報(bào)表類的系統(tǒng)在傳統(tǒng)BI時(shí)代都是通過OLAP數(shù)據(jù)倉庫去實(shí)現(xiàn)的(現(xiàn)在則更多是借助離線分析、流式計(jì)算等手段實(shí)現(xiàn)),而不該向上面描述的那樣直接在業(yè)務(wù)庫中執(zhí)行大量join和統(tǒng)計(jì)。
由于篇幅關(guān)系,下篇中我們再繼續(xù)細(xì)聊“水平分庫分表”相關(guān)的話題。
在之前的文章中,我介紹了分庫分表的幾種表現(xiàn)形式和玩法,也重點(diǎn)介紹了垂直分庫所帶來的問題和解決方法。本篇中,我們將繼續(xù)聊聊水平分庫分表的一些技巧。
分片技術(shù)的由來
關(guān)系型數(shù)據(jù)庫本身比較容易成為系統(tǒng)性能瓶頸,單機(jī)存儲(chǔ)容量、連接數(shù)、處理能力等都很有限,數(shù)據(jù)庫本身的“有狀態(tài)性”導(dǎo)致了它并不像Web和應(yīng)用服務(wù)器那么容易擴(kuò)展。在互聯(lián)網(wǎng)行業(yè)海量數(shù)據(jù)和高并發(fā)訪問的考驗(yàn)下,聰明的技術(shù)人員提出了分庫分表技術(shù)(有些地方也稱為Sharding、分片)。同時(shí),流行的分布式系統(tǒng)中間件(例如MongoDB、ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小異的。
分布式全局唯一ID
在很多中小項(xiàng)目中,我們往往直接使用數(shù)據(jù)庫自增特性來生成主鍵ID,這樣確實(shí)比較簡單。而在分庫分表的環(huán)境中,數(shù)據(jù)分布在不同的分片上,不能再借助數(shù)據(jù)庫自增長特性直接生成,否則會(huì)造成不同分片上的數(shù)據(jù)表主鍵會(huì)重復(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)項(xiàng)目中使用最多的,未發(fā)現(xiàn)重復(fù)或并發(fā)的問題。該算法生成的是64位唯一Id(由41位的timestamp+ 10位自定義的機(jī)器碼+ 13位累加計(jì)數(shù)器組成)。這里不做過多介紹,感興趣的讀者可自行查閱相關(guān)資料。
常見分片規(guī)則和策略
分片字段該如何選擇
在開始分片之前,我們首先要確定分片字段(也可稱為“片鍵”)。很多常見的例子和場景中是采用ID或者時(shí)間字段進(jìn)行拆分。這也并不絕對的,我的建議是結(jié)合實(shí)際業(yè)務(wù),通過對系統(tǒng)中執(zhí)行的sql語句進(jìn)行統(tǒng)計(jì)分析,選擇出需要分片的那個(gè)表中最頻繁被使用,或者最重要的字段來作為分片字段。
常見分片規(guī)則
常見的分片策略有隨機(jī)分片和連續(xù)分片這兩種,如下圖所示:
當(dāng)需要使用分片字段進(jìn)行范圍查找時(shí),連續(xù)分片可以快速定位分片進(jìn)行高效查詢,大多數(shù)情況下可以有效避免跨分片查詢的問題。后期如果想對整個(gè)分片集群擴(kuò)容時(shí),只需要添加節(jié)點(diǎn)即可,無需對其他分片的數(shù)據(jù)進(jìn)行遷移。但是,連續(xù)分片也有可能存在數(shù)據(jù)熱點(diǎn)的問題,就像圖中按時(shí)間字段分片的例子,有些節(jié)點(diǎn)可能會(huì)被頻繁查詢壓力較大,熱數(shù)據(jù)節(jié)點(diǎn)就成為了整個(gè)集群的瓶頸。而有些節(jié)點(diǎn)可能存的是歷史數(shù)據(jù),很少需要被查詢到。
隨機(jī)分片其實(shí)并不是隨機(jī)的,也遵循一定規(guī)則。通常,我們會(huì)采用Hash取模的方式進(jìn)行分片拆分,所以有些時(shí)候也被稱為離散分片。隨機(jī)分片的數(shù)據(jù)相對比較均勻,不容易出現(xiàn)熱點(diǎn)和并發(fā)訪問的瓶頸。但是,后期分片集群擴(kuò)容起來需要遷移舊的數(shù)據(jù)。使用一致性Hash算法能夠很大程度的避免這個(gè)問題,所以很多中間件的分片集群都會(huì)采用一致性Hash算法。離散分片也很容易面臨跨分片查詢的復(fù)雜問題。
數(shù)據(jù)遷移,容量規(guī)劃,擴(kuò)容等問題
很少有項(xiàng)目會(huì)在初期就開始考慮分片設(shè)計(jì)的,一般都是在業(yè)務(wù)高速發(fā)展面臨性能和存儲(chǔ)的瓶頸時(shí)才會(huì)提前準(zhǔn)備。因此,不可避免的就需要考慮歷史數(shù)據(jù)遷移的問題。一般做法就是通過程序先讀出歷史數(shù)據(jù),然后按照指定的分片規(guī)則再將數(shù)據(jù)寫入到各個(gè)分片節(jié)點(diǎn)中。
此外,我們需要根據(jù)當(dāng)前的數(shù)據(jù)量和QPS等進(jìn)行容量規(guī)劃,綜合成本因素,推算出大概需要多少分片(一般建議單個(gè)分片上的單表數(shù)據(jù)量不要超過1000W)。
如果是采用隨機(jī)分片,則需要考慮后期的擴(kuò)容問題,相對會(huì)比較麻煩。如果是采用的范圍分片,只需要添加節(jié)點(diǎn)就可以自動(dòng)擴(kuò)容。
跨分片技術(shù)問題
跨分片的排序分頁
一般來講,分頁時(shí)需要按照指定字段進(jìn)行排序。當(dāng)排序字段就是分片字段的時(shí)候,我們通過分片規(guī)則可以比較容易定位到指定的分片,而當(dāng)排序字段非分片字段的時(shí)候,情況就會(huì)變得比較復(fù)雜了。為了最終結(jié)果的準(zhǔn)確性,我們需要在不同的分片節(jié)點(diǎn)中將數(shù)據(jù)進(jìn)行排序并返回,并將不同分片返回的結(jié)果集進(jìn)行匯總和再次排序,最后再返回給用戶。如下圖所示:
上面圖中所描述的只是最簡單的一種情況(取第一頁數(shù)據(jù)),看起來對性能的影響并不大。但是,如果想取出第10頁數(shù)據(jù),情況又將變得復(fù)雜很多,如下圖所示:
有些讀者可能并不太理解,為什么不能像獲取第一頁數(shù)據(jù)那樣簡單處理(排序取出前10條再合并、排序)。其實(shí)并不難理解,因?yàn)楦鞣制?jié)點(diǎn)中的數(shù)據(jù)可能是隨機(jī)的,為了排序的準(zhǔn)確性,必須把所有分片節(jié)點(diǎn)的前N頁數(shù)據(jù)都排序好后做合并,最后再進(jìn)行整體的排序。很顯然,這樣的操作是比較消耗資源的,用戶越往后翻頁,系統(tǒng)性能將會(huì)越差。
跨分片的函數(shù)處理
在使用Max、Min、Sum、Count之類的函數(shù)進(jìn)行統(tǒng)計(jì)和計(jì)算的時(shí)候,需要先在每個(gè)分片數(shù)據(jù)源上執(zhí)行相應(yīng)的函數(shù)處理,然后再將各個(gè)結(jié)果集進(jìn)行二次處理,最終再將處理結(jié)果返回。如下圖所示:
跨分片join
Join是關(guān)系型數(shù)據(jù)庫中最常用的特性,但是在分片集群中,join也變得非常復(fù)雜。應(yīng)該盡量避免跨分片的join查詢(這種場景,比上面的跨分片分頁更加復(fù)雜,而且對性能的影響很大)。通常有以下幾種方式來避免:
全局表
全局表的概念之前在“垂直分庫”時(shí)提過。基本思想一致,就是把一些類似數(shù)據(jù)字典又可能會(huì)產(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)系的表記錄存放在同一個(gè)分片上,那么就能很好的避免跨分片join問題。在一對多關(guān)系的情況下,我們通常會(huì)選擇按照數(shù)據(jù)較多的那一方進(jìn)行拆分。如下圖所示:
這樣一來,Data Node1上面的訂單表與訂單詳細(xì)表就可以直接關(guān)聯(lián),進(jìn)行局部的join查詢了,Data Node2上也一樣。基于ER分片的這種方式,能夠有效避免大多數(shù)業(yè)務(wù)場景中的跨分片join問題。
內(nèi)存計(jì)算
隨著spark內(nèi)存計(jì)算的興起,理論上來講,很多跨數(shù)據(jù)源的操作問題看起來似乎都能夠得到解決。可以將數(shù)據(jù)丟給spark集群進(jìn)行內(nèi)存計(jì)算,最后將計(jì)算結(jié)果返回。
跨分片事務(wù)問題
跨分片事務(wù)也分布式事務(wù),想要了解分布式事務(wù),就需要了解“XA接口”和“兩階段提交”。值得提到的是,MySQL5.5x和5.6x中的xa支持是存在問題的,會(huì)導(dǎo)致主從數(shù)據(jù)不一致。直到5.7x版本中才得到修復(fù)。Java應(yīng)用程序可以采用Atomikos框架來實(shí)現(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)是否需要分庫分表嗎?
其實(shí)這點(diǎn)沒有明確的判斷標(biāo)準(zhǔn),比較依賴實(shí)際業(yè)務(wù)情況和經(jīng)驗(yàn)判斷。依照筆者個(gè)人的經(jīng)驗(yàn),一般MySQL單表1000W左右的數(shù)據(jù)是沒有問題的(前提是應(yīng)用系統(tǒng)和數(shù)據(jù)庫等層面設(shè)計(jì)和優(yōu)化的比較好)。當(dāng)然,除了考慮當(dāng)前的數(shù)據(jù)量和性能情況時(shí),作為架構(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)行,因?yàn)閾?dān)心越往后公司業(yè)務(wù)發(fā)展越快、系統(tǒng)越來越復(fù)雜、系統(tǒng)重構(gòu)和擴(kuò)展越困難…這種話聽起來是有那么一點(diǎn)道理,但我的觀點(diǎn)恰好相反,對于關(guān)系型數(shù)據(jù)庫來講,我認(rèn)為“能不分片就別分片”,除非是系統(tǒng)真正需要,因?yàn)閿?shù)據(jù)庫分片并非低成本或者免費(fèi)的。
這里筆者推薦一個(gè)比較靠譜的過渡技術(shù)–“表分區(qū)”。主流的關(guān)系型數(shù)據(jù)庫中基本都支持。不同的分區(qū)在邏輯上仍是一張表,但是物理上卻是分開的,能在一定程度上提高查詢性能,而且對應(yīng)用程序透明,無需修改任何代碼。筆者曾經(jīng)負(fù)責(zé)優(yōu)化過一個(gè)系統(tǒng),主業(yè)務(wù)表有大約8000W左右的數(shù)據(jù),考慮到成本問題,當(dāng)時(shí)就是采用“表分區(qū)”來做的,效果比較明顯,且系統(tǒng)運(yùn)行的很穩(wěn)定。
小結(jié)
最后,有很多讀者都想了解當(dāng)前社區(qū)中有沒有開源免費(fèi)的分庫分表解決方案,畢竟站在巨人的肩膀上能省力很多。當(dāng)前主要有兩類解決方案:
基于應(yīng)用程序?qū)用娴腄DAL(分布式數(shù)據(jù)庫訪問層)
比較典型的就是淘寶半開源的TDDL,當(dāng)當(dāng)網(wǎng)開源的Sharding-JDBC等。分布式數(shù)據(jù)訪問層無需硬件投入,技術(shù)能力較強(qiáng)的大公司通常會(huì)選擇自研或參照開源框架進(jìn)行二次開發(fā)和定制。對應(yīng)用程序的侵入性一般較大,會(huì)增加技術(shù)成本和復(fù)雜度。通常僅支持特定編程語言平臺(tái)(Java平臺(tái)的居多),或者僅支持特定的數(shù)據(jù)庫和特定數(shù)據(jù)訪問框架技術(shù)(一般支持MySQL數(shù)據(jù)庫,JDBC、MyBatis、Hibernate等框架技術(shù))。
數(shù)據(jù)庫中間件,比較典型的像mycat(在阿里開源的cobar基礎(chǔ)上做了很多優(yōu)化和改進(jìn),屬于后起之秀,也支持很多新特性),基于Go語言實(shí)現(xiàn)kingSharding,比較老牌的Atlas(由360開源)等。這些中間件在互聯(lián)網(wǎng)企業(yè)中大量被使用。另外,MySQL 5.x企業(yè)版中官方提供的Fabric組件也號(hào)稱支持分片技術(shù),不過國內(nèi)使用的企業(yè)較少。
中間件也可以稱為“透明網(wǎng)關(guān)”,大名鼎鼎的mysql_proxy大概是該領(lǐng)域的鼻祖(由MySQL官方提供,僅限于實(shí)現(xiàn)“讀寫分離”)。中間件一般實(shí)現(xiàn)了特定數(shù)據(jù)庫的網(wǎng)絡(luò)通信協(xié)議,模擬一個(gè)真實(shí)的數(shù)據(jù)庫服務(wù),屏蔽了后端真實(shí)的Server,應(yīng)用程序通常直接連接中間件即可。而在執(zhí)行SQL操作時(shí),中間件會(huì)按照預(yù)先定義分片規(guī)則,對SQL語句進(jìn)行解析、路由,并對結(jié)果集做二次計(jì)算再最終返回。引入數(shù)據(jù)庫中間件的技術(shù)成本更低,對應(yīng)用程序來講侵入性幾乎沒有,可以滿足大部分的業(yè)務(wù)。增加了額外的硬件投入和運(yùn)維成本,同時(shí),中間件自身也存在性能瓶頸和單點(diǎn)故障問題,需要能夠保證中間件自身的高可用、可擴(kuò)展。
總之,不管是使用分布式數(shù)據(jù)訪問層還是數(shù)據(jù)庫中間件,都會(huì)帶來一定的成本和復(fù)雜度,也會(huì)有一定的性能影響。所以,還需讀者根據(jù)實(shí)際情況和業(yè)務(wù)發(fā)展需要慎重考慮和選擇。