分庫(kù)分表

一、分庫(kù)分表原則

關(guān)系型數(shù)據(jù)庫(kù)本身比較容易成為系統(tǒng)性能瓶頸,單機(jī)存儲(chǔ)容量、連接數(shù)、處理能力等都很有限,數(shù)據(jù)庫(kù)本身的“有狀態(tài)性”導(dǎo)致了它并不像Web和應(yīng)用服務(wù)器那么容易擴(kuò)展。在互聯(lián)網(wǎng)行業(yè)海量數(shù)據(jù)和高并發(fā)訪問(wèn)的考驗(yàn)下,聰明的技術(shù)人員提出了分庫(kù)分表技術(shù)(有些地方也稱(chēng)為Sharding、分片)。同時(shí),流行的分布式系統(tǒng)中間件(例如MongoDB、ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小異的。

目前針對(duì)海量數(shù)據(jù)的優(yōu)化,其分庫(kù)分表是MySQL永遠(yuǎn)的話題,一般情況下認(rèn)為MySQL是個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù),在數(shù)據(jù)量大到一定程度之后處理查詢的效率降低,如果需要繼續(xù)保持高性能運(yùn)轉(zhuǎn)的話,必須分庫(kù)或者分表了。關(guān)于數(shù)據(jù)量達(dá)到多少大是個(gè)極限這個(gè)事兒,本文先不討論,研究源碼的同學(xué)已經(jīng)證實(shí)MySQL或者Innodb內(nèi)部的鎖粒度太大的問(wèn)題大大限制了MySQL提供QPS的能力或者處理大規(guī)模數(shù)據(jù)的能力。在這點(diǎn)上,一般的使用者只好坐等官方不斷推出的優(yōu)化版本了。

在一般運(yùn)維的角度來(lái)看,我們什么情況下需要考慮分庫(kù)分表?

首先說(shuō)明,這里所說(shuō)的分庫(kù)分表是指把數(shù)據(jù)庫(kù)數(shù)據(jù)的物理拆分到多個(gè)實(shí)例或者多臺(tái)機(jī)器上去,而不是類(lèi)似分區(qū)表的原地切分。

原則零:能不分就不分

是的,MySQL 是關(guān)系數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)表之間的關(guān)系從一定的角度上映射了業(yè)務(wù)邏輯。任何分庫(kù)分表的行為都會(huì)在某種程度上提升業(yè)務(wù)邏輯的復(fù)雜度,數(shù)據(jù)庫(kù)除了承載數(shù)據(jù)的存儲(chǔ)和訪問(wèn)外,協(xié)助業(yè)務(wù)更好的實(shí)現(xiàn)需求和邏輯也是其重要工作之一。分庫(kù)分表會(huì)帶來(lái)數(shù)據(jù)的合并,查詢或者更新條件的分離,事務(wù)的分離等等多種后果,業(yè)務(wù)實(shí)現(xiàn)的復(fù)雜程度往往會(huì)翻倍或者指數(shù)級(jí)上升。所以,在分庫(kù)分表之前,不要為分而分,去做其他力所能及的事情吧,例如升級(jí)硬件,升級(jí),升級(jí)網(wǎng)絡(luò),升級(jí)數(shù)據(jù)庫(kù)版本,讀寫(xiě)分離,負(fù)載均衡等等。所有分庫(kù)分表的前提是,這些你已經(jīng)盡力了。

原則一:數(shù)據(jù)量太大,正常的運(yùn)維影響正常業(yè)務(wù)訪問(wèn)

這里說(shuō)的運(yùn)維,例如:

