一種以ID特征為依據(jù)的數(shù)據(jù)分片(Sharding)策略

0 評(píng)論 2325 瀏覽 0 收藏 21 分鐘

假如您有一個(gè)應(yīng)用程序,隨著業(yè)務(wù)越來越有起色,系統(tǒng)所牽涉到的數(shù)據(jù)量也就越來越大,此時(shí)您要涉及到對(duì)系統(tǒng)進(jìn)行伸縮(Scale)的問題了。一種典型的擴(kuò)展方法叫做“向上伸縮(Scale-Up)”,它的意思是通過使用更好的硬件來提高系統(tǒng)的性能參數(shù)。而另一種方法則叫做“向外伸縮(Scale-Out)”,它是指通過增加額外的硬件(如服務(wù)器)來達(dá)到相同的效果。從“硬件成本”還是“系統(tǒng)極限”的角度來說,“向外伸縮”一般都會(huì)優(yōu)于“向上伸縮”,因此大部分上規(guī)模的系統(tǒng)都會(huì)在一定程度上考慮“向外”的方式。由于許多系統(tǒng)的瓶頸都處在數(shù)據(jù)存儲(chǔ)上,因此一種叫做“數(shù)據(jù)分片(Database Sharding)”的數(shù)據(jù)架構(gòu)方式應(yīng)運(yùn)而生,本文便會(huì)討論這種數(shù)據(jù)架構(gòu)方式的一種比較典型的實(shí)現(xiàn)方式。

簡介

數(shù)據(jù)分片,自然便是將整體數(shù)據(jù)分?jǐn)傇诙鄠€(gè)存儲(chǔ)設(shè)備(下文統(tǒng)稱為“數(shù)據(jù)分區(qū)”或“分區(qū)”)上,這樣每個(gè)存儲(chǔ)設(shè)備的數(shù)據(jù)量相對(duì)就會(huì)小很多,以此滿足系統(tǒng)的性能需求。值得注意的是,系統(tǒng)分片的策略有很多,例如常見的有以下幾種:

  • 根據(jù)ID特征:例如對(duì)記錄的ID取模,得到的結(jié)果是幾,那么這條記錄就放在編號(hào)為幾的數(shù)據(jù)分區(qū)上。
  • 根據(jù)時(shí)間范圍:例如前100萬個(gè)用戶數(shù)據(jù)在第1個(gè)分區(qū)中,第二個(gè)100萬用戶數(shù)據(jù)放在第2個(gè)分區(qū)中。
  • 基于檢索表:根據(jù)ID先去一個(gè)表內(nèi)找到它所在的分區(qū),然后再去目標(biāo)分區(qū)進(jìn)行查找。
  • ……

在這些數(shù)據(jù)分片策略之中沒有哪個(gè)有絕對(duì)的優(yōu)勢(shì),選擇哪種策略完全是根據(jù)系統(tǒng)的業(yè)務(wù)或是數(shù)據(jù)特征來確定的。值得強(qiáng)調(diào)的是:數(shù)據(jù)分片不是銀彈,它對(duì)系統(tǒng)的性能和伸縮性(Scalability)帶來一定好處的同時(shí),也會(huì)對(duì)系統(tǒng)開發(fā)帶來許多復(fù)雜度。例如,有兩條記錄分別處在不同的服務(wù)器上,那么如果有一個(gè)業(yè)務(wù)是為它們建立一個(gè)“關(guān)聯(lián)”,那么很可能表示“關(guān)聯(lián)”的記錄就必須在兩個(gè)分區(qū)內(nèi)各放一條。另外,如果您重視數(shù)據(jù)的完整性,那么跨數(shù)據(jù)分區(qū)的事務(wù)又立即變成了性能殺手。最后,如果有一些需要進(jìn)行全局查找的業(yè)務(wù),光有數(shù)據(jù)分片策略也很難對(duì)系統(tǒng)性能帶來什么優(yōu)勢(shì)。

