1. 程式人生 > 程式設計 >一文詳解分散式系統的分割槽

一文詳解分散式系統的分割槽

為什麼要分割槽?

資料的複製是冗餘的過程,冗餘會增加可用性,並且可以有效均衡讀取負載。而資料的分割槽是一個整體轉換為區域性的過程,這種拆解就像你擁有大量圖書,但你的書架放不下,所以需要再加幾個書架儲存是一個道理。將整體拆分,區域性儲存在多個較小空間內。這種思想對映到計算機上也是一樣的,當資料量過大,單個儲存節點不足與儲存這些資料(更大容量的磁碟沒有或者太貴)時,人們想要繼續儲存就需要將資料集拆解並規整。這就是資料分割槽的意義,它是用來提高資料系統的可擴充套件性而引入的技術方法。

img

如何分割槽?

分割槽的關鍵在於採用一種統一的規則,這種規則可以計算出將資料放在哪個節點,並且在讀取時也能計算出去哪個節點讀取資料。

要做到這幾點目前有三種分割槽方式:

  1. 按key的範圍進行分割槽 當要儲存資料時,我們取資料中的某一個欄位作為分割槽key,按這個欄位的範圍進行分割槽例如自增的id值,0-10000儲存在A節點上,10001-20000儲存在B節點上,那麼基於這樣的規則我們可以高效的存取分割槽中的資料,並且自然的支援按區間查詢(key的儲存是有序的),只要區間的範圍僅在一個分割槽時,那麼區間查詢就只會訪問一個分割槽,除非查詢範圍跨越多個分割槽。但是問題在於當資料的寫入在某段時間記憶體在熱點時,例如0-100000的key被大量的寫入,而10001-20000的key很少的時候,就會造成 資料傾斜 (資料分割槽大小不均衡)
  2. 按key的雜湊進行分割槽 對於資料傾斜,很自然的方式想到一個高效的雜湊函式來將資料存放在不同的分割槽,只要雜湊函式一致,相同的key一定會被對映到同一個分割槽。所以也是能夠解決分割槽的關鍵問題,但是由於雜湊的問題,自然的進行區間範圍查詢會非常的困難,有些資料庫會將區間查詢的請求傳送給所有分割槽,並行處理後,再全部聚合返回結果,但無疑會頻繁的產生大量的請求,雖然可以有效的解決資料傾斜問題,但是這種熱點資料是沒有辦法完全避免的,比如一個大V使用者總有非常多的粉絲,每天要產生非常多的資料,通過key雜湊這些資料還是會儲存在相同的分割槽內,造成資料傾斜的同時,還會導致熱點資料的頻繁訪問,讀與寫負載都會分佈不均勻。
  3. range+hash 模式 上述兩種分割槽的優缺點恰好是互補的,那麼可以考慮將二者結合例如用資料記錄的兩個關鍵欄位作為key,比如是id與時間戳,先用id 雜湊儲存在不同的分割槽上,然後在使用時間戳按範圍進行分割槽,這樣做在一定程度上彌補了二者的優缺點。 但依舊沒有完全解決熱點資料問題,這時熱點資料問題可以考慮其他方面來解決,比如建立熱點資料的快取架構。

分割槽方法看似完美的對資料的儲存進行了擴充套件,但也引入了另外的複雜度,那就是在查詢資料的時候,如果資料恰巧不在同一個分割槽內,就需要訪問多次不同的分割槽這樣就會加大請求的延遲,或者當我們需要對關係模型中的資料進行join操作時,由於資料在不同的分割槽中的不同表內,進行join的難度就會非常大,增加了多表查詢的複雜程度,一種折中的解決方案是,從業務上來看,將會被join或者同時讀取的資料儘量放在同一個分割槽上,來減少跨分割槽呼叫的效能損耗,這就相當於降低磁碟定址尋道的次數是一樣的道理,都是在降低最耗時操作的發生次數。

二級索引的分割槽如何設計?

