你知道雜湊演算法,但你知道一致性雜湊嗎?

JAVA旭陽發表於2023-01-22

前言

假如讓你為淘寶這種資料量非常大的公司的設計一個可擴充套件的資料儲存系統,你該如何儲存和管理資料呢?總不能放在單個伺服器上吧,肯定放不下,必然需要水平擴充套件。那麼這樣就帶來一個問題,這個資料要儲存在哪個伺服器上呢?這就引入了本文的主題一致性雜湊演算法,可能你沒聽過,那麼本文就透過一個簡單的例子帶你一步一步認識它。

歡迎關注個人公眾號【JAVA旭陽】

資料線性分佈

假如我們現在有一組伺服器,我們想提出一個在這組伺服器之間進行資料儲存和查詢的策略。讓我們從一個最最簡單的方案開始。假設我們一個接一個地填滿伺服器,即僅噹噹前伺服器已滿時,我們才開始將資料寫入下一個伺服器。

在下圖中,我們有一個簡單的伺服器,一次只能儲存 4 條記錄。當伺服器變滿時,我們新增一個新伺服器並向其新增新資料。

好吧,這種方法在任何伺服器上寫入資料時都非常有效。當您被要求讀取特定資料時會發生什麼?您需要識別儲存給定資料的伺服器,然後獲取它。你如何識別伺服器?您會遍歷所有伺服器併線性掃描每個伺服器嗎?這會影響讀取效能。

例如:在上面的例子中,如果你被要求查詢“New York”,因為鍵和伺服器之間沒有直接對映,你將不得不線性掃描所有伺服器並搜尋這個鍵。

這樣的效率是不是很糟糕,那麼有沒有更好的解決方案呢。

資料雜湊分佈

雜湊演算法想必大家都知道,Java中的HashMap就是採用的雜湊演算法。那麼根據這個思路,我們提出了一個新的解決方案,資料根據雜湊進行儲存管理。

我們看到如果我們有 N 個伺服器,則獲取記錄的時間複雜度將為 O(N)。我們希望在 O(1) 中高效地讀寫資料。我們首先想到的是提供 O(1) 查詢和寫入的 HashMap 資料結構。

讓我們看看 Hashing 是否可以解決我們的問題。假設我們有 N 個儲存資料的伺服器和一個具有分發資料策略的應用程式。該方法類似於 HashMap 使用的方法。首先,對鍵進行雜湊處理,然後確定資料將存放的儲存桶。應用程式將首先對金鑰進行雜湊處理,然後透過計算hash(data) % N來確定哪個伺服器。

上述演算法將給出寫入資料的伺服器編號。此外,在檢索資料時,它將使用相同的邏輯,獲取伺服器編號並獲取資料。讀取和寫入都在 O(1) 中完成。

讓我們來看一個例子。假設我們有三個名為 S0、S1 和 S2 的伺服器。我們的鑰匙是世界城市名稱。使用雜湊,我們計算需要將金鑰分配到的儲存桶或伺服器。

金鑰的雜湊和計算桶

金鑰分配

但這在分散式系統中總是有效的嗎?我們會遇到以下問題:

  • 如果我們新增更多伺服器,則hash(data) % N會有所不同。這意味著我們將不得不在新增新伺服器時重新分配所有資料。
  • 如果刪除其中一臺伺服器,我們將遇到同樣的問題。由於此處伺服器的數量 N 是可變的,因此所有金鑰都會受到影響。

以下是新增新伺服器時發生的情況的說明。隨著伺服器數量從 3 臺增長到 4 臺,桶的計算邏輯將變為Hash % 4

新舊金鑰分配

在新增新伺服器時,我們觀察到三個鍵中的兩個受到了影響。如果我們新增一個新伺服器,鍵Madrid的桶將是 0 (S0) 而不是 1(S1)。我們必須將此金鑰移動到伺服器 S1 以確保我們的應用程式找到它。因此,我們必須重新雜湊所有現有金鑰並將它們分配給不同的伺服器。在最壞的情況下,這可能會影響系統中的所有金鑰。

看來透過雜湊演算法將資料分發到不同伺服器中還是不大行,那還有什麼更好的辦法呢?這邊就要隆重介紹一致性雜湊

什麼是一致性雜湊?

當我們想要動態新增或刪除伺服器時,一致性雜湊解決了我們的問題。在簡單雜湊的情況下,新增或刪除伺服器將影響儲存在系統中的所有 M 金鑰。然而,一致性雜湊確保只有 M/N 鍵受到影響,其中 N 是伺服器的數量。

