【分佈式—要點】數據分區

採用數據分區的主要目的是提升可擴展性。不一樣的分區能夠放在一個無共享集羣的不一樣節點上。這樣一個大數據集能夠分散在更多的磁盤上,查詢負載也隨之分佈到更多的處理器上。算法

對單個分區進行查詢時,每一個節點對本身所在分區能夠獨立執行查詢操做,所以添加更多的節點能夠提升查詢吞吐量。超大而複雜的查詢儘管比較困難,但也可能作到跨節點的並行處理。數據庫

分區一般與複製結合使用,即每一個分區在多個節點都存有副本。這意味着某條記錄屬於特定的分區,而一樣的內容會保存在不一樣的節點上以提升系統的容錯性。網絡

一個節點上可能存儲了多個分區。每一個分區都有本身的主副本,而從副本則分配在其餘一些節點。一個節點可能便是某些分區的主副本,同時又是其餘分區的從副本。負載均衡

鍵-值數據的分區

如今假設數據是簡單的鍵-值數據模型,這意味着老是能夠經過關鍵字來訪問記錄。異步

基於關鍵字區間分區

一種分區方式是爲每一個分區分配一段連續的關鍵字或者關鍵字區間範圍(以最小值和最大值來指示)。若是知道關鍵字區間的上下限,就能夠輕鬆肯定哪一個分區包含這些關鍵字。若是還知道哪一個分區分配在哪一個節點,就能夠直接向該節點發出請求。分佈式

關鍵字的區間段不必定非要均勻分佈,這主要是由於數據自己可能就不均勻。分區邊界能夠由管理員手動肯定,或者由數據庫自動選擇。每一個分區內能夠按照關鍵字排序保存,這樣能夠輕鬆支持區間查詢,即將關鍵字做爲一個拼接起來的索引項從而一次查詢獲得多個相關記錄。例如,對於一個保存網絡傳感器數據的應用系統,選擇測量的時間戳(年-月-日-時-分-秒)做爲關鍵字,此時區間查詢會很是有用,它能夠快速得到某個月分內的全部數據。函數

然而,基於關鍵字的區間分區的缺點是某些訪問模式會致使熱點。若是關鍵字是時間戳,則分區對應於一個時間範圍,例如天天一個分區。然而,當測量數據從傳感器寫入數據庫時,全部的寫入操做都集中在同一個分區(即當天的分區),這會致使該分區在寫入時負載太高,而其餘分區始終處於空閒狀態。性能

爲了不上述問題,須要使用時間戳之外的其餘內容做爲關鍵字的第一項。例如,能夠在時間戳前面加上傳感器名稱做爲前綴,這樣首先由傳感器名稱,而後按時間進行分區。假設同時有許多傳感器處於活動狀態,則寫入負載最終會比較均勻地分佈在多個節點上。接下來,當須要獲取一個時間範圍內、多個傳感器的數據時,能夠根據傳感器名稱,各自執行區間查詢。大數據

基於關鍵字哈希值分區

對於上述數據傾斜與熱點問題,許多分佈式系統採用了基於關鍵字哈希函數的方式來分區。網站

一個好的哈希函數能夠處理數據傾斜並使其均勻分佈。一旦找到合適的關鍵字哈希函數,就能夠爲每一個分區分配一個哈希範圍(而不是直接做用於關鍵字範圍),關鍵字根據其哈希值的範圍劃分到不一樣的分區中。

這種方法能夠很好地將關鍵字均勻地分配到多個分區中。分區邊界能夠是均勻間隔,也能夠是僞隨機選擇(在這種狀況下,該技術有時被稱爲一致性哈希)。

然而,經過關鍵字哈希進行分區,咱們喪失了良好的區間查詢特性。即便關鍵字相鄰,但通過哈希以後會分散在不一樣的分區中,區間查詢就失去了原有的有序相鄰的特性

負載傾斜與熱點

如前所述,基於哈希的分區方法能夠減輕熱點,但沒法作到徹底避免。一個極端狀況是,全部的讀/寫操做都是針對同一個關鍵字,則最終全部請求都將被路由到同一個分區。

這種負載或許並不廣泛,但也並不是不可能:例如,社交媒體網站上,一些名人用戶有數百萬的粉絲,當其發佈一些熱點事件時可能會引起一場訪問風暴,出現大量的對相同關鍵字的寫操做(其中關鍵字多是名人的用戶ID,或者人們正在評論的事件ID)。此時,哈希起不到任何幫助做用,由於兩個相同ID的哈希值仍然相同。

大多數的系統今天仍然沒法自動消除這種高度傾斜的負載,而只能經過應用層來減輕傾斜程度。例如,若是某個關鍵字被確認爲熱點,一個簡單的技術就是在關鍵字的開頭或結尾處添加一個隨機數。只需一個兩位數的十進制隨機數就能夠將關鍵字的寫操做分佈到100個不一樣的關鍵字上,從而分配到不一樣的分區上。