1)對(duì)數(shù)據(jù)庫(kù)的備份。如果單表或者單個(gè)實(shí)例太大,在做備份的時(shí)候需要大量的磁盤(pán)IO或者網(wǎng)絡(luò)IO資源。例如1T的數(shù)據(jù),網(wǎng)絡(luò)傳輸占用50MB的時(shí)候,需要20000秒才能傳輸完畢,在此整個(gè)過(guò)程中的維護(hù)風(fēng)險(xiǎn)都是高于平時(shí)的。我們?cè)赒unar的做法是給所有的數(shù)據(jù)庫(kù)機(jī)器添加第二塊網(wǎng)卡,用來(lái)做備份,或者SST,Group Communication等等各種內(nèi)部的數(shù)據(jù)傳輸。1T的數(shù)據(jù)的備份,也會(huì)占用大量的磁盤(pán)IO,如果是SSD還好,當(dāng)然這里忽略某些廠商的產(chǎn)品在集中IO的時(shí)候會(huì)出一些BUG的問(wèn)題。如果是普通的物理磁盤(pán),則在不限流的情況下去執(zhí)行xtrabackup,該實(shí)例基本不可用。

2)對(duì)數(shù)據(jù)表的修改。如果某個(gè)表過(guò)大,對(duì)此表做DDL的時(shí)候,MySQL會(huì)鎖住全表,這個(gè)時(shí)間可能很長(zhǎng),在這段時(shí)間業(yè)務(wù)不能訪問(wèn)此表,影響甚大。解決的辦法有類(lèi)似騰訊游戲DBA自己改造的可以在線秒改表,不過(guò)他們目前也只是能添加字段而已,對(duì)別的DDL還是無(wú)效;或者使用pt-online-schema-change,當(dāng)然在使用過(guò)程中,它需要建立觸發(fā)器和影子表,同時(shí)也需要很長(zhǎng)很長(zhǎng)的時(shí)間,在此操作過(guò)程中的所有時(shí)間,都可以看做是風(fēng)險(xiǎn)時(shí)間。把數(shù)據(jù)表切分,總量減小,有助于改善這種風(fēng)險(xiǎn)。

3)整個(gè)表熱點(diǎn),數(shù)據(jù)訪問(wèn)和更新頻繁,經(jīng)常有鎖等待,你又沒(méi)有能力去修改源碼,降低鎖的粒度,那么只會(huì)把其中的數(shù)據(jù)物理拆開(kāi),用空間換時(shí)間,變相降低訪問(wèn)壓力。

原則二:表設(shè)計(jì)不合理,需要對(duì)某些字段垂直拆分

這里舉一個(gè)例子,如果你有一個(gè)用戶表,在最初設(shè)計(jì)的時(shí)候可能是這樣:

|

1 id bigint #用戶的ID

2 name varchar #用戶的名字

3 last_login_time datetime #最近登錄時(shí)間

4 personal_info text #私人信息

5 xxxxx #其他信息字段

一般的users表會(huì)有很多字段,我就不列舉了。如上所示,在一個(gè)簡(jiǎn)單的應(yīng)用中,這種設(shè)計(jì)是很常見(jiàn)的。但是:

設(shè)想情況一:你的業(yè)務(wù)中彩了,用戶數(shù)從100w飆升到10個(gè)億。你為了統(tǒng)計(jì)活躍用戶,在每個(gè)人登錄的時(shí)候都會(huì)記錄一下他的最近登錄時(shí)間。并且的用戶活躍得很,不斷的去更新這個(gè)login_time,搞的你的這個(gè)表不斷的被update,壓力非常大。那么,在這個(gè)時(shí)候,只要考慮對(duì)它進(jìn)行拆分,站在業(yè)務(wù)的角度,最好的辦法是先把last_login_time拆分出去,我們叫它 user_time。這樣做,業(yè)務(wù)的代碼只有在用到這個(gè)字段的時(shí)候修改一下就行了。如果你不這么做,直接把users表水平切分了,那么,所有訪問(wèn)users表的地方,都要修改?;蛟S你會(huì)說(shuō),我有proxy,能夠動(dòng)態(tài)merge數(shù)據(jù)。到目前為止我還從沒(méi)看到誰(shuí)家的proxy不影響性能的。