數(shù)據(jù)分片雖然重要,但在使用之前一定要三思而后行。一旦踏上這艘賊船,往往不成功便成仁,很難回頭。在我的經(jīng)驗(yàn)里,一個(gè)濫用數(shù)據(jù)分片策略而事倍功半的項(xiàng)目給我留下了非常深刻的印象(當(dāng)然也有成功的啦),因此目前我對(duì)待數(shù)據(jù)分片策略變得愈發(fā)謹(jǐn)慎。

那么現(xiàn)在,我們便來討論一種比較常見的數(shù)據(jù)分片策略。

策略描述

這里我先描述一個(gè)極其簡單的業(yè)務(wù):

  1. 系統(tǒng)中有用戶,用戶可以發(fā)表文章,文章會(huì)有評(píng)論
  2. 可以根據(jù)用戶查找文章
  3. 可以根據(jù)文章查找評(píng)論

那么,如果我要對(duì)這樣一個(gè)系統(tǒng)進(jìn)行數(shù)據(jù)分片又該怎么做呢?這里我們可以使用上面提到的第一種方式,即對(duì)記錄的ID取模,并根據(jù)結(jié)果選擇數(shù)據(jù)所在的分區(qū)。根據(jù)后兩條業(yè)務(wù)中描述的查詢要求,我們會(huì)為分區(qū)策略補(bǔ)充這樣的規(guī)則:

  • 某個(gè)用戶的所有文章,與這個(gè)用戶處在同一數(shù)據(jù)分區(qū)內(nèi)。
  • 某篇文章的所有評(píng)論,與這篇文章處在用一數(shù)據(jù)分區(qū)內(nèi)。

您可能會(huì)說,似乎只要保證“相同用戶文章在同一個(gè)數(shù)據(jù)分區(qū)內(nèi)”就行了,不是嗎?沒錯(cuò),不過我這里讓文章和用戶在同一個(gè)分區(qū)內(nèi),也是為了方便許多額外的操作(例如在關(guān)系數(shù)據(jù)庫中進(jìn)行連接)。那么假設(shè)我們有4個(gè)數(shù)據(jù)分區(qū),那么它們內(nèi)部的條目可能便是:

分區(qū)0 分區(qū)1
  • User 4
    • Article 8
    • Article 12
      • Comment 4
      • Comment 16
  • User 12
    • Article 4
  • User 1
    • Article 5
    • Article 9
      • Comment 13
      • Comment 17
  • User 5
    • Article 13
分區(qū)2 分區(qū)3
  • User 2
    • Article 10
    • Article 14
      • Comment 6
      • Comment 10
  • User 10
    • Article 4
  • User 7
    • Article 7
    • Article 11
      • Comment 3
      • Comment 15
  • User 11
    • Article 4

在ID為0的分區(qū)中,所有對(duì)象的ID模4均為0,其他分區(qū)里的對(duì)象也有這樣的規(guī)律。那么好,在實(shí)際應(yīng)用中,如果我們需要查找“ID為2的用戶”,便去第2分區(qū)搜索便是;如果要查找“ID為8的文章的所有評(píng)論”那么也只要去第0分區(qū)進(jìn)行一次查詢即可。既然查詢不成問題,那么我們?cè)撊绾翁砑有掠涗浤??其?shí)這也不難,只要:

  • 添加新用戶時(shí),隨機(jī)選擇一個(gè)數(shù)據(jù)分區(qū)
  • 添加新文章時(shí),選擇文章作者所在分區(qū)(可根據(jù)Article的UserID求模得到)
  • 添加新評(píng)論時(shí),選擇文章所在分區(qū)(可根據(jù)Comment的ArticleID求模得到)

