gossip協議是一種計算機對計算機的通信協議,它來源於日常社交活動中小道傳聞; 現代的分布式系統中通常使用gossip協議來解決用其他方法很難解決的問題.
1.背景
Gossip算法又被稱為反熵(Anti-Entropy),熵是物理學上的一個概念,代表雜亂無章,而反熵就是在雜亂無章中尋求一致,這充分說明了Gossip的特點:在一個有界網絡中,每個節點都隨機地與其他節點通信,經過一番雜亂無章的通信,最終所有節點的狀態都會達成一致。每個節點可能知道所有其他節點,也可能僅知道幾個鄰居節點,只要這些節可以通過網絡連通,最終他們的狀態都是一致的,當然這也是疫情傳播的特點。
要注意到的一點是,即使有的節點因宕機而重啟,有新節點加入,但經過一段時間后,這些節點的狀態也會與其他節點達成一致,也就是說,Gossip天然具有分布式容錯的優點。
Gossip是一個帶冗余的容錯算法,更進一步,Gossip是一個最終一致性算法。雖然無法保證在某個時刻所有節點狀態一致,但可以保證在”最終“所有節點一致,”最終“是一個現實中存在,但理論上無法證明的時間點。
因為Gossip不要求節點知道所有其他節點,因此又具有去中心化的特點,節點之間完全對等,不需要任何的中心節點。實際上Gossip可以用於眾多能接受“最終一致性”的領域:失敗檢測、路由同步、Pub/Sub、動態負載均衡。例如,Cassandra集群沒有中心節點,各個節點的地位完全相同,它們通過gossip協議維護集群的狀態。通過gossip,每個節點都能知道集群中包含哪些節點,以及這些節點的狀態,這使得Cassandra集群中的任何一個節點都可以完成任意key的路由,任意一個節點不可用都不會造成災難性的后果。
但Gossip的缺點也很明顯,冗余通信會對網路帶寬、CPU資源造成很大的負載,而這些負載又受限於通信頻率,該頻率又影響着算法收斂的速度,后面我們會講在各種場合下的優化方法。
2.基本概念
gossip分為兩種. 本文只討論anti-entropy
■anti-entropy 只要數據不同步,就開始同步數據
■rumor mongering 每隔固定的時間同步數據
Gossip中的每個節點維護一組狀態,狀態可以用一個key/value對表示,還附帶一個版本號,版本號大的為更新的狀態。信息達到同步的時間大概是log(N),這里N表示節點的數量。
為了保證一致性,規定數據的value及version只有宿主節點才能修改,其他節點只能間接通過Gossip協議來請求數據對應的宿主節點修改,即m (p)只能由有節點p來修改。
anti-entropy協議通過版本號大小來對數據進行更新。
兩個節點(A、B)之間存在三種通信方式:
■push-gossip: A節點將數據推送給B節點,B節點更新A中比自己新的數據
■pull-gossip:A僅將摘要數據 (node,key,value,version)推送給B,B根據摘要數據來選擇那些版本號比A高的數據推送給A,A更新本地。
■push-pull gossip:與pull類似,只是多了一步,A再將本地比B新的數據推送給B,B更新本地。
■rumor mongering 每隔固定的時間同步數據
Gossip中的每個節點維護一組狀態,狀態可以用一個key/value對表示,還附帶一個版本號,版本號大的為更新的狀態。信息達到同步的時間大概是log(N),這里N表示節點的數量。
為了保證一致性,規定數據的value及version只有宿主節點才能修改,其他節點只能間接通過Gossip協議來請求數據對應的宿主節點修改,即m (p)只能由有節點p來修改。
anti-entropy協議通過版本號大小來對數據進行更新。
兩個節點(A、B)之間存在三種通信方式:
■push-gossip: A節點將數據推送給B節點,B節點更新A中比自己新的數據
■pull-gossip:A僅將摘要數據 (node,key,value,version)推送給B,B根據摘要數據來選擇那些版本號比A高的數據推送給A,A更新本地。
■push-pull gossip:與pull類似,只是多了一步,A再將本地比B新的數據推送給B,B更新本地。
如果把兩個節點數據同步一次定義為一個周期,則在一個周期內,push需通信1次,pull需2次,push/pull則需3次。從效果上來講,push/pull最好,理論上一個周期內可以使兩個節點完全一致。直觀上也感覺,push/pull的收斂速度是最快的。
Cassandra就是使用的push-pull通信方式,所以cassandra的node by node gossip的時候,會有三次通信
3.算法樣例
Cassandra內部有一個Gossiper,每隔一秒運行一次(在Gossiper.java的start方法中),按照以下規則向其他節點發送同步消息:
1、隨機取一個當前活着的節點,並向它發送同步請求
2、向隨機一台不可達的機器發送同步請求
3、如果第一步中所選擇的節點不是seed,或者當前活着的節點數少於seed數,則向隨意一台seed發送同步請求
如果沒有這個判斷,考慮這樣一種場景,有4台機器,{A, B, C, D},並且配置了它們都是seed,如果它們同時啟動,可能會出現這樣的情形:
1、A節點起來,發現沒有活着的節點,走到第三步,和任意一個種子同步,假設選擇了B
2、B節點和A完成同步,則認為A活着,它將和A同步,由於A是種子,B將不再和其他種子同步
3、C節點起來,發現沒有活着的節點,同樣走到第三步,和任意一個種子同步,假設這次選擇了D
4、C節點和D完成同步,認為D活着,則它將和D同步,由於D也是種子,所以C也不再和其他種子同步
這時就形成了兩個孤島,A和B互相同步,C和D之間互相同步,但是{A,B}和{C,D}之間將不再互相同步,它們也就不知道對方的存在了。
加入第二個判斷后,A和B同步完,發現只有一個節點活着,但是seed有4個,這時會再和任意一個seed通信,從而打破這個孤島。
3.算法樣例
Cassandra內部有一個Gossiper,每隔一秒運行一次(在Gossiper.java的start方法中),按照以下規則向其他節點發送同步消息:
1、隨機取一個當前活着的節點,並向它發送同步請求
2、向隨機一台不可達的機器發送同步請求
3、如果第一步中所選擇的節點不是seed,或者當前活着的節點數少於seed數,則向隨意一台seed發送同步請求
如果沒有這個判斷,考慮這樣一種場景,有4台機器,{A, B, C, D},並且配置了它們都是seed,如果它們同時啟動,可能會出現這樣的情形:
1、A節點起來,發現沒有活着的節點,走到第三步,和任意一個種子同步,假設選擇了B
2、B節點和A完成同步,則認為A活着,它將和A同步,由於A是種子,B將不再和其他種子同步
3、C節點起來,發現沒有活着的節點,同樣走到第三步,和任意一個種子同步,假設這次選擇了D
4、C節點和D完成同步,認為D活着,則它將和D同步,由於D也是種子,所以C也不再和其他種子同步
這時就形成了兩個孤島,A和B互相同步,C和D之間互相同步,但是{A,B}和{C,D}之間將不再互相同步,它們也就不知道對方的存在了。
加入第二個判斷后,A和B同步完,發現只有一個節點活着,但是seed有4個,這時會再和任意一個seed通信,從而打破這個孤島。
我曾經在多個地方聽到有人說,cassandra gossip的時候是任意選取3個nodes來通信。這是不對的,從源代碼來看,上面才是正確的方法;