本文主要是比較三種分布緩存負載均衡的方法,第一種是最簡單的將 key的hash值對機器數取模算法,第二種是一致性哈希算法,第三種是淘寶開源的緩存解決方案tair的均衡算法。下面來分析下這三種算法的優缺點。
第一種:傳統的數據分布方法,將key的hash值對機器數取模
這個算法的實現非常簡單,計算hash(key)/n,n為機器數,得到的值就是該key需要路由到的服務器編號了。
優點:實現簡單
缺點:在服務器數量發生變化的時候,緩存會大量失效。
第二種:一致性hash
試想下如果使用傳統取模算法。如果有一個key要存到緩存中,根據hash(key)/n (n表示有n台緩存服務器),可以計算出緩存所在的服務器id。這個時候有一台服務器掛掉了,剩下n-1台服務器。這時候,同樣一個key,根據hash(key)/(n-1) ,這個時候就命中不了緩存了,基本所有的緩存都會失效,這個情況在線上可能是災難性的。 一致性hash可以解決部分問題。
通常可以考慮[0,(2^32-1)]為hash值的取值范圍。我們可以把取值范圍想象成一個閉環,2^32-1 和 0 相連接。如下圖所示。
這個時候假設我們有4個緩存服務器節點。(node1~node4) 首先計算這四個值的hash值,並且這四個點占據了閉環的四個位置,如上圖所示,這個時候需要根據key來路由緩存服務器,首先計算hash(key)值(這個hash算法需要保證hash(key)的值落在在區間[0,2^32-1]中)。然后我們可以想象這個hash(key)值位於閉環的其中一個點,然后用這個hash(key)依次加一,直到命中其中一個node為止,命中的node就是key需要路由的服務器,如上圖,需要找到key2的緩存值所在的服務器,首先計算hash(key2),落在如圖的閉環中,然后順時針找到離這個hash值最近的node,可以看到是node1。key2對應的緩存值就存在node1中。
如果這個時候如果增加了節點node5如下圖:
看下緩存失效的情況,如上圖,只有hash(key)落在node4~node5之間的key才會有影響,在未增加node5之前落在這個范圍的key最終路由到node1,增加node5之后路由到node5了,緩存失效了。除此之外,落在其他范圍的hash值就不受影響。如上圖,增加node5之后key6原來會路由到node1的,現在路由到node5。而key1不受影響,還是路由到node1。
同理,如果少掉一個節點,還是上圖為例,假如這個時候node5失效了,node5失效之前落在node4~node5之間的hash值,會路由到node5,失效之后會路由到node1,也就是原本落在node4~node5之間的hash值失效了。由此可見一致性hash能在節點變動的時候減少緩存失效的比例。
還有一種情況,當cache的數量很少的或者緩存服務器很少的時候,會導致緩存分布不均衡。如下圖所示:
當只有很少節點的時候,例如上圖的兩個,這個時候就會分配得很不均衡。上圖中node1明顯承受了較多的壓力。這個時候引入一個叫虛擬節點的概念,原理就是增加更多的虛擬節點,讓每個節點承受的壓力盡量均衡。這些增加的節點並不是真正新增的服務器節點,而是由原來的節點”復制“出來的。例如下圖,”復制“了兩個新節點,node3,node4。實際上,落到node3,node4的key最終訪問的服務器是node2,node1.
這樣就可以把壓力盡量分配到各個機器上。另外需要維護一張表格,用來記錄虛擬節點指向的真正節點。
優點:相比簡單的對機器數取模算法,當節點變動的時候只有相對較少的key失效,實現也相對簡單。不需要進行數據遷移,每個服務器是獨立的。
缺點:還是會有部分的key失效,如果訪問量非常大的時候,如果訪問到失效的key的時候,就會直接訪問到數據源上面去了,可能會導致數據源直接壓掛。
第三種:tair負載均衡算法,構造一張對照表
首先看下如何構建一張對照表。
tair中數據的存儲是以bucket為單位的,一個bucket對應一個dateServer,一個dateServer中會有多個bucket。bucket的數目是固定的,這里假設bucket的數量為1024個,並且每個bucket有屬於自己的id,依次從0到1023。如下圖:
可以看到對key進行hash之后首先會路由到某個bucket然后根據對應關系,再路由到對應的dataServer。
在tair中,你可以設置同一個bucket的備份數目,我們稱其中一個bucket為master,其他的備份稱為slave。configServer啟動的時候會構造三張bucket和dataServer的對照表,分別是hash_table, m_hash_table, d_hash_table,這三張對照表在初始構建的時候都是一樣的,但是在dataServer發生增加的時候會暫時不一樣,但是最終會趨向一致,三張表分別有什么用處下面會講到。configServer在構建對照表的時候有兩種模式,一種是均衡模式,這種模式會盡量保證每個節點的bucket數目差不多。一種是安全模式,這種模式會盡量保證一份數據以及它的備份分配在不同的區域的服務器上。client通過configServer獲取對照表(hash_table)之后,根據(hash(key)%bucket的總數)算出bucket的id並且參照對照表,就能知道這個key對應的dataServer了。對照表的結構大概如下(這里為了簡化,一共只有5個bucket,4個dataServer(ds1,ds2,ds3,ds4),這個是初始時的對照表,hash_table, m_hash_table, d_hash_table的內容都是一樣的)。
可以看到對key進行hash之后首先會路由到某個bucket然后根據對應關系,再路由到對應的dataServer。
在tair中,你可以設置同一個bucket的備份數目,我們稱其中一個bucket為master,其他的備份稱為slave。configServer啟動的時候會構造三張bucket和dataServer的對照表,分別是hash_table, m_hash_table, d_hash_table,這三張對照表在初始構建的時候都是一樣的,但是在dataServer發生增加的時候會暫時不一樣,但是最終會趨向一致,三張表分別有什么用處下面會講到。configServer在構建對照表的時候有兩種模式,一種是均衡模式,這種模式會盡量保證每個節點的bucket數目差不多。一種是安全模式,這種模式會盡量保證一份數據以及它的備份分配在不同的區域的服務器上。client通過configServer獲取對照表(hash_table)之后,根據(hash(key)%bucket的總數)算出bucket的id並且參照對照表,就能知道這個key對應的dataServer了。對照表的結構大概如下(這里為了簡化,一共只有5個bucket,4個dataServer(ds1,ds2,ds3,ds4),這個是初始時的對照表,hash_table, m_hash_table, d_hash_table的內容都是一樣的)。
當服務器變化之后configserver是如何重構對照表的?重構對照表的時候,數據是怎么遷移的?
configServer會定時發送心跳包給dataServer,用來檢測dataServer是否還存活,如果沒有存活的話就會重新構造對照表。或者如果有新加dataServer的話也會重新構造對照表。
當有一個dataServer當機之后,這個機器 上的存儲的bucket數據都會丟失,如果是master bucket丟失之后其中一個slave bucket會補上成為master bucket。至於選擇哪個slave bucket成為master bucket的是根據slave 上負載的master bucket有沒有超標,並且總bucket數有沒有超標等標准。舉個例子,上圖中當ds2失效之后,hash_table, m_hash_table, d_hash_table三個表分別是這樣子的。
hash_table :
m_hash_table:
d_hash_table:

hash_table發送給client,client不至於不能工作,m_hash_table和d_hash_table發送到dataServer,dataServer根據這兩張表就可以知道自己是否需要遷移數據和遷移到哪里了。例如這里第一列,第三排,對比兩表,可以知道,ds1需要把數據遷移到ds4。
如果是要增加新的dataServer,原理就差不多了,configServer新建對照表,然后把對照表發送到dateServer,然后dataServer再根據新對照表來做數據遷移,完成之后,client就可以使用新的對照表了。
優點:當需要增加機器的時候,不會有key失效。
缺點:實現比較復雜,需要進行數據的遷移。