但是,我們又如何保證新紀(jì)錄的ID正好滿足我們的分區(qū)規(guī)律?例如我們向第3分區(qū)添加的新數(shù)據(jù),則它的ID必須是3、7、11等等。以前,我們可能會(huì)使用數(shù)據(jù)庫的自增列作為ID的值,但這似乎不能滿足我們“取?!钡囊蟆R郧拔覀兛赡苓€會(huì)使用GUID,但是我們?nèi)绾紊梢粋€(gè)“被4模于3”的GUID呢?其實(shí)我們還是可以使用自增ID來解決這個(gè)問題,只不過需要進(jìn)行一些簡單的設(shè)置。例如在SQL Server中,默認(rèn)的自增ID屬性為IDENTITY(1, 1),表示ID從1開始,以1為間距自動(dòng)增長。于是我們?cè)趧?chuàng)建數(shù)據(jù)分區(qū)的時(shí)候,每個(gè)自增列的屬性則可以設(shè)置為:

  • 分區(qū)0:IDENTITY(4, 4)
  • 分區(qū)1:IDENTITY(1, 4)
  • 分區(qū)2:IDENTITY(2, 4)
  • 分區(qū)3:IDENTITY(3, 4)

這樣,ID方面的問題便交由數(shù)據(jù)庫來關(guān)心吧,我們的使用方式和以前并沒有什么區(qū)別。

缺陷

那么這個(gè)數(shù)據(jù)分片策略有什么缺陷呢?當(dāng)然缺陷還是有很多啦,只是大多數(shù)問題可能還是要和業(yè)務(wù)放在一起考慮時(shí)才會(huì)凸顯出來。不過有一個(gè)問題倒和業(yè)務(wù)關(guān)系不大:如果數(shù)據(jù)繼續(xù)增長,單個(gè)數(shù)據(jù)分區(qū)的數(shù)據(jù)量也超標(biāo)了,怎么辦?

自然,繼續(xù)拆分咯。那么我們使用什么分區(qū)規(guī)則呢?和原先一致嗎?我們舉個(gè)例子便知。假設(shè)我們?cè)?個(gè)分區(qū),有一個(gè)ID為1的用戶落在第1分區(qū)里,他的文章也都在這個(gè)分區(qū)里,ID分別是1、5、9、13、17等等。于是在某一天,我們需要將分區(qū)數(shù)量提高到5個(gè)(財(cái)力有限,一臺(tái)一臺(tái)來吧),在重新計(jì)算每篇文章所在的分區(qū)之后,我們忽然發(fā)現(xiàn):

  • ID為1的文章,模5余1,處在分區(qū)1。
  • ID為5的文章,模5余0,處在分區(qū)0。
  • ID為9的文章,模5余4,處在分區(qū)4。
  • ID為13的文章,模5余3,處在分區(qū)3。
  • ID為17的文章,模5余2,處在分區(qū)2。

呼,5個(gè)分區(qū)都齊了!這說明,如果我們保持記錄原來的ID不變,是沒有辦法直接使用之前的分區(qū)規(guī)則——無論您擴(kuò)展成幾個(gè)分區(qū),(即便是從4個(gè)到8個(gè))也只能“緩解”也不能“解決”這個(gè)情況。那么這時(shí)候該如何是好呢?例如,我們可以重新分配記錄,改變?cè)蠭D,只是這么做會(huì)產(chǎn)生一個(gè)問題,便是外部URL可能也會(huì)隨著ID一起改變,這樣對(duì)SEO的折損很大。為此,我們可以制作一個(gè)查詢表:例如在查詢小于1234567的ID時(shí)(這是“老系統(tǒng)”的最大ID),假設(shè)是100,則根據(jù)查詢表得知這條記錄的新ID為7654321,再以此去數(shù)據(jù)源進(jìn)行查找。解決這類問題的方法還有幾種,但無論怎么做都會(huì)對(duì)新系統(tǒng)帶來額外的復(fù)雜度。而且,一次擴(kuò)展也罷,如果以后還要有所擴(kuò)展呢?