可是,隨之而來的問題是,以後的任何讀取都須要些額外的工做,必須從全部100個關鍵字中讀取數據而後進行合併。所以一般只對少許的熱點關鍵字附加隨機數纔有意義;而對於寫入吞吐量低的絕大多數關鍵字,這些都意味着沒必要要的開銷。此外,還須要額外的元數據來標記哪些關鍵字進行了特殊處理。

分區與二級索引

在分區方案設計中,若是涉及二級索引,狀況會變得複雜。二級索引一般不能惟一標識一條記錄,而是用來加速特定值的查詢。二級索引是關係數據庫的必備特性,在文檔數據庫中應用也很是廣泛。

二級索引帶來的主要挑戰是它們不能規整的地映射到分區中。有兩種主要的方法來支持對二級索引進行分區:基於文檔的分區和基於詞條的分區。

基於文檔的二級索引

在這種索引方法中,每一個分區徹底獨立,各自維護本身的二級索引,且只負責本身分區內的文檔而不關心其餘分區中數據。每當須要寫數據庫時,包括添加,刪除或更新文檔等,只須要處理包含目標文檔ID的那一個分區。所以文檔分區索引也被稱爲本地索引,而不是全局索引。

但讀取時須要注意:除非對文檔ID作了特別的處理,不然不太可能全部特定查詢條件的數據都放在一個分區中。所以須要將查詢發送到全部的分區,而後合併全部返回的結果。

這種查詢分區數據庫的方法有時也稱爲分散/彙集,顯然這種二級索引的查詢代價高昂。即便採用了並行查詢,也容易致使讀延遲顯著放大。

基於詞條的二級索引

另外一種方法,咱們能夠對全部的數據構建全局索引,而不是每一個分區維護本身的本地索引。並且,爲避免成爲瓶頸,不能將全局索引存儲在一個節點上,不然就破壞了設計分區均衡的目標。因此,全局索引也必須進行分區,且能夠與數據關鍵字採用不一樣的分區策略。

和前面討論的方法同樣,能夠直接經過關鍵詞來全局劃分索引,或者對其取哈希值。直接分區的好處是能夠支持高效的區間查詢;而採用哈希的方式則能夠更均勻的劃分分區。

這種全局的詞條分區相比於文檔分區索引的主要優勢是,它的讀取更爲高效,即它不須要採用scatter/gather對全部的分區都執行一遍查詢,相反,客戶端只須要向包含詞條的那一個分區發出讀請求。然而全局索引的不利之處在於,寫入速度較慢且很是複雜,主要由於單個文檔的更新時,裏面可能會涉及多個二級索引,而二級索引的分區又可能徹底不一樣甚至在不一樣的節點上,由此勢必引入顯著的寫放大

實踐中,對全局二級索引的更新每每都是異步的。

分區再平衡

遷移負載的過程稱爲再平衡(或者動態平衡)。不管對於哪一種分區方案,分區再平衡一般至少要知足:

  • 平衡以後,負載、數據存儲、讀寫請求等應該在集羣範圍更均勻地分佈。
  • 再平衡執行過程當中,數據庫應該能夠繼續正常提供讀寫服務。
  • 避免沒必要要的負載遷移,以加快動態再平衡,並儘可能減小網絡和磁盤I/O影響。

動態再平衡的策略

爲何不用取模?

對節點數取模方法的問題是,若是節點數N發生了變化,會致使不少關鍵字須要從現有的節點遷移到另外一個節點。這種頻繁的遷移操做大大增長了再平衡的成本。

固定數量的分區

有一個至關簡單的解決方案:首先,建立遠超實際節點數的分區數,而後爲每一個節點分配多個分區。例如,對於一個10節點的集羣,數據庫能夠從一開始就邏輯劃分爲1000個分區,這樣大約每一個節點承擔100個分區。

接下來,若是集羣中添加了一個新節點,該新節點能夠從每一個現有的節點上勻走幾個分區,直到分區再次達到全局平衡。若是從集羣中刪除節點,則採起相反的均衡措施。

選中的整個分區會在節點之間遷移,但分區的總數量仍維持不變,也不會改變關鍵字到分區的映射關係。這裏惟一要調整的是分區與節點的對應關係。考慮到節點間經過網絡傳輸數據老是須要些時間,這樣調整能夠逐步完成,在此期間,舊的分區仍然能夠接收讀寫請求。

原則上,也能夠將集羣中的不一樣的硬件配置因素考慮進來,即性能更強大的節點將分配更多的分區,從而分擔更多的負載。