設(shè)想情況二:personal_info這個(gè)字段本來(lái)沒(méi)啥用,你就是讓用戶注冊(cè)的時(shí)候填一些個(gè)人愛(ài)好而已,基本不查詢。一開(kāi)始的時(shí)候有它沒(méi)它無(wú)所謂。但是到后來(lái)發(fā)現(xiàn)兩個(gè)問(wèn)題,一,這個(gè)字段占用了大量的空間,因?yàn)槭莟ext嘛,有很多人喜歡長(zhǎng)篇大論地介紹自己。更糟糕的是二,不知道哪天哪個(gè)產(chǎn)品經(jīng)理心血來(lái)潮,說(shuō)允許個(gè)人信息公開(kāi)吧,以方便讓大家更好的相互了解。那么在所有人獵奇窺私心理的影響下,對(duì)此字段的訪問(wèn)大幅度增加。數(shù)據(jù)庫(kù)壓力瞬間抗不住了,這個(gè)時(shí)候,只好考慮對(duì)這個(gè)表的垂直拆分了。

原則三:某些數(shù)據(jù)表出現(xiàn)了無(wú)窮增長(zhǎng)

例子很好舉,各種的評(píng)論,消息,日志記錄。這個(gè)增長(zhǎng)不是跟人口成比例的,而是不可控的,例如微博的feed的廣播,我發(fā)一條消息,會(huì)擴(kuò)散給很多很多人。雖然主體可能只存一份,但不排除一些索引或者路由有這種存儲(chǔ)需求。這個(gè)時(shí)候,增加存儲(chǔ),提升機(jī)器配置已經(jīng)蒼白無(wú)力了,水平切分是最佳實(shí)踐。拆分的標(biāo)準(zhǔn)很多,按用戶的,按時(shí)間的,按用途的,不在一一舉例。

原則四:安全性和可用性的考慮

這個(gè)很容易理解,雞蛋不要放在一個(gè)籃子里,我不希望我的數(shù)據(jù)庫(kù)出問(wèn)題,但我希望在出問(wèn)題的時(shí)候不要影響到100%的用戶,這個(gè)影響的比例越少越好,那么,水平切分可以解決這個(gè)問(wèn)題,把用戶,庫(kù)存,訂單等等本來(lái)同統(tǒng)一的資源切分掉,每個(gè)小的數(shù)據(jù)庫(kù)實(shí)例承擔(dān)一小部分業(yè)務(wù),這樣整體的可用性就會(huì)提升。這對(duì)Qunar這樣的業(yè)務(wù)還是比較合適的,人與人之間,某些庫(kù)存與庫(kù)存之間,關(guān)聯(lián)不太大,可以做一些這樣的切分。

原則五:業(yè)務(wù)耦合性考慮

這個(gè)跟上面有點(diǎn)類(lèi)似,主要是站在業(yè)務(wù)的層面上,我們的火車(chē)票業(yè)務(wù)和烤羊腿業(yè)務(wù)是完全無(wú)關(guān)的業(yè)務(wù),雖然每個(gè)業(yè)務(wù)的數(shù)據(jù)量可能不太大,放在一個(gè)MySQL實(shí)例中完全沒(méi)問(wèn)題,但是很可能烤羊腿業(yè)務(wù)的DBA 或者開(kāi)發(fā)人員水平很差,動(dòng)不動(dòng)給你出一些幺蛾子,直接把數(shù)據(jù)庫(kù)搞掛。這個(gè)時(shí)候,火車(chē)票業(yè)務(wù)的人員雖然技術(shù)很優(yōu)秀,工作也很努力,照樣被老板打屁股。解決的辦法很簡(jiǎn)單:惹不起,躲得起。

二、分庫(kù)分表方案

  • 垂直拆分

垂直拆分常見(jiàn)有垂直分庫(kù)和垂直分表兩種。垂直分表在日常開(kāi)發(fā)和設(shè)計(jì)中比較常見(jiàn),通俗的說(shuō)法叫做“大表拆小表”,拆分是基于關(guān)系型數(shù)據(jù)庫(kù)中的“列”(字段)進(jìn)行的。通常情況,某個(gè)表中的字段比較多,可以新建立一張“擴(kuò)展表”,將不經(jīng)常使用或者長(zhǎng)度較大的字段拆分出去放到“擴(kuò)展表”中,如下圖所示:

在字段很多的情況下,拆分開(kāi)確實(shí)更便于開(kāi)發(fā)和維護(hù)(筆者曾見(jiàn)過(guò)某個(gè)遺留系統(tǒng)中,一個(gè)大表中包含100多列的)。某種意義上也能避免“跨頁(yè)”的問(wèn)題(MySQL、MSSQL底層都是通過(guò)“數(shù)據(jù)頁(yè)”來(lái)存儲(chǔ)的,“跨頁(yè)”問(wèn)題可能會(huì)造成額外的性能開(kāi)銷(xiāo),這里不展開(kāi),感興趣的朋友可以自行查閱相關(guān)資料進(jìn)行研究)。

拆分字段的操作建議在數(shù)據(jù)庫(kù)設(shè)計(jì)階段就做好。如果是在發(fā)展過(guò)程中拆分,則需要改寫(xiě)以前的查詢語(yǔ)句,會(huì)額外帶來(lái)一定的成本和風(fēng)險(xiǎn),建議謹(jǐn)慎。

垂直分庫(kù)是根據(jù)數(shù)據(jù)庫(kù)里面的數(shù)據(jù)表的相關(guān)性進(jìn)行拆分,比如:一個(gè)數(shù)據(jù)庫(kù)里面既存在用戶數(shù)據(jù),又存在訂單數(shù)據(jù),那么垂直拆分可以把用戶數(shù)據(jù)放到用戶庫(kù)、把訂單數(shù)據(jù)放到訂單庫(kù)。垂直分表是對(duì)數(shù)據(jù)表進(jìn)行垂直拆分的一種方式,常見(jiàn)的是把一個(gè)多字段的大表按常用字段和非常用字段進(jìn)行拆分,每個(gè)表里面的數(shù)據(jù)記錄數(shù)一般情況下是相同的,只是字段不一樣,使用主鍵關(guān)聯(lián)。

另外,在“微服務(wù)”盛行的今天已經(jīng)非常普及了,按照業(yè)務(wù)模塊來(lái)劃分出不同的數(shù)據(jù)庫(kù),也是一種垂直拆分。而不是像早期一樣將所有的數(shù)據(jù)表都放到同一個(gè)數(shù)據(jù)庫(kù)中。如下圖:

垂直拆分優(yōu)點(diǎn):

  • 可以使得行數(shù)據(jù)變小,一個(gè)數(shù)據(jù)塊 (Block) 就能存放更多的數(shù)據(jù),在查詢時(shí)就會(huì)減少 I/O 次數(shù) (每次查詢時(shí)讀取的 Block 就少)。
  • 可以達(dá)到最大化利用 Cache 的目的,具體在垂直拆分的時(shí)候可以將不常變的字段放一起,將經(jīng)常改變的放一起。
  • 數(shù)據(jù)維護(hù)簡(jiǎn)單。

垂直拆分缺點(diǎn):

  • 主鍵出現(xiàn)冗余,需要管理冗余列。
  • 會(huì)引起表連接 JOIN 操作(增加 CPU 開(kāi)銷(xiāo))可以通過(guò)在業(yè)務(wù)服務(wù)器上進(jìn)行 join 來(lái)減少數(shù)據(jù)庫(kù)壓力。
  • 依然存在單表數(shù)據(jù)量過(guò)大的問(wèn)題(需要水平拆分)。
  • 事務(wù)處理復(fù)雜。

垂直拆分小結(jié):

系統(tǒng)層面的“服務(wù)化”拆分操作,能夠解決業(yè)務(wù)系統(tǒng)層面的耦合和性能瓶頸,有利于系統(tǒng)的擴(kuò)展維護(hù)。而數(shù)據(jù)庫(kù)層面的拆分,道理也是相通的。與服務(wù)的“治理”和“降級(jí)”機(jī)制類(lèi)似,我們也能對(duì)不同業(yè)務(wù)類(lèi)型的數(shù)據(jù)進(jìn)行“分級(jí)”管理、維護(hù)、監(jiān)控、擴(kuò)展等。