上述的三種分割槽方案,僅僅是對主鍵的分割槽,也就是一條記錄的唯一標識進行分割槽,但從資料庫功能的角度來看,我們還需要可以根據一條記錄的任意欄位建立索引,以便靈活高效的查詢.這樣的索引,就稱之為二級索引。那麼二級索引在分割槽資料庫的設計上應該如何實現呢?通常有兩種設計,本地索引與全域性索引。

本地索引

當寫入與讀取二級索引時都在本分割槽上進行時,我們就說這樣的二級索引為本地索引,也就是說每個分割槽上的二級索引檔案僅儲存本分割槽上的索引資料。這樣做的好處是,在寫入資料時更新一條記錄的二級索引會很方便,因為關於本記錄的所有二級索引都在這個節點上.但是以二級索引讀取某條記錄時,我們沒辦法知道記錄在哪個分割槽,因此我們需要進行並行查詢然後將查詢結果進行合併,這樣做無疑放大了讀取的延遲。

全域性索引

與之相對的是全域性索引,即對於某個二級索引,其全部的欄位都在同一個分割槽之中(不同的全域性索引在不同的分割槽上),當我們查詢某個二級索引時,我們可以只去唯一的一個節點上進行讀取資料即可,不需要並行查詢,這樣讀取的效率會很高,但是在寫入資料的時候,由於一條記錄涉及的二級索引可能在多個分割槽上,所以需要操作多個分割槽這就涉及到分散式的事務一致性等問題,複雜度大大增加並影響效能。全域性索引在讀取資料時,如何找到索引所在分割槽呢?答案是,對於全域性的二級索引我們可以對其採用相同的分割槽策略,範圍分割槽,雜湊分割槽或者雜湊範圍分割槽等. 不同的分割槽策略同樣會影響其對區間查詢的效率。

分割槽再平衡

多個節點上擁有多個分割槽,當隨著資料負載的增加,每個分割槽的大小就會不斷的增加,這樣就造成了隱患,一旦一個節點失效,其上分割槽都將失效,佔比很大的一部分資料都將失效,再比如現在向叢集中加入或剔除一個新的節點,那麼資料需要可以被均勻的轉移到新的節點上(新節點不轉移資料,而是接受新的寫請求是否可行?這樣做會使在一段時間內,寫入請求不能均衡的請求不同的節點,大量的請求新節點會使其負載不平衡),上述問題都概括起來就是引入分割槽再平衡特性的原因,即為了可用性與擴充套件性,分割槽再平衡都是必不可少的特性。

固定數量的分割槽

分割槽數與節點數應當不同,這樣做是為了方便其擴充套件。理由是:假設分割槽數與節點數相同,那麼通過對節點數取模來決定資料被分配到哪個分割槽上,這種取模會造成隱患.當我們新增或者刪除一個節點時,取模的數發生變化,之前的資料不能被路由到正確的分割槽,所以必須進行再平衡對,所有資料重新分割槽(類似,hashmap 的再雜湊),這會導致所有資料都處於遷移的狀態,整個叢集將不可用。

因此,我們必須將節點數與分割槽數進行解耦合,在一個節點上分配固定數量的分割槽數,例如在叢集初始化時指定一個有1024個分割槽,現在有三個節點,那麼每個節點應該擁有341個分割槽,最後一個節點可能擁有342個分割槽,這時新增一個節點,叢集擁有4個節點後,我們需要對其進行分割槽再平衡,僅需要將原來的三個節點上的分割槽各取一半即可,這樣就僅僅有一半的資料在遷移的過程中(比例經過複雜的演演算法可以動態調整),就可以降低分割槽再平衡過程中的複雜度了。節點刪除也是同樣的道理,將該節點上的分割槽平均的分散在其他節點上即可,固定數量的分割槽方案解決了節點數與分割槽數的耦合,我們對分割槽數進行取模即可很快的確定資料所在分割槽,並且在遷移前後相對分割槽保持不變,redis的叢集模式就是採用這種方式進行的分割槽再平衡。

動態數量分割槽

固定數量分割槽不能很好的應對熱點問題,當一個分割槽儲存的資料量遠多於其他節點時,這是不合理的。由於節點數量固定,這些資料無法遷移,因此引入類似B+樹節點分裂與合併的概念,我們對分割槽也可以根據其資料量的多少進行分裂與合併,當某個分割槽負載高於一個指定的閾值時,我們就會對其進行分裂,變成兩個分割槽,這樣分割槽的數量就發生了變化,此種方案就不能使用分割槽數取模的方式進行資料的散列了,僅能根據關鍵字區間或者雜湊進行分割槽。但這是值得的,他可以有效的平衡各個分割槽的資料負載。

按節點比例進行分割槽

動態分割槽同樣擁有一個弊端,那就是其分割槽的數量與資料量成正比,資料量的增加會不斷的增加新的分割槽,分割槽數量的變多將會成為新的效能瓶頸。

因此,引入一種新的方案,結合上述兩種方案,當節點數固定不變時,分割槽數也是固定不變的,每個節點上的分割槽數永遠是固定數量的,這樣當節點數不變時,隨著資料負載的增加,其分割槽的大小也會不斷變大,當有新的節點加入或者剔除時,會隨機(可以有某些複雜的策略)選擇一些節點上的分割槽進行分裂,一分為二的分割槽,一半被移動到新的節點,另一半留在原地,這樣做的好處是分割槽的數量被節點數所限定了,不會無限的增長成為瓶頸。

手動與自動的再平衡

是否應該有資料系統自動的完成分割槽再平衡?這樣做無疑是方便的,也有很多資料庫採用,但其複雜度確是非常之高的,例如,當發生節點分割槽平衡時,被系統檢測到節點不可用時,那麼就會造成級聯崩潰的情況,觸發剔除節點邏輯,又會觸發新的分割槽平衡致使整個叢集崩潰。

基於簡單性的原則,有管理員手動的分割槽再平衡是一種折中的選擇。

請求路由

引入複雜的分割槽方案後,客戶端如何知道請求的資料在哪個分割槽上?一般有三種方式:

  1. 隨機的請求一個節點,該分割槽會判斷資料是否在自身上,是則直接返回結果,不是則轉發請求到擁有資料的節點,並返回結果。
  2. 所有的請求都訪問一個路由層,路由層擁有足夠的元資料進行決策,將請求轉發到合適的節點上。
  3. 客戶端本身可以感知到分割槽節點的分配關係,直接請求相應分割槽。

無論哪種方式,只不過是路由決策的邏輯交由誰來完成的問題。

對於客戶端的請求路由,需要讓客戶端感知到分割槽與節點的對映關係的變化,通常基於分散式的一致性共識元件完成,例如zk,etcd等。當分割槽節點啟動時向zk註冊自己的元資料,然後zk會將資訊傳播到訂閱了此變化的客戶端上,客戶端更新分割槽與節點的對映關係,當有請求時直接訪問對應的分割槽即可。其優點在於這樣網路呼叫次數最少最高效,但依賴第三方一致性共識元件。

另一種不同的做法是,讓客戶端請求任意節點,分割槽節點根據自身持有的元資料資訊判斷請求的資料是否在自己的分割槽,在則直接處理並返回,不在則將請求轉發給擁有分割槽的請求進行處理並返回給客戶端。這樣做的優點在於不依賴共識元件,但最壞情況下網路的呼叫次數翻倍,影響效能。

並行查詢處理

當需要對多個欄位進行聯合查詢時,分割槽資料庫應該怎麼做,我們一直探討如何通過單獨的key進行查詢,但是對於大量的針對資料倉庫的查詢,更多的是複雜的join等多表操作.分割槽資料庫能表現的很好嗎?這就涉及到分散式資料庫的分割槽並行查詢的問題,理論上當請求到達路由層後,由路由層中並行查詢優化器等元件制定並行查詢計劃,並委派給對應的分割槽,並把結果做最後的合併。這個過程中的細節非常之多,我將在之後的文章中詳細介紹。

至此,我們完成了分散式資料分割槽的常見解決方案的介紹,希望讀者與我在未來中實踐這些知識,並且上述內容均來自《資料密集型系統設計》一書。