有朋友可能會(huì)說,取模自然會(huì)帶來這樣的問題,那么為什么不用一致性哈希(Consistent Hash)呢?現(xiàn)在一致性哈希是個(gè)很流行的東西,和Memcached一樣,如果不用上就會(huì)被一些高級(jí)架構(gòu)師所鄙視。不過在這里一致性哈希也不能解決問題。一致性哈希的目的,是希望“在增加服務(wù)器的時(shí)候降低數(shù)據(jù)移動(dòng)規(guī)模,讓盡可能多的數(shù)據(jù)保留在原有的服務(wù)器”上。而我們現(xiàn)在的問題卻是“在增加服務(wù)器的時(shí)候,讓特征相同的數(shù)據(jù)同樣放在一起”。兩個(gè)目標(biāo)不同,這并不是一致性哈希的應(yīng)用場(chǎng)景。

我在以前的一個(gè)項(xiàng)目中曾經(jīng)用過這樣的方法:根據(jù)對(duì)訪問量與數(shù)據(jù)量的預(yù)估,我們認(rèn)為使用最多24個(gè)分區(qū)便一定可以滿足性能要求(為什么是24個(gè)?因?yàn)樗鼙辉S多數(shù)字整除)。于是,從項(xiàng)目第一次在生產(chǎn)環(huán)境中部署時(shí)便創(chuàng)建了24個(gè)數(shù)據(jù)分區(qū),只不過一開始只用了2臺(tái)服務(wù)器,每臺(tái)服務(wù)器放置12個(gè)數(shù)據(jù)分區(qū)。待以后需要擴(kuò)展時(shí),則將數(shù)據(jù)分區(qū)均勻地遷移到新的服務(wù)器上即可。我們團(tuán)隊(duì)當(dāng)時(shí)便是用這種方法避免尷尬的數(shù)據(jù)分配問題。

沒錯(cuò),數(shù)據(jù)分區(qū)的數(shù)目是個(gè)限制,但您真認(rèn)為,24個(gè)數(shù)據(jù)分區(qū)還是無法滿足您的項(xiàng)目需求嗎?要知道,需要用上24個(gè)數(shù)據(jù)分區(qū)的項(xiàng)目,一般來說本身已經(jīng)有充分的時(shí)間和經(jīng)濟(jì)實(shí)力進(jìn)行架構(gòu)上的重大調(diào)整(也該調(diào)整了,幾乎沒有什么架構(gòu)可以滿足各種數(shù)據(jù)規(guī)模的需求)。此外,無論是系統(tǒng)優(yōu)化還是數(shù)據(jù)分片都可以同時(shí)運(yùn)用其他手段。

不過,我們目前還是想辦法解決這個(gè)問題吧。

策略改進(jìn)

我們之所以會(huì)遇到上面這個(gè)問題,在于我們沒有選擇好合適的策略,這個(gè)策略把一些重要的“要求”給“具體化”了,導(dǎo)致“具體化”后的結(jié)果在外部條件改變的時(shí)候,卻無法重新滿足原有的“要求”。還是以前面的案例來說明問題,其實(shí)我們“要求”其實(shí)是:

  • 某個(gè)用戶的所有文章,與這個(gè)用戶處在同一數(shù)據(jù)分區(qū)內(nèi)。
  • 某篇文章的所有評(píng)論,與這篇文章處在用一數(shù)據(jù)分區(qū)內(nèi)。

而我們“具體化”以后的結(jié)果卻是:

  • 某個(gè)用戶的所有文章ID,與這個(gè)用戶的ID模4后的余數(shù)相同。
  • 某篇文章的所有評(píng)論ID,與這篇文章的ID模4后的余數(shù)相同。

之所以能如此“具體化”,這是因?yàn)橛小?個(gè)分區(qū)”這樣的前提條件在,一旦這個(gè)前提條件發(fā)生了改變,則杯具無法避免。因此,我們?cè)谥贫ㄒ?guī)則的時(shí)候,其實(shí)不應(yīng)該把前提條件給過分的“具體化”——具體化可以,但不能過度,得留有一定空間(這個(gè)稍后再談)。打個(gè)比方,還是前面的條件(XX和XX處在同一數(shù)據(jù)分區(qū)內(nèi)),但我們換一種具體化的方式:

  • 某個(gè)用戶的所有文章ID的前綴,便是這個(gè)用戶的ID。例如,ID為1的用戶的所有文章,其ID便可能是1-A1、1-A2、1-A3……
  • 某篇文章的所有評(píng)論ID,與這個(gè)文章的ID使用相同前綴。例如,ID為3-A1的文章的所有評(píng)論,其ID便可能是3-C1、3-C2、3-C3……

