Gossip病毒協議 以及冪等處理

背景

Gossip protocol 也叫 Epidemic Protocol (流行病協議),實際上它還有很多別名,比如:“流言算法”、“疫情傳播算法”等。

這個協議的作用就像其名字表示的意思一樣,非常容易理解,它的方式其實在我們日常生活中也很常見,比如電腦病毒的傳播,森林大火,細胞擴散等等。

Gossip protocol 最早是在 1987 年發表在 ACM 上的論文 《Epidemic Algorithms for Replicated Database Maintenance》中被提出。主要用在分佈式數據庫系統中各個副本節點同步數據之用,這種場景的一個最大特點就是組成的網絡的節點都是對等節點,是非結構化網絡,這區別與之前介紹的用於結構化網絡中的 DHT 算法 Kadmelia。

我們知道,很多知名的 P2P 網絡或區塊鏈項目,比如 IPFS,Ethereum 等,都使用了 Kadmelia 算法,而大名鼎鼎的 Bitcoin 則是使用了 Gossip 協議來傳播交易和區塊信息。

實際上,只要仔細分析一下場景就知道,Ethereum 使用 DHT 算法並不是很合理,因爲它使用節點保存整個鏈數據,不像 IPFS 那樣分片保存數據,因此 Ethereum 真正適合的協議應該像 Bitcoin 那樣,是 Gossip 協議。

這裏先簡單介紹一下 Gossip 協議的執行過程:

Gossip 過程是由種子節點發起,當一個種子節點有狀態需要更新到網絡中的其他節點時,它會隨機的選擇周圍幾個節點散播消息,收到消息的節點也會重複該過程,直至最終網絡中所有的節點都收到了消息。這個過程可能需要一定的時間,由於不能保證某個時刻所有節點都收到消息,但是理論上最終所有節點都會收到消息,因此它是一個最終一致性協議。

Gossip 演示

現在,我們通過一個具體的實例來深入體會一下 Gossip 傳播的完整過程

爲了表述清楚,我們先做一些前提設定

1、Gossip 是週期性的散播消息,把週期限定爲 1 秒

2、被感染節點隨機選擇 k 個鄰接節點(fan-out)散播消息,這裏把 fan-out 設置爲 3,每次最多往 3 個節點散播。

3、每次散播消息都選擇尚未發送過的節點進行散播

4、收到消息的節點不再往發送節點散播,比如 A -> B,那麼 B 進行散播的時候,不再發給 A。

注意:Gossip 過程是異步的,也就是說發消息的節點不會關注對方是否收到,即不等待響應;不管對方有沒有收到,它都會每隔 1 秒向周圍節點發消息。異步是它的優點,而消息冗餘則是它的缺點。

這裏一共有 16 個節點,節點 1 爲初始被感染節點,通過 Gossip 過程,最終所有節點都被感染:

Gossip 的特點(優勢)

1)擴展性

網絡可以允許節點的任意增加和減少,新增加的節點的狀態最終會與其他節點一致。

2)容錯

網絡中任何節點的宕機和重啓都不會影響 Gossip 消息的傳播,Gossip 協議具有天然的分佈式系統容錯特性。

3)去中心化

Gossip 協議不要求任何中心節點,所有節點都可以是對等的,任何一個節點無需知道整個網絡狀況,只要網絡是連通的,任意一個節點就可以把消息散播到全網。

4)一致性收斂

Gossip 協議中的消息會以一傳十、十傳百一樣的指數級速度在網絡中快速傳播,因此係統狀態的不一致可以在很快的時間內收斂到一致。消息傳播速度達到了 logN。

5)簡單

Gossip 協議的過程極其簡單,實現起來幾乎沒有太多複雜性。

Márk Jelasity 在它的 Gossip一書中對其進行了歸納:

Gossip 的缺陷

分佈式網絡中,沒有一種完美的解決方案,Gossip 協議跟其他協議一樣,也有一些不可避免的缺陷,主要是兩個:

1)消息的延遲

由於 Gossip 協議中,節點只會隨機向少數幾個節點發送消息,消息最終是通過多個輪次的散播而到達全網的,因此使用 Gossip 協議會造成不可避免的消息延遲。不適合用在對實時性要求較高的場景下。

2)消息冗餘

Gossip 協議規定,節點會定期隨機選擇周圍節點發送消息,而收到消息的節點也會重複該步驟,因此就不可避免的存在消息重複發送給同一節點的情況,造成了消息的冗餘,同時也增加了收到消息的節點的處理壓力。而且,由於是定期發送,因此,即使收到了消息的節點還會反覆收到重複消息,加重了消息的冗餘。

