資深架構師技術分享:一文詳解分布式系統的分區

2019-08-26     IT技術分享

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

如何分區?

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

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

  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等多表操作.分區資料庫能表現的很好嗎?這就涉及到分布式資料庫的分區並行查詢的問題,理論上當請求到達路由層後,由路由層中並行查詢優化器等組件制定並行查詢計劃,並委派給對應的分區,並把結果做最後的合併。這個過程中的細節非常之多,我將在之後的文章中詳細介紹。

文章來源: https://twgreatdaily.com/zh-hk/5eMC12wBJleJMoPMrPAD.html