使用這個(gè)策略,我們便可以保證與某個(gè)用戶相關(guān)的“所有數(shù)據(jù)”都共享相同的“特征”(ID的前綴都相同),然后我們便可以根據(jù)這個(gè)特征來選擇分區(qū)——例如,還是以“取?!钡姆绞?。此時(shí),我們已經(jīng)確保了“相同分區(qū)內(nèi)的所有數(shù)據(jù)都具備相同的特征”,即便分區(qū)數(shù)量有所調(diào)整,我們也只需要根據(jù)特征重新計(jì)算分區(qū)即可,影響不大。而以前為什么不行?因?yàn)椤澳?的余數(shù)”只是“結(jié)果”而不是“特征”,這里的“特征”應(yīng)該是“追本溯源后的用戶ID相同”,而這一點(diǎn)已經(jīng)體現(xiàn)在新的策略中了。

還是通過圖示來說明問題吧。假設(shè)原有4個(gè)分區(qū),使用“取?!钡牟呗裕?/p>

分區(qū)0 分區(qū)1
  • User 4
    • Article 4-A1
    • Article 4-A2
      • Comment 4-C1
      • Comment 4-C2
  • User 12
    • Article 12-A3
  • User 1
    • Article 1-A4
    • Article 1-A5
      • Comment 1-C3
      • Comment 1-C4
  • User 5
    • Article 5-A6
分區(qū)2 分區(qū)3
  • User 2
    • Article 2-A7
    • Article 2-A8
      • Comment 2-C5
      • Comment 2-C6
  • User 10
    • Article 10-A9
  • User 7
    • Article 7-A10
    • Article 7-A11
      • Comment 7-C7
      • Comment 7-C8
  • User 11
    • Article 11-A12

當(dāng)分區(qū)數(shù)量調(diào)整為5個(gè)之后(為了避免分區(qū)3空缺,我又補(bǔ)充了一些對(duì)象):

分區(qū)0 分區(qū)1
  • User 10
    • Article 10-A9
  • User 5
    • Article 5-A6
  • User 1
    • Article 1-A4
    • Article 1-A5
      • Comment 1-C3
      • Comment 1-C4
  • User 11
    • Article 11-A12
分區(qū)2 分區(qū)3
  • User 2
    • Article 2-A7
    • Article 2-A8
      • Comment 2-C5
      • Comment 2-C6
  • User 12
    • Article 12-A3
  • User 7
    • Article 7-A10
    • Article 7-A11
      • Comment 7-C7
      • Comment 7-C8
  • User 8
    • Article 8-A12
    • Article 8-A13
      • Comment 8-C9
      • Comment 7-C10
分區(qū)4
  • User 4
    • Article 4-A1
    • Article 4-A2
      • Comment 4-C1
      • Comment 4-C2

是不是很合理?

值得一提的是,只要滿足了“特征”這個(gè)要求,其實(shí)選擇分區(qū)的方式并沒有什么限制。例如,我們可以不用“取?!钡姆绞?,而是使用“一致性哈?!薄獩]錯(cuò),這里就是一致性哈希的使用場(chǎng)景了。在利用“一致性哈希”來選擇分區(qū)之后,在添加服務(wù)器的情況下便可以相對(duì)減少數(shù)據(jù)的遷移數(shù)量了。

