分佈式

分佈式一致性算法

    • Paxos(帕克索斯)
      • 階段一
        • (a) Proposer選擇一個提案編號N,然後向半數以上的Acceptor發送編號爲N的Prepare請求。
        • (b) 如果一個Acceptor收到一個編號爲N的Prepare請求,且N大於該Acceptor已經響應過的所有Prepare請求的編號,那麼它就會將它已經接受過的編號最大的提案(如果有的話)作爲響應反饋給Proposer,同時該Acceptor承諾不再接受任何編號小於N的提案。
      • 階段二
        • (a) 如果Proposer收到半數以上Acceptor對其發出的編號爲N的Prepare請求的響應,那麼它就會發送一個針對[N,V]提案的Accept請求給半數以上的Acceptor。注意:V就是收到的響應中編號最大的提案的value,如果響應中不包含任何提案,那麼V就由Proposer自己決定。
        • (b) 如果Acceptor收到一個針對編號爲N的提案的Accept請求,只要該Acceptor沒有對編號大於N的Prepare請求做出過響應,它就接受該提案。
    • Zab—— 所有事務請求必須由一個全局唯一的服務器來協調處理,這樣的服務器被稱爲Leader服務器,餘下的服務器則稱爲Follower服務器,Leader服務器負責將一個客戶端事務請求轉化成一個事務Proposal(提議),並將該Proposal分發給集羣中所有的Follower服務器,之後Leader服務器需要等待所有Follower服務器的反饋,一旦超過半數的Follower服務器進行了正確的反饋後,那麼Leader就會再次向所有的Follower服務器分發Commit消息,要求其將前一個Proposal進行提交
    • Raft——一個節點向其他節點發起投票自己的請求,如果得到大多數票則成爲leader;如果得不到最大票數,每個節點隨機休眠一段時間,先醒的節點先發請求,後醒的只能投收到的投票請求。

第一階段 選舉:

從集羣中選出一個合適的節點作爲Leader。

第二階段 日誌同步:

選舉出的Leader接收客戶端請求,將其轉爲raft日誌。

Leader將日誌同步到其他節點,當大多數節點寫入成功後,日誌變爲Committed,一經Committed日誌便不會再被篡改。

Leader故障時,切換到第一階段,重新選舉。在Raft算法中,日誌衝突時以Leader的日誌爲準,Follower刪除不匹配部分。

    • Gossip——在一個有界網絡中,每個節點都隨機地與其他節點通信,經過一番雜亂無章的通信,最終所有節點的狀態都會達成一致。每個節點可能知道所有其他節點,也可能僅知道幾個鄰居節點,只要這些節可以通過網絡連通,最終他們的狀態都是一致的。
    • 二階段提交的算法思路可以概括爲:參與者將操作成敗通知協調者,再由協調者根據所有參與者的反饋情報決定各參與者是否要提交操作還是中止操作。
    • 三階段提交:1、引入超時機制。同時在協調者和參與者中都引入超時機制。2、在第一階段和第二階段中插入一個準備階段。保證了在最後提交階段之前各參與節點的狀態是一致的。
  • 冪等性——WEB資源或API方法的冪等性是指一次和多次請求某一個資源應該具有同樣的副作用。冪等性是系統的接口對外一種承諾(而不是實現), 承諾只要調用接口成功, 外部多次調用對系統的影響是一致的。
    • .MVCC方案——多版本併發控制,該策略主要使用update with condition(更新帶條件來防止)來保證多次外部請求調用對系統的影響是一致的。
    • 去重表——在插入數據的時候,插入去重表,利用數據庫的唯一索引特性,保證唯一的邏輯。
    • 悲觀鎖——select for update,整個執行過程中鎖定該訂單對應的記錄。
    • select + insert——核心高併發流程不要用這種方法。
    • 狀態機冪等——token機制,防止頁面重複提交。採用token加redis(redis單線程的,處理需要排隊)
    • 對外提供接口的api如何保證冪等——如銀聯提供的付款接口:需要接入商戶提交付款請求時附帶:source來源,seq序列號。source+seq在數據庫裏面做唯一索引,防止多次付款,(併發時,只能處理一個請求)
  • 分佈式系統事務
  • 分佈式事務:雙階段、三階段提交協議。事務管理器預通知各個資源管理器,收到所有資源管理器準備好迴應後,再發送提交申請,使所有資源管理器提交。(JTA、Jotm、Automikos、ado.net)。這種方式實現難度不算太高,比較適合傳統的單體應用,在同一個方法中存在跨庫操作的情況。但分佈式事務對性能的影響會比較大,不適合高併發和高性能要求的場景。
  • 提供回滾接口:BFF層來協調調用各個服務,有一個服務不成功不再調用其他服務或回滾其他已執行服務。這種實現方式會造成代碼量龐大,耦合性高。而且非常有侷限性,因爲有很多的業務是無法很簡單的實現回滾的,如果串行的服務很多,回滾的成本實在太高。
  • 本地消息表:源於ebay,其基本的設計思想是將遠程分佈式事務拆分成一系列的本地事務。一個本地事務完成後將狀態等關鍵信息寫入本地數據庫中,通過輪詢數據庫觸發其他事務運行。一種非常經典的實現,基本避免了分佈式事務,實現了“最終一致性”。但是,關係型數據庫的吞吐量和性能方面存在瓶頸,頻繁的讀寫消息會給數據庫造成壓力。所以,在真正的高併發場景下,該方案也會有瓶頸和限制的
  • MQ(非事務消息):將某個事務狀態信息存在MQ中,通知其他事務運行或回滾。這種方式比較常見,性能和吞吐量是優於使用關係型數據庫消息表的方案。如果MQ自身和業務都具有高可用性,理論上是可以滿足大部分的業務場景的。不過在沒有充分測試的情況下,不建議在交易業務中直接使用。
  • MQ(事務消息):RocketMQ第一階段發送Prepared消息時,會拿到消息的地址,第二階段執行本地事物,第三階段通過第一階段拿到的地址去訪問消息,並修改狀態。據筆者的瞭解,各大知名的電商平臺和互聯網公司,幾乎都是採用類似的設計思路來實現“最終一致性”的。這種方式適合的業務場景廣泛,而且比較可靠。不過這種方式技術實現的難度比較大。目前主流的開源MQ(ActiveMQ、RabbitMQ、Kafka)均未實現對事務消息的支持,所以需二次開發或者新造輪子。比較遺憾的是,RocketMQ事務消息部分的代碼也並未開源,需要自己去實現。
  • 多次發送確認消息:多次訪問冪等接口,打到最終一致性。
  • DTS