眾所周知,數(shù)據(jù)庫(kù)往往最容易成為應(yīng)用系統(tǒng)的瓶頸,而數(shù)據(jù)庫(kù)本身屬于“有狀態(tài)”的,相對(duì)于Web和應(yīng)用服務(wù)器來(lái)講,是比較難實(shí)現(xiàn)“橫向擴(kuò)展”的。數(shù)據(jù)庫(kù)的連接資源比較寶貴且單機(jī)處理能力也有限,在高并發(fā)場(chǎng)景下,垂直分庫(kù)一定程度上能夠突破IO、連接數(shù)及單機(jī)硬件資源的瓶頸,是大型分布式系統(tǒng)中優(yōu)化數(shù)據(jù)庫(kù)架構(gòu)的重要手段。

然后,很多人并沒(méi)有從根本上搞清楚為什么要拆分,也沒(méi)有掌握拆分的原則和技巧,只是一味的模仿大廠的做法。導(dǎo)致拆分后遇到很多問(wèn)題(例如:跨庫(kù)join,分布式事務(wù)等)。

  • 水平拆分

水平拆分是通過(guò)某種策略將數(shù)據(jù)分片來(lái)存儲(chǔ),分為庫(kù)內(nèi)分表和分庫(kù)分表兩部分,每片數(shù)據(jù)會(huì)分散到不同的MySQL表或庫(kù),達(dá)到分布式的效果,能夠支持非常大的數(shù)據(jù)量。

庫(kù)內(nèi)分表,僅僅是單純的解決了單一表數(shù)據(jù)過(guò)大的問(wèn)題,由于沒(méi)有把表的數(shù)據(jù)分布到不同的機(jī)器上,因此對(duì)于減輕 MySQL 服務(wù)器的壓力來(lái)說(shuō),并沒(méi)有太大的作用,大家還是競(jìng)爭(zhēng)同一個(gè)物理機(jī)上的 IO、CPU、網(wǎng)絡(luò),這個(gè)就要通過(guò)分庫(kù)分表來(lái)解決。

最常見(jiàn)的方式就是通過(guò)主鍵或者時(shí)間等字段進(jìn)行Hash和取模后拆分。如下圖所示:

當(dāng)下分表有靜態(tài)分表和動(dòng)態(tài)分表兩種:

靜態(tài)分表:事先估算出表能達(dá)到的量,然后根據(jù)每一個(gè)表需要存多少數(shù)據(jù)直接算出需要?jiǎng)?chuàng)建表的數(shù)量。如:1億數(shù)據(jù)每一個(gè)表100W條數(shù)據(jù)那就要建100張表,然后通過(guò)一定的hash算法計(jì)算每一條數(shù)據(jù)存放在那張表。其實(shí)就有點(diǎn)像是使用partition table一樣。靜態(tài)分表有一個(gè)斃命就是當(dāng)分的那么多表還不滿足時(shí),需要再擴(kuò)展難度和成本就會(huì)很高。

動(dòng)態(tài)分表:同樣也是對(duì)大數(shù)據(jù)量的表進(jìn)行拆分,他可以避免靜態(tài)分表帶來(lái)的后遺癥。當(dāng)然也需要在設(shè)計(jì)上多一些東西(這往往是我們能接受的)。

某種意義上來(lái)講,有些系統(tǒng)中使用的“冷熱數(shù)據(jù)分離”(將一些使用較少的歷史數(shù)據(jù)遷移到其他的數(shù)據(jù)庫(kù)中。而在業(yè)務(wù)功能上,通常默認(rèn)只提供熱點(diǎn)數(shù)據(jù)的查詢),也是類(lèi)似的實(shí)踐。在高并發(fā)和海量數(shù)據(jù)的場(chǎng)景下,分庫(kù)分表能夠有效緩解單機(jī)和單庫(kù)的性能瓶頸和壓力,突破IO、連接數(shù)、硬件資源的瓶頸。當(dāng)然,投入的硬件成本也會(huì)更高。同時(shí),這也會(huì)帶來(lái)一些復(fù)雜的技術(shù)問(wèn)題和挑戰(zhàn)(例如:跨分片的復(fù)雜查詢,跨分片事務(wù)等)。

