[TOC]
# 分布式算法
在做服務器負載均衡時候可供選擇的負載均衡的算法有很多,包括: 輪循算法(Round Robin)、哈希算法(HASH)、最少連接算法(Least Connection)、響應速度算法(Response Time)、加權法(Weighted )等。其中哈希算法是最為常用的算法.
典型的應用場景是: 有N臺服務器提供緩存服務,需要對服務器進行負載均衡,將請求平均分發到每臺服務器上,每臺機器負責1/N的服務。
常用的算法是對hash結果取余數 (hash() mod N ):對機器編號從0到N-1,按照自定義的 hash()算法,對每個請求的hash()值按N取模,得到余數i,然后將請求分發到編號為i的機器。但這樣的算法方法存在致命問題,如果某一臺機器宕機,那么應該落在該機器的請求就無法得到正確的處理,這時需要將當掉的服務器從算法從去除,此時候會有(N-1)/N的服務器的緩存數據需要重新進行計算;如果新增一臺機器,會有N /(N+1)的服務器的緩存數據需要進行重新計算。對于系統而言,這通常是不可接受的顛簸(因為這意味著大量緩存的失效或者數據需要轉移)。那么,如何設計一個負載均衡策略,使得受到影響的請求盡可能的少呢?
在Memcached、Key-Value Store 、Bittorrent DHT、LVS中都采用了Consistent Hashing算法,可以說Consistent Hashing 是分布式系統負載均衡的首選算法。
# 分布式緩存問題
在大型web應用中,緩存可算是當今的一個標準開發配置了。在大規模的緩存應用中,應運而生了分布式緩存系統。分布式緩存系統的基本原理,大家也有所耳聞。key-value如何均勻的分散到集群中?說到此,最常規的方式莫過于hash取模的方式。比如集群中可用機器適量為N,那么key值為K的的數據請求很簡單的應該路由到hash(K) mod N對應的機器。的確,這種結構是簡單的,也是實用的。但是在一些高速發展的web系統中,這樣的解決方案仍有些缺陷。隨著系統訪問壓力的增長,緩存系統不得不通過增加機器節點的方式提高集群的相應速度和數據承載量。增加機器意味著按照hash取模的方式,在增加機器節點的這一時刻,大量的緩存命不中,緩存數據需要重新建立,甚至是進行整體的緩存數據遷移,瞬間會給DB帶來極高的系統負載,設置導致DB服務器宕機。 那么就沒有辦法解決hash取模的方式帶來的詬病嗎?
假設我們有一個網站,最近發現隨著流量增加,服務器壓力越來越大,之前直接讀寫數據庫的方式不太給力了,于是我們想引入Memcached作為緩存機制。現在我們一共有三臺機器可以作為Memcached服務器,如下圖所示:

很顯然,最簡單的策略是將每一次Memcached請求隨機發送到一臺Memcached服務器,但是這種策略可能會帶來兩個問題:一是同一份數據可能被存在不同的機器上而造成數據冗余,二是有可能某數據已經被緩存但是訪問卻沒有命中,因為無法保證對相同key的所有訪問都被發送到相同的服務器。因此,隨機策略無論是時間效率還是空間效率都非常不好。
要解決上述問題只需做到如下一點:保證對相同key的訪問會被發送到相同的服務器。很多方法可以實現這一點,最常用的方法是計算哈希。例如對于每次訪問,可以按如下算法計算其哈希值:
~~~
h = Hash(key) % 3
~~~
其中Hash是一個從字符串到正整數的哈希映射函數。這樣,如果我們將Memcached Server分別編號為0、1、2,那么就可以根據上式和key計算出服務器編號h,然后去訪問。
這個方法雖然解決了上面提到的兩個問題,但是存在一些其它的問題。如果將上述方法抽象,可以認為通過:
~~~
h = Hash(key) % N
~~~
這個算式計算每個key的請求應該被發送到哪臺服務器,其中N為服務器的臺數,并且服務器按照0 – (N-1)編號。
這個算法的問題在于容錯性和擴展性不好。所謂容錯性是指當系統中某一個或幾個服務器變得不可用時,整個系統是否可以正確高效運行;而擴展性是指當加入新的服務器后,整個系統是否可以正確高效運行。
現假設有一臺服務器宕機了,那么為了填補空缺,要將宕機的服務器從編號列表中移除,后面的服務器按順序前移一位并將其編號值減一,此時每個key就要按h = Hash(key) % (N-1)重新計算;同樣,如果新增了一臺服務器,雖然原有服務器編號不用改變,但是要按h = Hash(key) % (N+1)重新計算哈希值。因此系統中一旦有服務器變更,大量的key會被重定位到不同的服務器從而造成大量的緩存不命中。而這種情況在分布式系統中是非常糟糕的。
一個設計良好的分布式哈希方案應該具有良好的單調性,即服務節點的增減不會造成大量哈希重定位。一致性哈希算法就是這樣一種哈希方案。
Hash 算法的一個衡量指標是單調性( Monotonicity ),定義如下:單調性是指如果已經有一些內容通過哈希分派到了相應的緩沖中,又有新的緩沖加入到系統中。哈希的結果應能夠保證原有已分配的內容可以被映射到新的緩沖中去,而不會被映射到舊的緩沖集合中的其他緩沖區。
容易看到,上面的簡單 hash 算法 hash(object)%N 難以滿足單調性要求
# 一致性哈希算法的理解
## 算法簡述
一致性哈希算法(Consistent Hashing Algorithm)是一種分布式算法,常用于負載均衡。Memcached client也選擇這種算法,解決將key-value均勻分配到眾多Memcached server上的問題。它可以取代傳統的取模操作,解決了取模操作無法應對增刪Memcached Server的問題(增刪server會導致同一個key,在get操作時分配不到數據真正存儲的server,命中率會急劇下降)。
簡單來說,一致性哈希將整個哈希值空間組織成一個虛擬的圓環,如假設某哈希函數H的值空間為`0 - (2^32)-1`(即哈希值是一個32位無符號整形),整個哈希空間環如下:

整個空間按順時針方向組織。0和(2^32)-1在零點中方向重合。
下一步將各個服務器使用H進行一個哈希,具體可以選擇服務器的ip或主機名作為關鍵字進行哈希,這樣每臺機器就能確定其在哈希環上的位置,這里假設將上文中三臺服務器使用ip地址哈希后在環空間的位置如下

接下來使用如下算法定位數據訪問到相應服務器:將數據key使用相同的函數H計算出哈希值h,通根據h確定此數據在環上的位置,從此位置沿環順時針“行走”,第一臺遇到的服務器就是其應該定位到的服務器。
例如我們有A、B、C、D四個數據對象,經過哈希計算后,在環空間上的位置如下

根據一致性哈希算法,數據A會被定為到Server 1上,D被定為到Server 3上,而B、C分別被定為到Server 2上
## 容錯性與可擴展性分析
下面分析一致性哈希算法的容錯性和可擴展性。現假設Server 3宕機了:

可以看到此時A、C、B不會受到影響,只有D節點被重定位到Server 2。一般的,在一致性哈希算法中,如果一臺服務器不可用,則受影響的數據僅僅是此服務器到其環空間中前一臺服務器(即順著逆時針方向行走遇到的第一臺服務器)之間數據,其它不會受到影響。
下面考慮另外一種情況,如果我們在系統中增加一臺服務器Memcached Server 4:

此時A、D、C不受影響,只有B需要重定位到新的Server 4。一般的,在一致性哈希算法中,如果增加一臺服務器,則受影響的數據僅僅是新服務器到其環空間中前一臺服務器(即順著逆時針方向行走遇到的第一臺服務器)之間數據,其它不會受到影響。
綜上所述,一致性哈希算法對于節點的增減都只需重定位環空間中的一小部分數據,具有較好的容錯性和可擴展性
## 虛擬節點
一致性哈希算法在服務節點太少時,容易因為節點分部不均勻而造成數據傾斜問題。例如我們的系統中有兩臺服務器,其環分布如下

此時必然造成大量數據集中到Server 1上,而只有極少量會定位到Server 2上。為了解決這種數據傾斜問題,一致性哈希算法引入了虛擬節點機制,即對每一個服務節點計算多個哈希,每個計算結果位置都放置一個此服務節點,稱為虛擬節點。具體做法可以在服務器ip或主機名的后面增加編號來實現。例如上面的情況,我們決定為每臺服務器計算三個虛擬節點,于是可以分別計算`“Memcached Server 1#1”`、`“Memcached Server 1#2”`、`“Memcached Server 1#3”`、`“Memcached Server 2#1”`、`“Memcached Server 2#2”`、`“Memcached Server 2#3”`的哈希值,于是形成六個虛擬節點:

- OAuth
- 簡介
- 步驟
- 單點登錄
- .user.ini
- 時間轉換為今天昨天前天幾天前
- 獲取ip接口
- 協程
- 概念
- yield-from && return-values
- 協程與阻塞的思考
- 中間件
- mysqli異步與php的協程
- 代碼片段
- pdo 執行的sql語句
- 二進制安全
- 捕捉異常中斷
- global
- 利用cookie模擬登陸
- 解析非正常json
- 簡單的對稱加密算法
- RSA 加密
- 過濾掉emoji表情
- 判斷遠程圖片是否存在
- 一分鐘限制請求100次
- 文件處理
- 多文件上傳
- 顯示所有文件
- 文件下載和上面顯示所有文件配合
- 文件的刪除,統計,存數組等
- 圖片處理
- 簡介
- 驗證碼
- 圖片等比縮放
- 批量添加水印
- beanstalkd
- 安裝
- 使用
- RabbitMQ
- 簡介
- debain安裝
- centos安裝
- 常用方法
- 入門
- 工作隊列
- 訂閱,發布
- 路由
- 主題
- 遠程調用RPC
- 消息中間件的選型
- .htaccess
- isset、empty、if區別以及0、‘’、null
- php各版本
- php7.2 不向后兼容的改動
- php中的各種坑
- php7改變
- php慢日志
- 郵件
- PHPMailer實現發郵件
- 驗證郵件地址真實性
- 文件下載
- FastCgi 與 PHP-fpm 之間的關系
- openssl 加解密
- 反射
- 鉤子方法
- 查找插件
- opcode
- opcache使用
- opcache優化
- 分布式一致性hash算法
- 概念
- 哈希算法好壞的四個定義
- php實現
- java實現
- 數組
- jwt
- jwt簡介
- 單點登錄
- phpize
- GeoIP擴展
- php無法獲得https網頁內容的解決方案
- homestead運行的腳本
- Unicode和Utf-8轉換
- php優化
- kafka
- fpm配置
- configure配置詳解