一致性雜湊使金鑰的分佈與系統使用的伺服器數量無關。因此,我們可以在不影響整個系統的情況下擴大或縮小規模。

從根本上說,一致性雜湊使用雜湊環。該演算法將每個伺服器對映到圓上的一個點。它首先使用伺服器的 IP 地址,計算其雜湊值併為其分配圓上的一個點(角度)。以下是如何為 3 個伺服器 S1、S2、S3 計算角度的簡單說明:

將伺服器分配給雜湊環上的點

此外,每個金鑰都使用相同的雜湊演算法進行雜湊處理並在伺服器上分配一個點。對於每個雜湊鍵,我們按順時針方向移動並找到最近的伺服器並分配給它。

將金鑰分配給雜湊環上的點

我們得到以上金鑰集的以下分配:

將金鑰分配給伺服器

以下是上述金鑰分配到雜湊環上不同伺服器的圖形表示:

在雜湊環上將金鑰分配給伺服器

從上圖可以看出,我們從每個鍵順時針方向移動,找到它的伺服器。

擴充套件和新增新伺服器

如前一節所述,我們首先計算伺服器IP地址的雜湊值並找到它在圓上的位置。例如:如果我們新增一個伺服器S4,發現它位於圓上的S2和S0之間。此外,我們重新分配 S0 的鍵,其角度小於 S3,或者換句話說,在圓上出現在 S3 之前。

下圖說明了此過程,其中新增了一個新伺服器 S3,它位於 S2 和 S0 之間。最初,鍵Mumbai被分配給伺服器 S0。在新增 S3 時,我們看到從鍵Mumbai順時針方向遇到的第一個伺服器是 S3,因此我們將此鍵分配給 S3。

新增新伺服器 S3

從上面可以看出,新增新伺服器不會影響所有金鑰。只有雜湊環上兩個伺服器之間出現的金鑰需要重新分配。

刪除現有伺服器

當刪除現有伺服器時,只需要重新分配屬於該伺服器的金鑰。對於屬於被移除伺服器的key,按順時針方向找到雜湊環上的下一個伺服器。此外,然後將金鑰分配給新伺服器。

下圖說明了刪除現有伺服器的過程:

刪除伺服器 S1

在上圖中,刪除了伺服器 S1。鍵New York被分配給伺服器 S1。刪除 S1 後,我們從鍵New York中找到第一個伺服器並找到伺服器 S2。因此,鍵New York被重新分配給伺服器 S2。

與普通雜湊不同,刪除伺服器不需要重新雜湊所有金鑰。只需重新分配已移除伺服器的金鑰。

虛擬節點

我們看到,當一個節點被移除時,分配給這個節點的所有鍵都會被移動到雜湊環中的下一個節點。通常,在刪除一個節點時,資料分佈會變得不均勻,並且其中一個節點的負載會增加。

在上述情況下,如果我們從系統中刪除 S0,則鍵London將對映到伺服器 S2。最終,我們會發現 S2 處理三個金鑰,而 S1 只管理一個金鑰。因此,資料分佈不均勻。

刪除 S0 會給 S2 帶來更多負載

在理想情況下,當有M個金鑰和N個伺服器時,每個伺服器必須有接近 M/N 個金鑰。因此,新增或刪除節點會影響系統中的最大 M/N 鍵。為了確保接近理想的分佈,我們在系統中引入了虛擬節點。每個物理節點在雜湊環上都有多個虛擬節點。

我們使用多個雜湊函式來查詢虛擬節點在雜湊環上的位置。每個伺服器都用 Sij 表示,其中 i 表示實際伺服器編號,j 代表其虛擬副本。例如:對於第一臺伺服器,虛擬副本將是 S00、S01、S02、S03 等。我們使用不同的雜湊函式來計算每個虛擬副本的雜湊值。

在上面的示例中,我們得到以下虛擬伺服器分配:

雜湊環上的虛擬伺服器

從上圖可以看出,伺服器S1的虛擬副本是S10、S12和S13。這同樣適用於伺服器 S0。這導致節點之間的資料分佈接近均勻。

對於給定的金鑰,如果雜湊環中的下一個伺服器是 S12,則它將分配給第一個物理伺服器。一般而言,分配給虛擬伺服器 Sij 的金鑰將儲存在物理伺服器 Si 上。

總結

本文帶大家認識了一致性雜湊演算法,一致性雜湊於 1997 年推出,它已經在許多分散式系統中的得到了應用。 AmazonDynamo DB 中使用它作分割槽元件。此外,Apache Cassandra Voldermort 等開源應用程式使用它進行資料分割槽。

歡迎關注個人公眾號【JAVA旭陽】

相關文章