水平拆分優(yōu)點(diǎn):

  • 不存在單庫(kù)大數(shù)據(jù)和高并發(fā)的性能瓶頸。
  • 應(yīng)用端改造較少。
  • 提高了系統(tǒng)的穩(wěn)定性和負(fù)載能力。

水平拆分缺點(diǎn):

  • 分片事務(wù)一致性難以解決。
  • 跨節(jié)點(diǎn) Join 性能差,邏輯復(fù)雜。
  • 數(shù)據(jù)多次擴(kuò)展難度跟維護(hù)量極大。

三、分庫(kù)分表難點(diǎn)

垂直分庫(kù)帶來(lái)的問(wèn)題和解決思路:

  • 跨庫(kù)join的問(wèn)題

在拆分之前,系統(tǒng)中很多列表和詳情頁(yè)所需的數(shù)據(jù)是可以通過(guò)sql join來(lái)完成的。而拆分后,數(shù)據(jù)庫(kù)可能是分布式在不同實(shí)例和不同的主機(jī)上,join將變得非常麻煩。而且基于架構(gòu)規(guī)范,性能,安全性等方面考慮,一般是禁止跨庫(kù)join的。那該怎么辦呢?首先要考慮下垂直分庫(kù)的設(shè)計(jì)問(wèn)題,如果可以調(diào)整,那就優(yōu)先調(diào)整。如果無(wú)法調(diào)整的情況,下面筆者將結(jié)合以往的實(shí)際經(jīng)驗(yàn),總結(jié)幾種常見(jiàn)的解決思路,并分析其適用場(chǎng)景。

跨庫(kù)Join的幾種解決思路

全局表

所謂全局表,就是有可能系統(tǒng)中所有模塊都可能會(huì)依賴(lài)到的一些表。比較類(lèi)似我們理解的“數(shù)據(jù)字典”。為了避免跨庫(kù)join查詢,我們可以將這類(lèi)表在其他每個(gè)數(shù)據(jù)庫(kù)中均保存一份。同時(shí),這類(lèi)數(shù)據(jù)通常也很少發(fā)生修改(甚至幾乎不會(huì)),所以也不用太擔(dān)心“一致性”問(wèn)題。

字段冗余

這是一種典型的反范式設(shè)計(jì),在互聯(lián)網(wǎng)行業(yè)中比較常見(jiàn),通常是為了性能來(lái)避免join查詢。

舉個(gè)電商業(yè)務(wù)中很簡(jiǎn)單的場(chǎng)景:“訂單表”中保存“賣(mài)家Id”的同時(shí),將賣(mài)家的“Name”字段也冗余,這樣查詢訂單詳情的時(shí)候就不需要再去查詢“賣(mài)家用戶表”。

字段冗余能帶來(lái)便利,是一種“空間換時(shí)間”的體現(xiàn)。但其適用場(chǎng)景也比較有限,比較適合依賴(lài)字段較少的情況。最復(fù)雜的還是數(shù)據(jù)一致性問(wèn)題,這點(diǎn)很難保證,可以借助數(shù)據(jù)庫(kù)中的觸發(fā)器或者在業(yè)務(wù)代碼層面去保證。當(dāng)然,也需要結(jié)合實(shí)際業(yè)務(wù)場(chǎng)景來(lái)看一致性的要求。就像上面例子,如果賣(mài)家修改了Name之后,是否需要在訂單信息中同步更新呢?

數(shù)據(jù)同步

定時(shí)A庫(kù)中的tab_a表和B庫(kù)中tbl_b有關(guān)聯(lián),可以定時(shí)將指定的表做同步。當(dāng)然,同步本來(lái)會(huì)對(duì)數(shù)據(jù)庫(kù)帶來(lái)一定的影響,需要性能影響和數(shù)據(jù)時(shí)效性中取得一個(gè)平衡。這樣來(lái)避免復(fù)雜的跨庫(kù)查詢。筆者曾經(jīng)在項(xiàng)目中是通過(guò)ETL工具來(lái)實(shí)施的。

系統(tǒng)層組裝