若是數據集的總規模高度不肯定或可變,此時如何選擇合適的分區數就有些困難。每一個分區包含的數據量的上限是固定的,實際大小應該與集羣中的數據總量成正比。若是分區裏的數據量很是大,則每次再平衡和節點故障恢復的代價就很大;可是若是一個分區過小,就會產生太多的開銷。分區大小應該「恰到好處」,不要太大,也不能太小,若是分區數量固定了但總數據量卻高度不肯定,就難以達到一個最佳取捨點。

動態分區

對於採用關鍵字區間分區的數據庫,若是邊界設置有問題,最終可能會出現全部數據都擠在一個分區而其餘分區基本爲空,那麼設定固定邊界、固定數量的分區將很是不便,而手動去從新配置分區邊界又很是繁瑣。

所以,一些數據庫如HBase等採用了動態建立分區。當分區的數據增加超過一個可配的參數閾值,它就拆分爲兩個分區,每一個承擔一半的數據量。若是大量數據被刪除,而且分區縮小到某個閾值如下,則將其與相鄰分區進行合併。該過程相似於B樹的分裂操做。

每一個分區老是分配給一個節點,而每一個節點能夠承載多個分區,這點與固定數量的分區同樣。當一個大的分區發生分裂以後,能夠將其中的一半轉移到其餘某節點以平衡負載。

動態分區的一個優勢是分區數量能夠自動適配數據總量。若是隻有少許的數據,少許的分區就足夠了,這樣系統開銷很小;若是有大量的數據,每一個分區的大小則被限制在一個可配的最大值。

按節點比例分區

採用動態分區策略,拆分和合並操做使每一個分區的大小維持在設定的最小值和最大值之間,所以分區的數量與數據集的大小成正比關係。另外一方面,對於固定數量的分區方式,其每一個分區的大小也與數據集的大小成正比。兩種狀況,分區的數量都與節點數無關。

Cassandra和Ketama則採用了第三種方式,使分區數與集羣節點數成正比關係。換句話說,每一個節點具備固定數量的分區。此時,當節點數不變時,每一個分區的大小與數據集大小保持正比的增加關係;當節點數增長時,分區則會調整變得更小。較大的數據量一般須要大量的節點來存儲,所以這種方法也使每一個分區大小保持穩定。

當一個新節點加入集羣時,它隨機選擇固定數量的現有分區進行分裂,而後拿走這些分區的一半數據量,將另外一半數據留在原節點。隨機選擇可能會帶來不太公平的分區分裂,可是當平均分區數量較大時,新節點最終會從現有節點中拿走至關數量的負載。

隨機選擇分區邊界的前提要求採用基於哈希分區(能夠從哈希函數產生的數字範圍裏設置邊界)。這種方法也最符合本章開頭所定義一致性哈希。一些新設計的哈希函數也能夠以較低的元數據開銷達到相似的效果。

請求路由

歸納來說,這個問題有如下幾種不一樣的處理策略(分別如圖所示的三種狀況):

  1. 容許客戶端鏈接任意的節點(例如,採用循環式的負載均衡器)。若是某節點剛好擁有所請求的分區,則直接處理該請求;不然,將請求轉發到下一個合適的節點,接收答覆,並將答覆返回給客戶端。
  2. 將全部客戶端的請求都發送到一個路由層,由後者負責將請求轉發到對應的分區節點上。路由層自己不處理任何請求,它僅充一個分區感知的負載均衡器。
  3. 客戶端感知分區和節點分配關係。此時,客戶端能夠直接鏈接到目標節點,而不須要任何中介。

image.png

這實際上是一個頗有挑戰性的問題,全部參與者都要達成共識這一點很重要。不然請求可能被髮送到錯誤的節點,而沒有獲得正確處理。分佈式系統中有專門的共識協議算法,但一般難以正確實現。

許多分佈式數據系統依靠獨立的協調服務(如ZooKeeper)跟蹤集羣範圍內的元數據。每一個節點都向ZooKeeper中註冊本身,ZooKeeper維護了分區到節點的最終映射關係。其餘參與者(如路由層或分區感知的客戶端)能夠向ZooKeeper訂閱此信息。一旦分區發生了改變,或者添加、刪除節點,ZooKeeper就會主動通知路由層,這樣使路由信息保持最新狀態。

Cassandra和Riak則採用了不一樣的方法,它們在節點之間使用gossip協議來同步羣集狀態的變化。請求能夠發送到任何節點,由該節點負責將其轉發到目標分區節點。這種方式增長了數據庫節點的複雜性,可是避免了對ZooKeeper之類的外部協調服務的依賴。

當使用路由層或隨機選擇節點發送請求時,客戶端仍然須要知道目標節點的IP地址。IP地址的變化每每沒有分區-節點變化那麼頻繁,採用DNS一般就足夠了。

相關文章
相關標籤/搜索