全局唯一ID

  • Twitter 方案(Snowflake 算法):41位時間戳+10位機器標識(比如IP,服務器名稱等)+12位序列號(本地計數器)
  • Flicker 方案:MySQL自增ID + "REPLACE INTO XXX:SELECT LAST_INSERT_ID();"
  • UUID:缺點,無序,字符串過長,佔用空間,影響檢索性能。
  • MongoDB 方案:利用 ObjectId。缺點:不能自增。
  • TDDL 在分佈式下的SEQUENCE:在數據庫中創建 sequence 表,用於記錄,當前已被佔用的id最大值。每臺客戶端主機取一個id區間(比如 1000~2000)緩存在本地,並更新 sequence 表中的id最大值記錄。客戶端主機之間取不同的id區間,用完再取,使用樂觀鎖機制控制併發。

分佈式鎖

實現分佈式鎖有以下幾個方式:

  • MySQL
  • ZK(Curator)
  • Redis(Redission)
  • 自研分佈式鎖:如谷歌的 Chubby。

mysql 實現方式是新建一個鎖表,利用悲觀鎖或樂觀鎖進行訪問,沒有就插入新數據,可重入鎖就加1,獲取鎖阻塞通過死循環實現,鎖超時通過比較超時時間來實現。解鎖的話就把相應記錄刪除。通過事務保證其原子性。

ZK的實現方式是新建一個有序臨時節點。如果順序爲最小,則獲得鎖,未獲得鎖的線程對節點註冊監聽,當鎖釋放即節點連接斷開,嘗試再次獲取鎖。Curator 封裝了 ZooKeeper 底層的 API,使我們更加容易方便的對 ZooKeeper 進行操作,並且它封裝了分佈式鎖的功能。InterProcessMutex 是 Curator 實現的可重入鎖。Curator 提供了讀寫鎖,其實現類是 InterProcessReadWriteLock。

Redis 分佈式鎖

對於某個資源加鎖我們只需要:setNx(set if not exist) 

setNx resourceName value

加鎖了之後如果機器宕機那麼這個鎖就不會得到釋放所以會加入過期時間,加入過期時間需要和 setNx 同一個原子操作。

Redis 2.8 之後 Redis 支持 nx 和 ex 操作是同一原子操作。

set resourceName value ex 5 nx

Redission 也是 Redis 的客戶端,相比於 Jedis 功能簡單。Jedis 簡單使用阻塞的 I/O 和 Redis 交互,Redission 通過 Netty 支持非阻塞 I/O。

Redission 封裝了鎖的實現,其繼承了 java.util.concurrent.locks.Lock 的接口,讓我們像操作我們的本地 Lock 一樣去操作 Redission 的 Lock。

 

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