這個分佈式對象緩存問題,難倒了年薪 40w 的大廠架構師

作者簡介:李智慧,前阿里巴巴技術專家 本文選自:拉勾教育專欄《

架構師的 36 項修煉

你好,我是李智慧。在實踐中,總有一些棘手的問題讓人困惑。其實,只要吃透本質,多數問題都可以迎刃而解。今天我們來講一講系統架構中,非常重要的一塊內容:分佈式對象緩存

01 分佈式對象緩存

這個分佈式對象緩存問題,難倒了年薪 40w 的大廠架構師

分佈式對象緩存是系統架構中比較重要的一部分內容。所謂的分佈式對象緩存是指對象緩存以一個分佈式集群的方式對外提供服務,多個應用系統使用同一個分佈式對象緩存提供的緩存服務。這裡的緩存服務器是由多臺服務器組成的,這些服務器共同構成了一個集群對外提供服務。

所以使用分佈式對象緩存一個重要的點就是,數據進行讀寫操作的時候,如何找到正確的緩存服務器進行讀寫操作。因為如果第一次寫入數據的時候寫入的是 A 服務器,但是數據進行緩存讀操作的時候訪問的是 B 服務器,就不能夠正確地查找到數據,緩存也就沒有了效果。

點擊文末“瞭解更多”查看:拉勾教育專欄《架構師的 36 項修煉》

02 如何找到正確的緩存服務器

這個分佈式對象緩存問題,難倒了年薪 40w 的大廠架構師

以 Memcached 服務器集群為例,我們來看一下分佈式對象的緩存模型。

當需要進行分佈式緩存訪問的時候,依然是以 Key、value 這樣的數據結構進行訪問。比如說,我們這個例子中就是 BEIJING 作為 Key,一個DATA 數據作為它的 value。

當需要進行分佈式對象訪問的時候,應用程序需要使用分佈式對象緩存的客戶端 SDK。比如說 Memcached 提供的一個客戶端 API 程序進行訪問,客戶端 API 程序會使用自己的路由算法進行路由選擇,選擇其中的某一臺服務器,找到這臺服務器的 IP 地址和端口以後,通過通訊模塊和相對應的這臺服務器進行通信。

因為進行路由選擇的時候,就是使用緩存對象的 Key 進行的計算。下一次使用相同的 Key 使用相同路由算法進行計算的時候,算出來的服務器依然還是前面計算出來的這個服務器。

所以通過這種方法可以訪問到正確的服務器進行數據讀寫。服務器越多,提供的緩存空間就越大,實現的緩存效果也就越好。通過集群的方式,提供了更多的緩存空間。

那麼,路由算法又是如何進行服務器路由選擇的?這裡的主要算法通常講依然可以使用上面講到的哈希表的路由算法,也就是取模算法。

比如說,我們這裡緩存服務器集群中有 3 臺服務器,根據 Key 的哈希值對3 取模得到的餘數一定在 0、1、2 三個數據之間,那麼每一個數字都對應著一臺服務器,根據這個數字查找對應的服務器 IP 地址就可以了

使用餘數取模這種方式進行路由計算非常簡單,但這種算法也有一個問題,就是當服務器進行擴容的時候,比如說我們當前的服務器集群有 3 臺服務器,如果我們 3 臺服務器不夠用了,需要添加 1 臺服務器,這個時候對 3 取膜就會變成對 4 去取模,導致的後果就是以前對 3 取模的時候寫入的數據,對 4 取模的時候可能就查找不到了。

剛才也講過緩存雪崩的情況,實際上如果使用取模算法進行服務器添加,因為除數的變化會導致和緩存雪崩一樣的後果,也就是說前面寫入緩存服務器集群中的緩存數據,添加了 1 臺服務器後很多數據都找不到了,類似於雪崩,

最後會導致整個服務器集群都崩潰。

我們添加服務器的主要目的是提高它的處理能力,但是不正確的操作可能會導致整個集群都失效。

點擊文末“瞭解更多”查看:拉勾教育專欄《架構師的 36 項修煉》

03 使用一致性哈希算法