Gossip 類型

Gossip 有兩種類型:

  • Anti-Entropy(反熵):以固定的概率傳播所有的數據
  • Rumor-Mongering(謠言傳播):僅傳播新到達的數據

Anti-Entropy 是 SI model,節點只有兩種狀態,Suspective 和 Infective,叫做 simple epidemics。

Rumor-Mongering 是 SIR model,節點有三種狀態,Suspective,Infective 和 Removed,叫做 complex epidemics。

其實,Anti-entropy 反熵是一個很奇怪的名詞,之所以定義成這樣,Jelasity 進行了解釋,因爲 entropy 是指混亂程度(disorder),而在這種模式下可以消除不同節點中數據的 disorder,因此 Anti-entropy 就是 anti-disorder。換句話說,它可以提高系統中節點之間的 similarity。

在 SI model 下,一個節點會把所有的數據都跟其他節點共享,以便消除節點之間數據的任何不一致,它可以保證最終、完全的一致。

由於在 SI model 下消息會不斷反覆的交換,因此消息數量是非常龐大的,無限制的(unbounded),這對一個系統來說是一個巨大的開銷。

但是在 Rumor Mongering(SIR Model) 模型下,消息可以發送得更頻繁,因爲消息只包含最新 update,體積更小。而且,一個 Rumor 消息在某個時間點之後會被標記爲 removed,並且不再被傳播,因此,SIR model 下,系統有一定的概率會不一致。

而由於,SIR Model 下某個時間點之後消息不再傳播,因此消息是有限的,系統開銷小。

Gossip 中的通信模式

在 Gossip 協議下,網絡中兩個節點之間有三種通信方式:

  • Push: 節點 A 將數據 (key,value,version) 及對應的版本號推送給 B 節點,B 節點更新 A 中比自己新的數據
  • Pull:A 僅將數據 key, version 推送給 B,B 將本地比 A 新的數據(Key, value, version)推送給 A,A 更新本地
  • Push/Pull:與 Pull 類似,只是多了一步,A 再將本地比 B 新的數據推送給 B,B 則更新本地

如果把兩個節點數據同步一次定義爲一個週期,則在一個週期內,Push 需通信 1 次,Pull 需 2 次,Push/Pull 則需 3 次。雖然消息數增加了,但從效果上來講,Push/Pull 最好,理論上一個週期內可以使兩個節點完全一致。直觀上,Push/Pull 的收斂速度也是最快的。

複雜度分析

對於一個節點數爲 N 的網絡來說,假設每個 Gossip 週期,新感染的節點都能再感染至少一個新節點,那麼 Gossip 協議退化成一個二叉樹查找,經過 LogN 個週期之後,感染全網,時間開銷是 O(LogN)。由於每個週期,每個節點都會至少發出一次消息,因此,消息複雜度(消息數量 = N * N)是 O(N^2) 。注意,這是 Gossip 理論上最優的收斂速度,但是在實際情況中,最優的收斂速度是很難達到的。

假設某個節點在第 i 個週期被感染的概率爲 pi,第 i+1 個週期被感染的概率爲 pi+1 ,

1)則 Pull 的方式:

2)Push 方式:

顯然 Pull 的收斂速度大於 Push ,而每個節點在每個週期被感染的概率都是固定的 p (0<p<1),因此 Gossip 算法是基於 p 的平方收斂,也稱爲概率收斂,這在衆多的一致性算法中是非常獨特的。

冪等處理
一、背景

  1. 前端重複提交選中的數據,應該後臺只產生對應這個數據的一個反應結果。
    2. 我們發起一筆付款請求,應該只扣用戶賬戶一次錢,當遇到網絡重發或系統bug重發,也應該只扣一次錢;
    3. 發送消息,也應該只發一次,同樣的短信發給用戶,用戶會哭的;
    4. 創建業務訂單,一次業務請求只能創建一個,創建多個就會出大問題。

二、什麼事冪等

一個操作,不論執行多少次,產生的效果和返回的結果都是一樣的

同樣一個請求連續發兩遍(請求的參數可能有細微不一樣,比如時間戳,但是對後臺來說這應該屬於同一個請求),想達到的目的是:兩個請求同時到達的時候只有一個請求在執行,另外一個請求等待第一個請求結束,並返回相同結果。這就是冪等的意思。

三、實現冪等有哪些思路

1. 查詢操作

查詢一次和查詢多次,在數據不變的情況下,查詢結果是一樣的。select是天然的冪等操作

2. 刪除操作

刪除操作也是冪等的,刪除一次和多次刪除都是把數據刪除。(注意可能返回結果不一樣,刪除的數據不存在,返回0,刪除的數據多條,返回結果多個)

3.唯一索引,防止新增髒數據

比如:支付寶的資金賬戶,支付寶也有用戶賬戶,每個用戶只能有一個資金賬戶,怎麼防止給用戶創建資金賬戶多個,那麼給資金賬戶表中的用戶ID加唯一索引,所以一個用戶新增成功一個資金賬戶記錄

要點:

唯一索引或唯一組合索引來防止新增數據存在髒數據

(當表存在唯一索引,併發時新增報錯時,再查詢一次就可以了,數據應該已經存在了,返回結果即可)

4. token機制,防止頁面重複提交

業務要求:

頁面的數據只能被點擊提交一次

發生原因:

由於重複點擊或者網絡重發,或者nginx重發等情況會導致數據被重複提交

解決辦法:

集羣環境:採用token加redis(redis單線程的,處理需要排隊)

單JVM環境:採用token加redis或token加jvm內存

處理流程:

1. 數據提交前要向服務的申請token,token放到redis或jvm內存,token有效時間

2. 提交後後臺校驗token,同時刪除token,生成新的token返回

token特點:

要申請,一次有效性,可以限流

注意:redis要用刪除操作來判斷token,刪除成功代表token校驗通過,如果用select+delete來校驗token,存在併發問題,不建議使用

5. 悲觀鎖

獲取數據的時候加鎖獲取

select * from table_xxx where id='xxx' for update;

注意:id字段一定是主鍵或者唯一索引,不然是鎖表,會死人的

悲觀鎖使用時一般伴隨事務一起使用,數據鎖定時間可能會很長,根據實際情況選用

6. 樂觀鎖

樂觀鎖只是在更新數據那一刻鎖表,其他時間不鎖表,所以相對於悲觀鎖,效率更高。

樂觀鎖的實現方式多種多樣可以通過version或者其他狀態條件:

1). 通過版本號實現

update table_xxx set name=#name#,version=version+1 where version=#version#

2). 通過條件限制

update table_xxx set avai_amount=avai_amount-#subAmount# where avai_amount-#subAmount# >= 0

要求:quality-#subQuality# >= ,這個情景適合不用版本號,只更新是做數據安全校驗,適合庫存模型,扣份額和回滾份額,性能更高

注意:樂觀鎖的更新操作,最好用主鍵或者唯一索引來更新,這樣是行鎖,否則更新時會鎖表,上面兩個sql改成下面的兩個更好

update table_xxx set name=#name#,version=version+1 where id=#id# and version=#version#

update table_xxx set avai_amount=avai_amount-#subAmount# where id=#id# and avai_amount-#subAmount# >= 0

7. 分佈式鎖

還是拿插入數據的例子,如果是分佈是系統,構建全局唯一索引比較困難,例如唯一性的字段沒法確定,這時候可以引入分佈式鎖,通過第三方的系統(redis或zookeeper),在業務系統插入數據或者更新數據,獲取分佈式鎖,然後做操作,之後釋放鎖,這樣其實是把多線程併發的鎖的思路,引入多多個系統,也就是分佈式系統中得解決思路。

要點:某個長流程處理過程要求不能併發執行,可以在流程執行之前根據某個標誌(用戶ID+後綴等)獲取分佈式鎖,其他流程執行時獲取鎖就會失敗,也就是同一時間該流程只能有一個能執行成功,執行完成後,釋放分佈式鎖(分佈式鎖要第三方系統提供)

8. select + insert

併發不高的後臺系統,或者一些任務JOB,爲了支持冪等,支持重複執行,簡單的處理方法是,先查詢下一些關鍵數據,判斷是否已經執行過,在進行業務處理,就可以了

注意:核心高併發流程不要用這種方法

9. 狀態機冪等

在設計單據相關的業務,或者是任務相關的業務,肯定會涉及到狀態機(狀態變更圖),就是業務單據上面有個狀態,狀態在不同的情況下會發生變更,一般情況下存在有限狀態機,這時候,如果狀態機已經處於下一個狀態,這時候來了一個上一個狀態的變更,理論上是不能夠變更的,這樣的話,保證了有限狀態機的冪等。

注意:訂單等單據類業務,存在很長的狀態流轉,一定要深刻理解狀態機,對業務系統設計能力提高有很大幫助

10. 對外提供接口的api如何保證冪等

如銀聯提供的付款接口:需要接入商戶提交付款請求時附帶:source來源,seq序列號

source+seq在數據庫裏面做唯一索引,防止多次付款,(併發時,只能處理一個請求
)

轉載於:https://www.cnblogs.com/h-c-g/p/10415048.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章