掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
作者:xybaby 2018-08-24 07:03:45
大數(shù)據(jù)
分布式 本文主要討論數(shù)據(jù)分片的三個(gè)問題:(1)如何做數(shù)據(jù)分片,即如何將數(shù)據(jù)映射到節(jié)點(diǎn);(2)數(shù)據(jù)分片的特征值,即按照數(shù)據(jù)中的哪一個(gè)屬性(字段)來分片;(3)數(shù)據(jù)分片的元數(shù)據(jù)的管理,如何保證元數(shù)據(jù)服務(wù)器的高性能、高可用,如果是一組服務(wù)器,如何保證強(qiáng)一致性。

創(chuàng)新互聯(lián)專注于凌海網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供凌海營銷型網(wǎng)站建設(shè),凌海網(wǎng)站制作、凌海網(wǎng)頁設(shè)計(jì)、凌海網(wǎng)站官網(wǎng)定制、微信小程序開發(fā)服務(wù),打造凌海網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供凌海網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
正文
在前文中,提出了分布式系統(tǒng)(尤其是分布式存儲(chǔ)系統(tǒng))需要解決的兩個(gè)最主要的問題,即數(shù)據(jù)分片和數(shù)據(jù)冗余,下面這個(gè)圖片(來源)形象生動(dòng)的解釋了其概念和區(qū)別:
其中數(shù)據(jù)即A、B屬于數(shù)據(jù)分片,原始數(shù)據(jù)被拆分成兩個(gè)正交子集分布在兩個(gè)節(jié)點(diǎn)上。而數(shù)據(jù)集C屬于數(shù)據(jù)冗余,同一份完整的數(shù)據(jù)在兩個(gè)節(jié)點(diǎn)都有存儲(chǔ)。當(dāng)然,在實(shí)際的分布式系統(tǒng)中,數(shù)據(jù)分片和數(shù)據(jù)冗余一般都是共存的。
本文主要討論數(shù)據(jù)分片的三個(gè)問題:
所謂分布式系統(tǒng),就是利用多個(gè)獨(dú)立的計(jì)算機(jī)來解決單個(gè)節(jié)點(diǎn)(計(jì)算機(jī))無法處理的存儲(chǔ)、計(jì)算問題,這是非常典型的分而治之的思想。每個(gè)節(jié)點(diǎn)只負(fù)責(zé)原問題(即整個(gè)系統(tǒng)需要完成的任務(wù))的一個(gè)子集,那么原問題如何拆分到多個(gè)節(jié)點(diǎn)?在分布式存儲(chǔ)系統(tǒng)中,任務(wù)的拆分即數(shù)據(jù)分片。
何為數(shù)據(jù)分片(segment,fragment, shard, partition),就是按照一定的規(guī)則,將數(shù)據(jù)集劃分成相互獨(dú)立、正交的數(shù)據(jù)子集,然后將數(shù)據(jù)子集分布到不同的節(jié)點(diǎn)上。注意,這里提到,數(shù)據(jù)分片需要按照一定的規(guī)則,不同的分布式應(yīng)用有不同的規(guī)則,但都遵循同樣的原則:按照最主要、最頻繁使用的訪問方式來分片。
三種數(shù)據(jù)分片方式
首先介紹三種分片方式:hash方式,一致性hash(consistent hash),按照數(shù)據(jù)范圍(range based)。對(duì)于任何方式,都需要思考以下幾個(gè)問題:
為了后面分析不同的數(shù)據(jù)分片方式,假設(shè)有三個(gè)物理節(jié)點(diǎn),編號(hào)為N0, N1, N2;有以下幾條記錄:
- R0: {id: 95, name: 'aa', tag:'older'}
- R1: {id: 302, name: 'bb',}
- R2: {id: 759, name: 'aa',}
- R3: {id: 607, name: 'dd', age: 18}
- R4: {id: 904, name: 'ff',}
- R5: {id: 246, name: 'gg',}
- R6: {id: 148, name: 'ff',}
- R7: {id: 533, name: 'kk',}
hash方式:
哈希表(散列表)是最為常見的數(shù)據(jù)結(jié)構(gòu),根據(jù)記錄(或者對(duì)象)的關(guān)鍵值將記錄映射到表中的一個(gè)槽(slot),便于快速訪問。絕大多數(shù)編程語言都有對(duì)hash表的支持,如python中的dict, C++中的map,Java中的Hashtable, Lua中的table等等。在哈希表中,最為簡單的散列函數(shù)是 mod N(N為表的大小)。即首先將關(guān)鍵值計(jì)算出hash值(這里是一個(gè)整型),通過對(duì)N取余,余數(shù)即在表中的位置。
數(shù)據(jù)分片的hash方式也是這個(gè)思想,即按照數(shù)據(jù)的某一特征(key)來計(jì)算哈希值,并將哈希值與系統(tǒng)中的節(jié)點(diǎn)建立映射關(guān)系,從而將哈希值不同的數(shù)據(jù)分布到不同的節(jié)點(diǎn)上。
我們選擇id作為數(shù)據(jù)分片的key,那么各個(gè)節(jié)點(diǎn)負(fù)責(zé)的數(shù)據(jù)如下:
由此可以看到,按照hash方式做數(shù)據(jù)分片,映射關(guān)系非常簡單;需要管理的元數(shù)據(jù)也非常之少,只需要記錄節(jié)點(diǎn)的數(shù)目以及hash方式就行了。
但hash方式的缺點(diǎn)也非常明顯:當(dāng)加入或者刪除一個(gè)節(jié)點(diǎn)的時(shí)候,大量的數(shù)據(jù)需要移動(dòng)。比如在這里增加一個(gè)節(jié)點(diǎn)N3,因此hash方式變?yōu)榱薽od 4,數(shù)據(jù)的遷移如下:
在這種方式下,是不滿足單調(diào)性(Monotonicity)的:如果已經(jīng)有一些內(nèi)容通過哈希分派到了相應(yīng)的緩沖中,又有新的緩沖加入到系統(tǒng)中。哈希的結(jié)果應(yīng)能夠保證原有已分配的內(nèi)容可以被映射到原有的或者新的緩沖中去,而不會(huì)被映射到舊的緩沖集合中的其他緩沖區(qū)。
在工程中,為了減少遷移的數(shù)據(jù)量,節(jié)點(diǎn)的數(shù)目可以成倍增長,這樣概率上來講至多有50%的數(shù)據(jù)遷移。
hash方式還有一個(gè)缺點(diǎn),即很難解決數(shù)據(jù)不均衡的問題。有兩種情況:原始數(shù)據(jù)的特征值分布不均勻,導(dǎo)致大量的數(shù)據(jù)集中到一個(gè)物理節(jié)點(diǎn)上;第二,對(duì)于可修改的記錄數(shù)據(jù),單條記錄的數(shù)據(jù)變大。在這兩種情況下,都會(huì)導(dǎo)致節(jié)點(diǎn)之間的負(fù)載不均衡,而且在hash方式下很難解決。
一致性hash
一致性hash是將數(shù)據(jù)按照特征值映射到一個(gè)首尾相接的hash環(huán)上,同時(shí)也將節(jié)點(diǎn)(按照IP地址或者機(jī)器名hash)映射到這個(gè)環(huán)上。對(duì)于數(shù)據(jù),從數(shù)據(jù)在環(huán)上的位置開始,順時(shí)針找到的***個(gè)節(jié)點(diǎn)即為數(shù)據(jù)的存儲(chǔ)節(jié)點(diǎn)。這里仍然以上述的數(shù)據(jù)為例,假設(shè)id的范圍為[0, 1000],N0, N1, N2在環(huán)上的位置分別是100, 400, 800,那么hash環(huán)示意圖與數(shù)據(jù)的分布如下:
可以看到相比于上述的hash方式,一致性hash方式需要維護(hù)的元數(shù)據(jù)額外包含了節(jié)點(diǎn)在環(huán)上的位置,但這個(gè)數(shù)據(jù)量也是非常小的。
一致性hash在增加或者刪除節(jié)點(diǎn)的時(shí)候,受到影響的數(shù)據(jù)是比較有限的,比如這里增加一個(gè)節(jié)點(diǎn)N3,其在環(huán)上的位置為600,因此,原來N2負(fù)責(zé)的范圍段(400, 800]現(xiàn)在由N3(400, 600] N2(600, 800]負(fù)責(zé),因此只需要將記錄R7(id:533) 從N2,遷移到N3:
不難發(fā)現(xiàn)一致性hash方式在增刪的時(shí)候只會(huì)影響到hash環(huán)上響應(yīng)的節(jié)點(diǎn),不會(huì)發(fā)生大規(guī)模的數(shù)據(jù)遷移。
但是,一致性hash方式在增加節(jié)點(diǎn)的時(shí)候,只能分?jǐn)傄粋€(gè)已存在節(jié)點(diǎn)的壓力;同樣,在其中一個(gè)節(jié)點(diǎn)掛掉的時(shí)候,該節(jié)點(diǎn)的壓力也會(huì)被全部轉(zhuǎn)移到下一個(gè)節(jié)點(diǎn)。我們希望的是“一方有難,八方支援”,因此需要在增刪節(jié)點(diǎn)的時(shí)候,已存在的所有節(jié)點(diǎn)都能參與響應(yīng),達(dá)到新的均衡狀態(tài)。
因此,在實(shí)際工程中,一般會(huì)引入虛擬節(jié)點(diǎn)(virtual node)的概念。即不是將物理節(jié)點(diǎn)映射在hash換上,而是將虛擬節(jié)點(diǎn)映射到hash環(huán)上。虛擬節(jié)點(diǎn)的數(shù)目遠(yuǎn)大于物理節(jié)點(diǎn),因此一個(gè)物理節(jié)點(diǎn)需要負(fù)責(zé)多個(gè)虛擬節(jié)點(diǎn)的真實(shí)存儲(chǔ)。操作數(shù)據(jù)的時(shí)候,先通過hash環(huán)找到對(duì)應(yīng)的虛擬節(jié)點(diǎn),再通過虛擬節(jié)點(diǎn)與物理節(jié)點(diǎn)的映射關(guān)系找到對(duì)應(yīng)的物理節(jié)點(diǎn)。
引入虛擬節(jié)點(diǎn)后的一致性hash需要維護(hù)的元數(shù)據(jù)也會(huì)增加:***,虛擬節(jié)點(diǎn)在hash環(huán)上的問題,且虛擬節(jié)點(diǎn)的數(shù)目又比較多;第二,虛擬節(jié)點(diǎn)與物理節(jié)點(diǎn)的映射關(guān)系。但帶來的好處是明顯的,當(dāng)一個(gè)物理節(jié)點(diǎn)失效是,hash環(huán)上多個(gè)虛擬節(jié)點(diǎn)失效,對(duì)應(yīng)的壓力也就會(huì)發(fā)散到多個(gè)其余的虛擬節(jié)點(diǎn),事實(shí)上也就是多個(gè)其余的物理節(jié)點(diǎn)。在增加物理節(jié)點(diǎn)的時(shí)候同樣如此。
工程中,Dynamo、Cassandra都使用了一致性hash算法,且在比較高的版本中都使用了虛擬節(jié)點(diǎn)的概念。在這些系統(tǒng)中,需要考慮綜合考慮數(shù)據(jù)分布方式和數(shù)據(jù)副本,當(dāng)引入數(shù)據(jù)副本之后,一致性hash方式也需要做相應(yīng)的調(diào)整, 可以參加cassandra的相關(guān)文檔。
range based
簡單來說,就是按照關(guān)鍵值劃分成不同的區(qū)間,每個(gè)物理節(jié)點(diǎn)負(fù)責(zé)一個(gè)或者多個(gè)區(qū)間。其實(shí)這種方式跟一致性hash有點(diǎn)像,可以理解為物理節(jié)點(diǎn)在hash環(huán)上的位置是動(dòng)態(tài)變化的。
還是以上面的數(shù)據(jù)舉例,三個(gè)節(jié)點(diǎn)的數(shù)據(jù)區(qū)間分別是N0(0, 200], N1(200, 500], N2(500, 1000]。那么數(shù)據(jù)分布如下:
注意,區(qū)間的大小不是固定的,每個(gè)數(shù)據(jù)區(qū)間的數(shù)據(jù)量與區(qū)間的大小也是沒有關(guān)系的。比如說,一部分?jǐn)?shù)據(jù)非常集中,那么區(qū)間大小應(yīng)該是比較小的,即以數(shù)據(jù)量的大小為片段標(biāo)準(zhǔn)。在實(shí)際工程中,一個(gè)節(jié)點(diǎn)往往負(fù)責(zé)多個(gè)區(qū)間,每個(gè)區(qū)間成為一個(gè)塊(chunk、block),每個(gè)塊有一個(gè)閾值,當(dāng)達(dá)到這個(gè)閾值之后就會(huì)分裂成兩個(gè)塊。這樣做的目的在于當(dāng)有節(jié)點(diǎn)加入的時(shí)候,可以快速達(dá)到均衡的目的。
不知道讀者有沒有發(fā)現(xiàn),如果一個(gè)節(jié)點(diǎn)負(fù)責(zé)的數(shù)據(jù)只有一個(gè)區(qū)間,range based與沒有虛擬節(jié)點(diǎn)概念的一致性hash很類似;如果一個(gè)節(jié)點(diǎn)負(fù)責(zé)多個(gè)區(qū)間,range based與有虛擬節(jié)點(diǎn)概念的一致性hash很類似。
range based的元數(shù)據(jù)管理相對(duì)復(fù)雜一些,需要記錄每個(gè)節(jié)點(diǎn)的數(shù)據(jù)區(qū)間范圍,特別單個(gè)節(jié)點(diǎn)對(duì)于多個(gè)區(qū)間的情況。而且,在數(shù)據(jù)可修改的情況下,如果塊進(jìn)行分裂,那么元數(shù)據(jù)中的區(qū)間信息也需要同步修改。
range based這種數(shù)據(jù)分片方式應(yīng)用非常廣泛,比如MongoDB, PostgreSQL, HDFS
小結(jié):
在這里對(duì)三種分片方式(應(yīng)該是四種,有沒有virtual node的一致性hash算兩種)進(jìn)行簡單總結(jié),主要是針對(duì)提出的幾個(gè)問題:
上面的數(shù)據(jù)動(dòng)態(tài)均衡,值得是上述問題的第4點(diǎn),即如果某節(jié)點(diǎn)數(shù)據(jù)量變大,能否以及如何將部分?jǐn)?shù)據(jù)遷移到其他負(fù)載較小的節(jié)點(diǎn)
分片特征值的選擇
上面的三種方式都提到了對(duì)數(shù)據(jù)的分片是基于關(guān)鍵值、特征值的。這個(gè)特征值在不同的系統(tǒng)中有不同的叫法,比如MongoDB中的sharding key, Oracle中的Partition Key,不管怎么樣,這個(gè)特征值的選擇都是非常非常重要的。
那么。怎么選擇這個(gè)特征值呢?《Distributed systems for fun and profit》給出了言簡意賅的標(biāo)準(zhǔn):
大概翻譯為:基于最常用的訪問模式。訪問時(shí)包括對(duì)數(shù)據(jù)的增刪改查的。比如上面的列子,我們選擇“id”作為分片的依據(jù),那么就是默認(rèn)對(duì)的數(shù)據(jù)增刪改查都是通過“id”字段來進(jìn)行的。
如果在應(yīng)用中,大量的數(shù)據(jù)操作都是通過這個(gè)特征值進(jìn)行,那么數(shù)據(jù)分片就能提供兩個(gè)額外的好處:
如果大量操作并沒有使用到特征值,那么就很麻煩了。比如在本文的例子中,如果用name去查詢,而元數(shù)據(jù)記錄的是如何根據(jù)按照id映射數(shù)據(jù)位置,那就尷尬了,需要到多有分片都去查一下,然后再做一個(gè)聚合!
另外一個(gè)問題,如果以單個(gè)字段為特征值(如id),那么不管按照什么分布方式,在多條數(shù)據(jù)擁有相同的特征值(如id)的情況下,這些數(shù)據(jù)一定都會(huì)分布到同一個(gè)節(jié)點(diǎn)上。在這種情況下有兩個(gè)問題,一是不能達(dá)到節(jié)點(diǎn)間數(shù)據(jù)的均衡,二是如果數(shù)據(jù)超過了單個(gè)節(jié)點(diǎn)的存儲(chǔ)能力怎么辦?關(guān)鍵在于,即使按照分布式系統(tǒng)解決問題的常規(guī)辦法 -- 增加節(jié)點(diǎn) --也是于事無補(bǔ)的。
在這個(gè)時(shí)候,單個(gè)字段做特征值就不行了,可能得再增加一個(gè)字段作為“聯(lián)合特征值”,類似數(shù)據(jù)庫中的聯(lián)合索引。比如,數(shù)據(jù)是用戶的操作日志,可以使用id和時(shí)間戳一起作為hash函數(shù)的輸入,然后算出特征值;但在這種情況下,如果還想以id為查詢關(guān)鍵字來查詢,那就得遍歷所有節(jié)點(diǎn)了。
所以說沒有***的設(shè)計(jì),只有***應(yīng)用需求的設(shè)計(jì)。
下面以MongoDB中的sharding key為例,解釋特征值選擇的重要性以及對(duì)數(shù)據(jù)操作的影響。如果有數(shù)據(jù)庫操作基礎(chǔ),即使沒有使用過MongoDB,閱讀下面的內(nèi)容應(yīng)該也沒有問題。
以MongoDB sharding key為例
關(guān)于MongoDB Sharded cluster,之前也寫過一篇文章《通過一步步創(chuàng)建sharded cluster來認(rèn)識(shí)mongodb》,做了簡單介紹。在我的工作場景中,除了聯(lián)合查詢(join)和事務(wù),MongoDB的使用和Mysql還是比較相似的,特別是基本的CRUD操作、數(shù)據(jù)庫索引。MongoDb中,每一個(gè)分片成為一個(gè)shard,分片的特征值成為sharding key,每個(gè)數(shù)據(jù)稱之為一個(gè)document。選擇適合的字段作為shardingkey非常重要,why?
前面也提到,如果使用非sharding key去訪問數(shù)據(jù),那么元數(shù)據(jù)服務(wù)器(或者元數(shù)據(jù)緩存服務(wù)器,后面會(huì)講解這一部分)是沒法知道對(duì)應(yīng)的數(shù)據(jù)在哪一個(gè)shard上,那么該訪問就得發(fā)送到所有的shard,得到所有shard的結(jié)果之后再做聚合,在mongoDB中,由mongos(緩存有元數(shù)據(jù)信息)做數(shù)據(jù)聚合。對(duì)于數(shù)據(jù)讀取(R: read or retrieve),通過同一個(gè)字段獲取到多個(gè)數(shù)據(jù),是沒有問題的,只是效率比較低而已。對(duì)于數(shù)據(jù)更新,如果只能更新一個(gè)數(shù)據(jù),那么在哪一個(gè)shard上更新呢,似乎都不對(duì),這個(gè)時(shí)候,MongoDB是拒絕的。對(duì)應(yīng)到MongoDB(MongoDD3.0)的命令包括但不限于:
findandmodify:這個(gè)命令只能更新一個(gè)document,因此查詢部分必須包含sharding key
update:這個(gè)命令有一個(gè)參數(shù)multi,默認(rèn)是false,即只能更新一個(gè)document,此時(shí)查詢部分必須包含sharding key
remove:有一個(gè)參數(shù)JustOne,如果為True,只能刪除一個(gè)document,也必須使用sharidng key
另外,熟悉sql的同學(xué)都知道,在數(shù)據(jù)中索引中有unique index(唯一索引),即保證這個(gè)字段的值在table中是唯一的。mongoDB中,也可以建立unique index,但是在sharded cluster環(huán)境下,只能對(duì)sharding key創(chuàng)建unique index,道理也很簡單,如果unique index不是sharidng key,那么插入的時(shí)候就得去所有shard上查看,而且還得加鎖。
接下來,討論分片到shard上的數(shù)據(jù)不均的問題,如果一段時(shí)間內(nèi)shardkey過于集中(比如按時(shí)間增長),那么數(shù)據(jù)只往一個(gè)shard寫入,導(dǎo)致無法平衡集群壓力。
MongoDB中提供了"range partition"和"hash partition",這個(gè)跟上面提到的分片方式 hash方式, ranged based不是一回事兒,而是指對(duì)sharding key處理。MongoDB一定是ranged base分片方式,docuemnt中如是說:
那么什么是"range partition"和"hash partition",官網(wǎng)的一張圖很好說明了二者的區(qū)別:
上圖左是range partition,右是hash partition。range partition就是使用字段本身作為分片的邊界,比如上圖的x;而hash partition會(huì)將字段重新hash到一個(gè)更大、更離散的值域區(qū)間。
hash partition的***好處在于保證數(shù)據(jù)在各個(gè)節(jié)點(diǎn)上均勻分布(這里的均勻指的是在寫入的時(shí)候就均勻,而不是通過MongoDB的balancing功能)。比如MongoDB中默認(rèn)的_id是objectid,objectid是一個(gè)12個(gè)字節(jié)的BSON類型,前4個(gè)字節(jié)是機(jī)器的時(shí)間戳,那么如果在同一時(shí)間大量創(chuàng)建以O(shè)bjectId為_id的數(shù)據(jù) 會(huì)分配到同一個(gè)shard上,此時(shí)若將_id設(shè)置為hash index 和 hash sharding key,就不會(huì)有這個(gè)問題。
當(dāng)然,hash partition相比range partition也有一個(gè)很大的缺點(diǎn),就是范圍查詢的時(shí)候效率低!因此到底選用hash partition還是range partition還得根據(jù)應(yīng)用場景來具體討論。
***得知道,sharding key一但選定,就無法修改(Immutable)。如果應(yīng)用必須要修改sharidng key,那么只能將數(shù)據(jù)導(dǎo)出,新建數(shù)據(jù)庫并創(chuàng)建新的sharding key,***導(dǎo)入數(shù)據(jù)。
元數(shù)據(jù)服務(wù)器
在上面討論的三種數(shù)據(jù)分片分式中,或多或少都會(huì)記錄一些元數(shù)據(jù):數(shù)據(jù)與節(jié)點(diǎn)的映射關(guān)系、節(jié)點(diǎn)狀態(tài)等等。我們稱記錄元數(shù)據(jù)的服務(wù)器為元數(shù)據(jù)服務(wù)器(metaserver),不同的系統(tǒng)叫法不一樣,比如master、configserver、namenode等。
元數(shù)據(jù)服務(wù)器就像人類的大腦,一只手不能用了還沒忍受,大腦不工作整個(gè)人就癱瘓了。因此,元數(shù)據(jù)服務(wù)器的高性能、高可用,要達(dá)到這兩個(gè)目標(biāo),元數(shù)據(jù)服務(wù)器就得高可擴(kuò)展 -- 以此應(yīng)對(duì)元數(shù)據(jù)的增長。
元數(shù)據(jù)的高可用要求元數(shù)據(jù)服務(wù)器不能成為故障單點(diǎn)(single point of failure),因此需要元數(shù)據(jù)服務(wù)器有多個(gè)備份,并且能夠在故障的時(shí)候迅速切換。
有多個(gè)備份,那么問題就來了,怎么保證多個(gè)備份的數(shù)據(jù)一致性?
多個(gè)副本的一致性、可用性是CAP理論討論的范疇,這里簡單介紹兩種方案。***種是主從同步,首先選出主服務(wù)器,只有主服務(wù)器提供對(duì)外服務(wù),主服務(wù)器將元數(shù)據(jù)的變革信息以日志的方式持久化到共享存儲(chǔ)(例如nfs),然后從服務(wù)器從共享存儲(chǔ)讀取日志并應(yīng)用,達(dá)到與主服務(wù)器一致的狀態(tài),如果主服務(wù)器被檢測(cè)到故障(比如通過心跳),那么會(huì)重新選出新的主服務(wù)器。第二種方式,通過分布式一致性協(xié)議來達(dá)到多個(gè)副本件的一致,比如大名鼎鼎的Paxos協(xié)議,以及工程中使用較多的Paxos的特化版本 -- Raft協(xié)議,協(xié)議可以實(shí)現(xiàn)所有備份均可以提供對(duì)外服務(wù),并且保證強(qiáng)一致性。
HDFS元數(shù)據(jù)
HDFS中,元數(shù)據(jù)服務(wù)器被稱之為namenode,在hdfs1.0之前,namenode還是單點(diǎn),一旦namenode掛掉,整個(gè)系統(tǒng)就無法工作。在hdfs2.0,解決了namenode的單點(diǎn)問題。
上圖中NN即NameNode, DN即DataNode(即實(shí)際存儲(chǔ)數(shù)據(jù)的節(jié)點(diǎn))。從圖中可以看到, 兩臺(tái) NameNode 形成互備,一臺(tái)處于 Active 狀態(tài),為主 NameNode,另外一臺(tái)處于 Standby 狀態(tài),為備 NameNode,只有主 NameNode 才能對(duì)外提供讀寫服務(wù)。
Active NN與standby NN之間的數(shù)據(jù)同步通過共享存儲(chǔ)實(shí)現(xiàn),共享存儲(chǔ)系統(tǒng)保證了Namenode的高可用。為了保證元數(shù)據(jù)的強(qiáng)一致性,在進(jìn)行準(zhǔn)備切換的時(shí)候,新的Active NN必須要在確認(rèn)元數(shù)據(jù)完全同步之后才能繼續(xù)對(duì)外提供服務(wù)。
另外,Namenode的狀態(tài)監(jiān)控以及準(zhǔn)備切換都是Zookeeper集群負(fù)責(zé),在網(wǎng)絡(luò)分割(network partition)的情況下,有可能zookeeper認(rèn)為原來的Active NN掛掉了,選舉出新的ActiveNN,但實(shí)際上原來的Active NN還在繼續(xù)提供服務(wù)。這就導(dǎo)致了“雙主“或者腦裂(brain-split)現(xiàn)象。為了解決這個(gè)問題,提出了fencing機(jī)制,也就是想辦法把舊的 Active NameNode 隔離起來,使它不能正常對(duì)外提供服務(wù)。具體參見這篇文章。
MongoDB元數(shù)據(jù)
MongoDB中,元數(shù)據(jù)服務(wù)器被稱為config server。在MongoDB3.2中,已經(jīng)不再建議使用三個(gè)鏡像(Mirrored)MongoDB實(shí)例作為config server,而是推薦使用復(fù)制集(replica set)作為config server,此舉的目的是增強(qiáng)config server的一致性,而且config sever中mongod的數(shù)目也能從3個(gè)達(dá)到replica set的上線(50個(gè)節(jié)點(diǎn)),從而提高了可靠性。
在MongoDB3.0及之前的版本中,元數(shù)據(jù)的讀寫按照下面的方式進(jìn)行:
MongoDB的官方文檔并沒有詳細(xì)解釋這一過程,不過在stackexchange上,有人指出這個(gè)過程是兩階段提交。
MongoDB3.2及之后的版本,使用了replica set config server,在《CAP理論與MongoDB一致性、可用性的一些思考》文章中,詳細(xì)介紹了replica set的write concern、read concern和read references,這三個(gè)選項(xiàng)會(huì)影響到復(fù)制集的一致性、可靠性與讀取性能。在config server中,使用了WriteConcern:Majority;ReadConcern:Majority;ReadReferences:nearest。
元數(shù)據(jù)的緩存:
即使元數(shù)據(jù)服務(wù)器可以由一組物理機(jī)器組成,也保證了副本集之間的一致性問題。但是如果每次對(duì)數(shù)據(jù)的請(qǐng)求都經(jīng)過元數(shù)據(jù)服務(wù)器的話,元數(shù)據(jù)服務(wù)器的壓力也是非常大的。很多應(yīng)用場景,元數(shù)據(jù)的變化并不是很頻繁,因此可以在訪問節(jié)點(diǎn)上做緩存,這樣應(yīng)用可以直接利用緩存數(shù)據(jù)進(jìn)行數(shù)據(jù)讀寫,減輕元數(shù)據(jù)服務(wù)器壓力。
在這個(gè)環(huán)境下,緩存的元數(shù)據(jù)必須與元數(shù)據(jù)服務(wù)器上的元數(shù)據(jù)一致,緩存的元數(shù)據(jù)必須是準(zhǔn)確的,未過時(shí)的。相反的例子是DNS之類的緩存,即使使用了過期的DNS緩存也不會(huì)有太大的問題。
怎么達(dá)到緩存的強(qiáng)一致性呢?比較容易想到的辦法是當(dāng)metadata變化的時(shí)候立即通知所有的緩存服務(wù)器(mongos),但問題是通信有延時(shí),不可靠。
解決不一致的問題,一個(gè)比較常見的思路是版本號(hào),比如網(wǎng)絡(luò)通信,通信協(xié)議可能會(huì)發(fā)生變化,通信雙方為了達(dá)成一致,那么可以使用版本號(hào)。在緩存一致性的問題上,也可以使用版本號(hào),基本思路是請(qǐng)求的時(shí)候帶上緩存的版本號(hào),路由到具體節(jié)點(diǎn)之后比較實(shí)際數(shù)據(jù)的版本號(hào),如果版本號(hào)不一致,那么表示緩存信息過舊,此時(shí)需要從元數(shù)據(jù)服務(wù)器重新拉取元數(shù)據(jù)并緩存。在MongoDB中,mongos緩存上就是使用的這種辦法。
另外一種解決辦法,就是大名鼎鼎的lease機(jī)制 -- “An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency”,lease機(jī)制在分布式系統(tǒng)中使用非常廣泛,不僅僅用于分布式緩存,在很多需要達(dá)成某種約定的地方都大顯身手,在《分布式系統(tǒng)原理介紹》中,對(duì)lease機(jī)制有較為詳細(xì)的描述,下面對(duì)lease機(jī)制進(jìn)行簡單介紹。
Lease機(jī)制:
既然,Lease機(jī)制提出的時(shí)候是為了解決分布式存儲(chǔ)系統(tǒng)中緩存一致性的問題,那么首先來看看Lease機(jī)制是怎么保證緩存的強(qiáng)一致性的。注意,為了方便后文描述,在本小節(jié)中,我們稱元數(shù)據(jù)服務(wù)器為服務(wù)器,緩存服務(wù)器為客戶端。
要點(diǎn):
在Lease論文的標(biāo)題中,提到了“Fault-Tolerant”,那么lease是怎么做到容錯(cuò)的呢。關(guān)鍵在于,只要服務(wù)器一旦發(fā)出數(shù)據(jù)和lease,不關(guān)心客戶端是否收到數(shù)據(jù),只要等待lease過期,就可以修改元數(shù)據(jù);另外,lease的有效期通過過期時(shí)間(一個(gè)時(shí)間戳)來標(biāo)識(shí),因此即使從服務(wù)器到客戶端的消息延時(shí)到達(dá)、或者重復(fù)發(fā)送都是沒有關(guān)系的。
不難發(fā)現(xiàn),容錯(cuò)的前提是服務(wù)器與客戶端的時(shí)間要一致。如果服務(wù)器的時(shí)間比客戶端的時(shí)間慢,那么客戶端收到lease之后很快就過期了,lease機(jī)制就發(fā)揮不了作用;如果服務(wù)器的時(shí)間比客戶端的時(shí)間快,那么就比較危險(xiǎn),因?yàn)榭蛻舳藭?huì)在服務(wù)器已經(jīng)開始更新元數(shù)據(jù)的時(shí)候繼續(xù)使用緩存,工程中,通常將服務(wù)器的過期時(shí)間設(shè)置得比客戶端的略大,來解決這個(gè)問題。為了保持時(shí)間的一致,***的辦法是使用NTP(Network Time Protocol)來保證時(shí)鐘同步。
Lease機(jī)制的本質(zhì)是頒發(fā)者授予的在某一有效期內(nèi)的承諾,承諾的范圍是非常廣泛的:比如上面提到的cache;比如做權(quán)限控制,例如當(dāng)需要做并發(fā)控制時(shí),同一時(shí)刻只給某一個(gè)節(jié)點(diǎn)頒發(fā)lease,只有持有l(wèi)ease的節(jié)點(diǎn)才可以修改數(shù)據(jù);比如身份驗(yàn)證,例如在primary-secondary架構(gòu)中,給節(jié)點(diǎn)頒發(fā)lease,只有持有l(wèi)ease的節(jié)點(diǎn)才具有primary身份;比如節(jié)點(diǎn)的狀態(tài)監(jiān)測(cè),例如在primary-secondary架構(gòu)中監(jiān)測(cè)primary是否正常,這個(gè)后文再詳細(xì)介紹。
工程中,lease機(jī)制也有大量的應(yīng)用:GFS中使用Lease確定Chuck的Primary副本, Lease由Master節(jié)點(diǎn)頒發(fā)給primary副本,持有Lease的副本成為primary副本。chubby通過paxos協(xié)議實(shí)現(xiàn)去中心化的選擇primary節(jié)點(diǎn),然后Secondary節(jié)點(diǎn)向primary節(jié)點(diǎn)發(fā)送lease,該lease的含義是:“承諾在lease時(shí)間內(nèi),不選舉其他節(jié)點(diǎn)成為primary節(jié)點(diǎn)”。chubby中,primary節(jié)點(diǎn)也會(huì)向每個(gè)client節(jié)點(diǎn)頒發(fā)lease。該lease的含義是用來判斷client的死活狀態(tài),一個(gè)client節(jié)點(diǎn)只有只有合法的lease,才能與chubby中的primary進(jìn)行讀寫操作。
總結(jié)
本文主要介紹分布式系統(tǒng)中的分片相關(guān)問題,包括三種分布方式:hash、一致性hash、range based,以及各自的優(yōu)缺點(diǎn)。分片都是按照一定的特征值來進(jìn)行,特征值應(yīng)該從應(yīng)用的使用場景來選取,并結(jié)合MongoDB展示了特征值(mongodb中的sharding key)對(duì)數(shù)據(jù)操作的影響。分片信息(即元數(shù)據(jù))需要專門的服務(wù)器存儲(chǔ),元數(shù)據(jù)服務(wù)器是分布式存儲(chǔ)系統(tǒng)的核心,因此需要提到其可用性和可靠性,為了減輕元數(shù)據(jù)服務(wù)器的壓力,分布式系統(tǒng)中,會(huì)在其他節(jié)點(diǎn)緩存元數(shù)據(jù),緩存的元數(shù)據(jù)由帶來了一致性的挑戰(zhàn),由此引入了Lease機(jī)制。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流