當(dāng)然,在實(shí)現(xiàn)時(shí)還可以運(yùn)用一些技巧。例如,我們的特征并非一定要“把用戶ID作為前綴”——畢竟用戶ID可能比較長,作為ID前綴還真有些難看(請(qǐng)想象把GUID作為ID前綴,再加上另一個(gè)GUID作為ID主體的情景)。此時(shí),我們可以把前提條件先進(jìn)行一定程度的“具體化”(但就像之前提到的,不能過度),例如我們可以把用戶ID先進(jìn)行取模,可能是1000萬,便可以得到一個(gè)落在較大區(qū)間范圍內(nèi)的數(shù)字。然后,再把這個(gè)數(shù)字作BASE64編碼,一下子前綴就縮小為4個(gè)字符以內(nèi)了。而且,1000萬這個(gè)區(qū)間范圍,無論是使用取模還是一致性哈希的方式來選擇分區(qū)都非??尚?,一般不會(huì)造成什么問題。

總結(jié)

數(shù)據(jù)分片是系統(tǒng)優(yōu)化的常用設(shè)計(jì)方式之一。正如前文所說的那樣,數(shù)據(jù)分片的做法很多,本文提到的方式只是其中一種方式。這種根據(jù)ID特征的分片方式比較容易遇到的問題之一,便是在數(shù)據(jù)分區(qū)數(shù)量改變時(shí)造成的規(guī)則沖突,這也正是我這篇文章所討論的主要內(nèi)容。從這個(gè)角度看來,其他一些分片方式,如創(chuàng)建時(shí)間也好,查找表也罷,這樣的問題反而不太常見。如果您有這方面的經(jīng)驗(yàn)或是疑惑,也歡迎與我進(jìn)行交流。

現(xiàn)在Web 2.0網(wǎng)站越來越熱門了,此類項(xiàng)目的數(shù)據(jù)量也越來越大,從近幾年的討論形式可以看出,越來越多的人在強(qiáng)調(diào)什么大規(guī)模、高性能、或是海量數(shù)據(jù)。然后,似乎每個(gè)人都會(huì)橫向切分、縱向切分、緩存、分離。我猜,再接下來,估計(jì)又會(huì)有許多人以用關(guān)系型數(shù)據(jù)庫為恥了吧?但是,想想這樣的問題:博客園和JavaEye都是國內(nèi)技術(shù)社區(qū)的翹楚,它們都只用了1臺(tái)數(shù)據(jù)庫服務(wù)器。StackOverflow是世界上最大的編程網(wǎng)站(它是使用ASP.NET MVC寫的,兄弟們記住這個(gè)經(jīng)典案例吧),似乎也只用了1臺(tái)還是2臺(tái)數(shù)據(jù)庫服務(wù)器(可能配置比較高)及SQL Server。因此,即便是單臺(tái)服務(wù)器,即便是使用關(guān)系型數(shù)據(jù)庫,它在性能方面的潛力也是非常之高的。

因此,數(shù)據(jù)分片應(yīng)該只在需要的時(shí)候才做,因?yàn)樗鼛淼膹?fù)雜度會(huì)比中心存儲(chǔ)的方式高出很多。這帶來的結(jié)果是,可能您的應(yīng)用程序還沒有用足架構(gòu)的能力就已經(jīng)失敗了,這樣各種投資也已經(jīng)浪費(fèi)了。假如您一開始用最簡單的方式去做,可能很快會(huì)帶來成長所需要空間及資源,此時(shí)再做更多投資進(jìn)行架構(gòu)優(yōu)化也不遲——架構(gòu)不是一蹴而就,而是演變得來的。當(dāng)然,第一次投入多少復(fù)雜度是個(gè)需要權(quán)衡的東西,這也是考驗(yàn)架構(gòu)師能力的地方。架構(gòu)不是空中樓閣,而是各種真實(shí)資源調(diào)配的結(jié)果。

來源:http://www.cnblogs.com/JeffreyZhao/archive/2010/03/09/sharding-by-id-characteristic.html

更多精彩內(nèi)容,請(qǐng)關(guān)注人人都是產(chǎn)品經(jīng)理微信公眾號(hào)或下載App
評(píng)論
評(píng)論請(qǐng)登錄
  1. 目前還沒評(píng)論,等你發(fā)揮!