在系統(tǒng)層面,通過(guò)調(diào)用不同模塊的組件或者服務(wù),獲取到數(shù)據(jù)并進(jìn)行字段拼裝。說(shuō)起來(lái)很容易,但實(shí)踐起來(lái)可真沒(méi)有這么簡(jiǎn)單,尤其是數(shù)據(jù)庫(kù)設(shè)計(jì)上存在問(wèn)題但又無(wú)法輕易調(diào)整的時(shí)候。具體情況通常會(huì)比較復(fù)雜。

  • 跨庫(kù)事務(wù)(分布式事務(wù))問(wèn)題

按業(yè)務(wù)拆分?jǐn)?shù)據(jù)庫(kù)之后,不可避免的就是“分布式事務(wù)”的問(wèn)題。想要了解分布式事務(wù),就需要了解“XA接口”和“兩階段提交”。值得提到的是,MySQL5.5x和5.6x中的xa支持是存在問(wèn)題的,會(huì)導(dǎo)致主從數(shù)據(jù)不一致。直到5.7x版本中才得到修復(fù)。Java應(yīng)用程序可以采用Atomikos框架來(lái)實(shí)現(xiàn)XA事務(wù)(J2EE中JTA)。感興趣的讀者可以自行參考《分布式事務(wù)一致性解決方案》,鏈接地址:http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

四、常見(jiàn)分片規(guī)則和策略

  • 分布式全局唯一ID

在很多中小項(xiàng)目中,我們往往直接使用數(shù)據(jù)庫(kù)自增特性來(lái)生成主鍵ID,這樣確實(shí)比較簡(jiǎn)單。而在分庫(kù)分表的環(huán)境中,數(shù)據(jù)分布在不同的分片上,不能再借助數(shù)據(jù)庫(kù)自增長(zhǎng)特性直接生成,否則會(huì)造成不同分片上的數(shù)據(jù)表主鍵會(huì)重復(fù)。簡(jiǎn)單介紹下使用和了解過(guò)的幾種ID生成算法。

1. Twitter的Snowflake(又名“雪花算法”)

2. UUID/GUID(一般應(yīng)用程序和數(shù)據(jù)庫(kù)均支持)

3. MongoDB ObjectID(類(lèi)似UUID的方式)

4. Ticket Server(數(shù)據(jù)庫(kù)生存方式,F(xiàn)lickr采用的就是這種方式)

其中,Twitter的Snowflake算法是近幾年在分布式系統(tǒng)項(xiàng)目中使用最多的,未發(fā)現(xiàn)重復(fù)或并發(fā)的問(wèn)題。該算法生成的是64位唯一Id(由41位的timestamp+10位自定義的機(jī)器碼+13位累加計(jì)數(shù)器組成)。這里不做過(guò)多介紹,感興趣的讀者可自行查閱相關(guān)資料。

  • 分片字段該如何選擇

在開(kāi)始分片之前,我們首先要確定分片字段(也可稱(chēng)為“片鍵”)。很多常見(jiàn)的例子和場(chǎng)景中是采用ID或者時(shí)間字段進(jìn)行拆分。這也并不絕對(duì)的,我的建議是結(jié)合實(shí)際業(yè)務(wù),通過(guò)對(duì)系統(tǒng)中執(zhí)行的sql語(yǔ)句進(jìn)行統(tǒng)計(jì)分析,選擇出需要分片的那個(gè)表中最頻繁被使用,或者最重要的字段來(lái)作為分片字段。

常見(jiàn)的分片策略有隨機(jī)分片和連續(xù)分片這兩種,如下圖所示:

當(dāng)需要使用分片字段進(jìn)行范圍查找時(shí),連續(xù)分片可以快速定位分片進(jìn)行高效查詢,大多數(shù)情況下可以有效避免跨分片查詢的問(wèn)題。后期如果想對(duì)整個(gè)分片集群擴(kuò)容時(shí),只需要添加節(jié)點(diǎn)即可,無(wú)需對(duì)其他分片的數(shù)據(jù)進(jìn)行遷移。但是,連續(xù)分片也有可能存在數(shù)據(jù)熱點(diǎn)的問(wè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í)候也被稱(chēng)為離散分片。隨機(jī)分片的數(shù)據(jù)相對(duì)比較均勻,不容易出現(xiàn)熱點(diǎn)和并發(fā)訪問(wèn)的瓶頸。但是,后期分片集群擴(kuò)容起來(lái)需要遷移舊的數(shù)據(jù)。使用一致性Hash算法能夠很大程度的避免這個(gè)問(wèn)題,所以很多中間件的分片集群都會(huì)采用一致性Hash算法。離散分片也很容易面臨跨分片查詢的復(fù)雜問(wèn)題。

  • 數(shù)據(jù)遷移,容量規(guī)劃,擴(kuò)容等問(wèn)題

很少有項(xiàng)目會(huì)在初期就開(kāi)始考慮分片設(shè)計(jì)的,一般都是在業(yè)務(wù)高速發(fā)展面臨性能和存儲(chǔ)的瓶頸時(shí)才會(huì)提前準(zhǔn)備。因此,不可避免的就需要考慮歷史數(shù)據(jù)遷移的問(wèn)題。一般做法就是通過(guò)程序先讀出歷史數(shù)據(jù),然后按照指定的分片規(guī)則再將數(shù)據(jù)寫(xiě)入到各個(gè)分片節(jié)點(diǎn)中。

此外,我們需要根據(jù)當(dāng)前的數(shù)據(jù)量和QPS等進(jìn)行容量規(guī)劃,綜合成本因素,推算出大概需要多少分片(一般建議單個(gè)分片上的單表數(shù)據(jù)量不要超過(guò)1000W)。

如果是采用隨機(jī)分片,則需要考慮后期的擴(kuò)容問(wèn)題,相對(duì)會(huì)比較麻煩。如果是采用的范圍分片,只需要添加節(jié)點(diǎn)就可以自動(dòng)擴(kuò)容。

五、跨分片技術(shù)問(wèn)題

  • 跨分片的排序分頁(yè)

一般來(lái)講,分頁(yè)時(shí)需要按照指定字段進(jìn)行排序。當(dāng)排序字段就是分片字段的時(shí)候,我們通過(guò)分片規(guī)則可以比較容易定位到指定的分片,而當(dāng)排序字段非分片字段的時(shí)候,情況就會(huì)變得比較復(fù)雜了。為了最終結(jié)果的準(zhǔn)確性,我們需要在不同的分片節(jié)點(diǎn)中將數(shù)據(jù)進(jìn)行排序并返回,并將不同分片返回的結(jié)果集進(jìn)行匯總和再次排序,最后再返回給用戶。如下圖所示:

上面圖中所描述的只是最簡(jiǎn)單的一種情況(取第一頁(yè)數(shù)據(jù)),看起來(lái)對(duì)性能的影響并不大。但是,如果想取出第10頁(yè)數(shù)據(jù),情況又將變得復(fù)雜很多,如下圖所示:

有些讀者可能并不太理解,為什么不能像獲取第一頁(yè)數(shù)據(jù)那樣簡(jiǎn)單處理(排序取出前10條再合并、排序)。其實(shí)并不難理解,因?yàn)楦鞣制?jié)點(diǎn)中的數(shù)據(jù)可能是隨機(jī)的,為了排序的準(zhǔn)確性,必須把所有分片節(jié)點(diǎn)的前N頁(yè)數(shù)據(jù)都排序好后做合并,最后再進(jìn)行整體的排序。很顯然,這樣的操作是比較消耗資源的,用戶越往后翻頁(yè),系統(tǒng)性能將會(huì)越差。

  • 跨分片的函數(shù)處理

在使用Max、Min、Sum、Count之類(lèi)的函數(shù)進(jìn)行統(tǒng)計(jì)和計(jì)算的時(shí)候,需要先在每個(gè)分片數(shù)據(jù)源上執(zhí)行相應(yīng)的函數(shù)處理,然后再將各個(gè)結(jié)果集進(jìn)行二次處理,最終再將處理結(jié)果返回。如下圖所示:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容