這個分佈式對象緩存問題,難倒了年薪 40w 的大廠架構師

一致性哈希和餘數哈希不同,一致性哈希首先是構建一個一致性哈希環的結構。一致性哈希環的大小是 0 到 2 的 32 次方減 1 ,實際上就是我們計算機中無符號整型值的取值範圍,這個取值範圍 0 和最後一個值 2 的 32 次方減 1 首尾相連,就構成了一個一致性哈希環。

然後我們對每個服務器的節點取模,求它的哈希值把這個哈希值放到環上,所有的服務器都取哈希值放到環上,每一次進行服務器查找路由計算的時候,都是把 Key 也取它的哈希值,取到哈希值以後把 Key 放到環上,順時針查找距離它最近的服務器的節點是哪一個它的路由節點就是哪一個。

通過這種方式也可以實現,Key 不變的情況下找到的總是相同的服務器。這種一致性哈希算法除了可以實現像餘數哈希一樣的路由效果以外,同時對服務器的集群擴容效果也非常好

在一致性哈希環上進行服務器擴容的時候,新增加一個節點不需要改動前面取模算法裡的除數,導致最後的取值結果全部混亂,它只需要在哈希環里根據新的服務器節點的名稱計算它的哈希值,把哈希值放到這個環上就可以了。

放到環上後,它不會影響到原先節點的哈希值,也不會影響到原先服務器在哈希環上的分佈,它只會影響到離他最近的服務器,比如說圖中 node3 是新加入的服務器,那麼它只會影響到 node1,原先訪問 node1 的一部 key 會訪問到 node3 上,也就是說對緩存的影響是比較小的,它只會影響到緩存裡面的一小段。

我們知道緩存中如果一小部分數據受到了影響,不能夠正確的命中,那麼他可以去數據庫中讀取,而數據庫的壓力只要在它的負載能力之內,也不會崩潰,系統就可以正常運行。所以通過一致性哈希算法可以實現緩存服務器的順利伸縮擴容。

但是一致性哈希算法有一個致命的缺陷。我們知道哈希值其實是一個隨機值,把一個隨機值放到一個環上以後,可能是不均衡的,也就是說某兩個服務器可能距離很近,而和其它的服務器距離很遠,這個時候就會導致有些服務器的負載壓力特別大,有些服務器的負載壓力非常小

同時在進行擴容的時候,比如說加入一個節點 3 它影響的只是節點 1 ,而我們實際上希望加入一個服務器節點的時候,它能夠分攤其他所有服務器的一部分訪問壓力和數據衝突。

但是使用上述描述的算法並不能夠達到這個效果,所以對這個算法需要進行一些改進,改進辦法就是使用虛擬節點。也就是說我們這一個服務器節點放入到一致性哈希環上的時候,並不是把真實的服務器的哈希值放到環上,而是將一個服務器虛擬成若干個虛擬節點,把這些虛擬節點的 hash 值放到環上去。

在實踐中通常是把一個服務器節點虛擬成 200 個虛擬節點,然後把 200 個虛擬節點放到環上。Key 依然是順時針的查找距離它最近的虛擬節點,找到虛擬節點以後,根據映射關係找到真正的物理節點。畫帶虛擬節點的一致性 hash 環比較亂,這裡大家可以想象一下。

通過這種手段第一可以解決我們剛才提到的負載不均衡的問題,因為有更多的虛擬節點在環上,所以它們之間的距離總體來說大致是相近的。同時加入一個新節點的時候,他也是加入多個虛擬節點,比如說 200 個虛擬節點,那麼加入進來以後在環上每個節點都可能會受到影響,從而分攤原先每個服務器的一部分負載。

好的,這節課的內容就到這裡,下一次我會繼續分享我的架構經驗。加油,我們不見不散。

點擊文末“瞭解更多”查看:拉勾教育專欄《架構師的 36 項修煉》

版權聲明:本文版權歸屬拉勾教育及該專欄作者,任何媒體、網站或個人未經本網協議授權不得轉載、鏈接、轉貼或以其他方式複製發佈/發表,違者必究。


分享到:


相關文章: