一次學夠分佈式:CAP理論,分佈式事務,分佈式鎖,分佈式ID,服務限流等。滿滿乾貨!!!

本文章爲你收集了分佈式的基礎理論,羅列了常見的分佈式應用場景的實現方案:分佈式鎖,分佈式事務,分佈式主鍵ID(美團的Leaf),服務限流算法,一致性hash算法。同時每一部份內容筆者都詳細的進行了收集整理並分析了每一種方案的優缺點。筆者但願該文章可以成爲網速比較全面的彙總文章,能爲讀者帶來比較系統的講解。若是讀者發現文章內還有收集不全以及錯誤的地方還請在評論區留言,筆者會盡快完善文章內容。謝謝!

CAP理論:

  • C(一致性) 若是在某個節點更新了數據,那麼在其餘節點若是都能讀取到這個最新的數據,那麼就稱爲強一致,若是有某個節點沒有讀取到,那就是分佈式不一致。即全部的節點同時看到相同的數據
  • A (可用性):非故障的節點在合理的時間內返回合理的響應。任什麼時候候,讀寫都是成功的。
  • P (分區容錯性):當部分節點出現消息丟失或故障的時候,分佈式系統仍能正常工做。

CAP理論認爲:一個分佈式系統最多隻能同時知足,一致性,可用性,分區容錯性的三項中的兩項。因爲分區容錯性是必然存在的,因此大部分分佈式軟件系統都在CP和AP中作取捨redis

  • Zookeeper 採用CP一致性,強調一致性,弱化可用性。
  • Eureka 採用AP可用性,強調可用性,弱化一致性。

Base理論

Base理論:即基本可用(Basically Available),軟狀態(Soft State),最終一致性(Eventually Consistent)。既然沒法作到強一致性,那麼不一樣的應用可用根據本身的業務特色,採用適當的方式來達到最終一致性。Base理論是對CAP理論的實際應用算法

  • 基本可用性:不追求強可用性,並且強調系統基本可以一直運行對外提供服務,當分佈式系統遇到不可預估的故障時,容許必定程度上的不可用,好比:對請求進行限流排隊,使得部分用戶響應時間變長,或對非核心服務進行降級。
  • 軟狀態:對於數據庫中事務的原子性:要麼所有成功,要不所有不成功。軟狀態容許系統中的數據存在中間狀態。
  • 最終一致性:數據不可能一直都是軟狀態,必須在一個時間期限以後達到各個節點的一致性。在此以後,全部的節點的數據都是一致的。系統達到最終一致性。

分佈式一致性算法

WARO:Write All Read One

一種簡單的副本控制協議,當客戶端向一個分佈式應用發送寫請求的時候,只有當全部的副本節點都更新成功以後,此次寫操做纔算成功。不然視爲失敗。這下降了寫操做的可用性,提升了讀操做的可用性。sql

Quorm:最終一致性

假設有N個副本,客戶端向一個分佈式應用發送寫請求的時候,若是有W個副本更新成功以後,此次寫操做纔算成功。則讀操做最多須要讀N-W個副本就能讀取到最新的結果。
Quorm沒法保證強一致性,它是分佈式系統中經常使用的一種機制,用來保證數據冗餘的最終一致性的投票算法。Kafka的ISR機制有點相似該機制。數據庫

Paxos算法:分佈式一致性算法

在Paxos協議中,一共有三類角色節點segmentfault

  • Proposer 提案者
    提案者能夠有多個,在流程開始時,提案者提出操做(被稱爲value)(好比修改某個變量的值),多個提案者能夠提出不一樣的操做。但通過一輪的Paxos算法後,只有一個提案者的操做被運行執行。
  • Acceptor 批准者
    在集羣中,批准者有多個(數量設爲N)。批准者之間徹底獨立對等。提案者提出的操做,必須得到半數以上(N/2+1)的批准者批准後才能經過執行
  • Learner 學習者
    學習者不參與選舉,而是執行被批准者批准的操做。

分佈式事務:

分佈式事務解決方案有 兩階段提交協議,三階段提交協議,TCC分段提交,基於消息隊列的最終一致性緩存

兩階段提交協議(2PC)

  • 兩階段提交系統中,存在一個節點做爲協調者,其餘節點爲參與者。
  • 全部的節點都採用預寫式日誌。日誌記錄不會丟失。
  • 全部的節點不會永久性的宕機,即便宕機後仍能夠恢復。
  1. 第一階段:事務管理器要求每一個涉及到事務的數據庫預提交此操做,並反映是否能夠提交.
  2. 第二階段:根據第一階段的反饋,事務協調器要求每一個數據庫提交數據,或者回滾數據。

缺點安全

  1. 事務管理器爲單點,故障之後整個數據庫集羣沒法使用
  2. 在執行過程當中,全部參與事務的節點都是事務獨佔狀態,當有參與者佔用公共資源時,那麼其餘第三方節點對公共資源的訪問會被阻塞。
  3. 第二階段中,假設協調者發出了事務commit的通知僅被一部分參與者所收到並執行,其他的參與者則由於沒有收到通知一直處於阻塞狀態,這時候就產生了數據的不一致性。

三階段提交協議(3PC)

爲解決兩階段提交協議中,公共資源佔用堵塞的問題,三階段提交協議中協調者和參與者都引入了超時機制,而後把兩階段提交協議裏的第一個階段拆分爲兩步:先詢問(CanCommit),再鎖資源(PreCommit),再最後提交(DoCommit)。服務器

  1. CanCommit:協調者向參與者發送Commit請求,參與節點反映是否能夠調節。
  2. PreCommit:根據CanCommit響應狀況有如下兩種執行狀況。網絡

    • 若是全部的參與節點返回Yes,則進行事務的預執行:協調者向參與者發送PreCommit請求,使參與者進入Prepare階段。並向協調者反饋ACk。
    • 若是任意一個節點返回了NO,或者等待超時進進行中斷操做。則協調者向全部的參與者發送abort請求,參與者執行abort請求放棄事務的執行。
  3. DoCommit:階段根據PreCommit的響應也有兩種執行狀況。併發

    • 若是協調者收到全部參與者的ACk響應,則發送doCommit請求,全部的參與者提交事務釋放資源,並向協調者反饋ACK響應。
    • 若是協調者沒有到全部參與者的ACK響應,則會執行中斷事務

缺點:在DoCommit階段中,假設協調者發出了事務commit的通知僅被一部分參與者所收到並執行,其他的參與者則由於沒有收到通知一直處於阻塞狀態,這時候就產生了數據的不一致性。

TCC

TCC:TCC是支付寶提出的分佈式事務解決方案,每一個分佈式事務的參與者都須要實現3個接口:try、confirm、cancel。

  1. Try階段:調用方調用各個服務的 try 接口,各個服務執行資源檢查和鎖定,看本身是否有能力完成,若是容許,則鎖定資源.
  2. Confirm階段:各個服務的 try 接口都返回了 yes,則進入提交階段,調用方調用各個服務的 confirm 接口,各個服務對 try 階段鎖定的資源進行處理。若是 try 階段有一方返回了 no,或者超時,調用方調用各個服務的 cancel 接口,釋放鎖定的資源
  3. Cancel階段:取消執行,釋放Try階段預留的業務資源
  • Confirm階段和Cancel階段是互斥的,只能進行一個,並且都是冪等性的,容許失敗重試。

優勢

1. TCC解決了跨服務的業務操做原子性問題,可讓應用本身定義數據庫操做的粒度,下降鎖衝突,提升系統的業務吞吐量。
2. TCC的每一階段都由業務本身控制,避免了長事務,提升了性能。

缺點

1. 業務侵入性強:業務邏輯必須都要實現Try,Confirm,Cancel三個操做

異常狀況

  • 空回滾
    現象是 try 沒被執行,就調用了 cancel:調用 try 時出現異常,try 接口實際沒有被調用,天然沒有返回 yes,那麼會按照正常流程進入第2階段,調用 cancel 接口,這就造成了空回滾。
    解決方法:讓 cancel 可以識別出這是一個空回滾,能夠記錄事務執行狀態,cancel 中判斷 try 是否執行了。

  • 重複調用
    提交階段異常時可能會重複調用 confirm 和 cancel,因此要實現冪等,保證屢次執行效果一致。

解決方法:記錄事務執行狀態,若是執行過了,就再也不執行。

接口冪等性:指的是在調用方屢次調用的狀況下,接口最終獲得的數據是一致的。查詢接口具備自然的冪等性。
  • 懸掛
    現象是先執行了 cancel,後執行的 try,形成資源沒人釋放:調用 try 時網絡擁堵超時,被認爲失敗,而後調用 cancel,這時事務至關於結束了,但後來網絡好點以後 try 開始執行了,鎖定了相關資源,由於事務已經結束,confirm、cancel 都不會再調用了,就形成資源懸掛,沒法釋放。
    解決方法:仍是記錄事務執行狀態,try 執行時判斷 cancel 是否執行了。

MySql內部的XA事務規範

XA事務是基於兩階段提交協議的,XA規範主要定義了事務協調者和資源管理器之間的接口。

  1. 事務協調者:用來保證全部的事務參與者都完成了準備工做。若是事務協調者收到全部參與者都準備好的消息,就會通知全部的事務能夠提交。
  2. 資源管理器:負責控制和管理實際資源。

XA事務執行流程與兩階段提交協議差很少。

  1. Prepare階段: 事務管理者向全部的資源管理器發送prepare指令,管理器收到指定後執行數據操做和日誌記錄。而後反饋結果。
  2. Commit階段:事務協調者接收到全部的資源管理器的結果,選擇執行RollBack命令或者Commit命令。完成一次事務操做。

MySQL中的XA事務有兩種狀況,內部XA和外部XA。若是事務發生在MySQL服務器單機上使用內部XA,若是事務發生在多個外部節點上,使用外部XA。

內部XA: Mysql會同時維護binlog日誌與InnoDB的redolog,爲了保證兩個日誌一致性,MySql使用了XA事務。當有事務提交時:

1. 第一步:InnoDB進入Prepare階段,將事務的XID寫入redo日誌。binlog不作任何操做。
2. 第二步:進行寫binlog日誌,也將XID寫入binlog。
3. 第三部:調用InnoDB引擎的Commit完成事務的提交。而後將Commit信息寫入redo日誌。

分佈式鎖實現方案彙總

基於數據庫的主鍵實現方案

得到鎖:當要鎖住某一個資源時,就在表中插入對應的一條記錄。
釋放鎖:刪除對應的記錄。
基於數據庫實現分佈式鎖的方案實現簡單,但有不少的問題存在。

  1. 存在單點故障:一旦數據庫掛掉,整個業務系統不可用。能夠配置主從,防止單點故障。
  2. 超時問題:若是解鎖操做失敗,會致使鎖一直在數據庫中,其餘線程沒法得到鎖。能夠添加獨立的定時任務,經過時間戳等方式刪除超時數據。
  3. 不可重入:同一個線程在沒有釋放鎖以前不能再次得到該鎖。實現可重入須要改造加鎖方法,增長存儲和判斷線程信息。
  4. 阻塞等待問題:其餘線程在請求對應的資源時,插入數據失敗,會直接返回,不會阻塞線程。故線程內要作循環插入判斷,對數據庫操做較大的資源浪費。
  5. 主從問題:在高併發的場景下,數據庫主從延時增大,線程讀取的數據非最新版,致使鎖重複。

基於ZooKeeper的實現方案

利用Zookeeper支持的臨時順序節點的特性,能夠實現分佈式鎖。

獨佔鎖-使用臨時節點實現

得到鎖: 當要對某個資源加鎖時,Zookeeper在該資源對應的指定的節點目錄下,生成一個惟一的臨時節點。其餘客戶端對該節點設置一個Watcher通知。
釋放鎖:Zookeeper刪除對應的臨時節點,其餘客戶端能夠監聽到節點被刪除的通知,並從新競爭鎖。

讀寫鎖-使用臨時有序節點實現

得到讀鎖:

  1. 得到臨時有序節點,並標記爲讀鎖
  2. 獲取資源路徑下全部的子節點,從小到大排序。
  3. 獲取當前臨近節點前的臨近寫鎖節點。
  4. 若是不存在臨近寫鎖節點,則成功得到讀鎖
  5. 若是存在臨近寫鎖節點,則設置Water監聽該節點的刪除事件。
  6. 一旦監聽到刪除事件,重複2,3,4,5的步驟。

得到寫鎖

  1. 建立臨時有序節點,並標記爲寫鎖。
  2. 獲取路徑下的全部子節點,並進行從小到大排序。
  3. 獲取當前節點的臨近的寫鎖節點和讀鎖節點。
  4. 若是不存在臨近節點,則成功獲取鎖。
  5. 若是存在臨近節點,對其進行監聽刪除事件。
  6. 一旦監聽到刪除事件,重複2,3,4,5的步驟(遞歸)。

釋放鎖
刪除對應的臨時節點。

基於Redis的實現方案

原理:在獲取鎖以前,先查詢一下以該鎖爲key對應的value是否存在,若存在,說明該鎖被其餘客戶端獲取了。

改進1:爲了防止獲取鎖的客戶端忽然宕機,須要在設置key的時候,指定一個過時時間,以確保即便宕機了,鎖也能最後釋放。經過SETNX命令設置key的值,經過EXPIRE命令設置過時時間。

改進2:因爲SETNX和EXPIRE命令的執行不是原子性的,多個客戶端在檢驗鎖是否存在時會致使多個客戶端都認爲本身能獲取到鎖。Redis提供了
Set原子性命令,在設置值的同時指定過時時間。

改進3:客戶端獲取鎖之後任務未執行完,但鎖已通過期,被別的客戶端獲取了,這時客戶端扔會釋放鎖,致使鎖失效。能夠在設置key的時候,設置value爲一個隨機值r,刪除的時候先比較一下redis裏的value是否爲r再決定刪除。

改進4:客戶端先比較一下redis的value是否爲r再決定刪除,但因爲比較後再刪除鎖不是原子的。在比較過程當中,key有可能因過時而被清除了致使一個客戶端就能夠獲取到key。Redis並無提供相關的比較後刪除的原子操做。此時釋放鎖的過程可使用lua腳本,redis將lua腳本的命令視爲一個原子操做。

分佈式惟一ID生成系列

UUID:

UID使用以太網卡地址、納秒級時間、芯片ID碼和許多可能的數字來生成一串惟一隨機32位長度數據。
優勢:性能好,本地生成,全局惟一。
缺點

  1. UUID長度固定爲32位,對於Mysql索引來講,全部的非主鍵索引都會包含一個主鍵,UUID長度過長會不利於MySql的存儲和性能。
  2. UUID是亂序的,每一次UUID數據的插入都會對主鍵地城的b+樹進行很大的修改。
  3. 信息不安全,UUID裏包含了MAC地址,芯片ID碼能信息。會形成信息泄露。

數據庫自增ID

對於多臺數據庫,經過每臺數據庫的起始值增值和自增值的跨度,能夠實現全局的自增ID。以4臺數據庫爲例,以下表

數據庫編號 起始值增值 自增值的跨度 生成的主鍵序列
1 1 4 [1,5,9,13,17,21,25,29.....]
2 2 4 [2,6,10,14,18,22,26,30....]
3 3 4 [3,7,11,15,19,23,27,31....]
4 4 4 [4,8,12,16,20,24,28,32....]

優勢:容易存儲,能夠直接用數據庫存儲。
缺點

  1. 統水平擴展比較困難,定義好步長和機器臺數以後,再增長數據庫須要重調整全部的數據庫起始值增值和自增值的跨度。
  2. 數據庫壓力大,每次獲取ID都會寫一次數據庫。
  3. 信息不安全,遞增性太強。很容根據兩個ID的差值判斷競爭對手的中間的出單量。

Snowflake

snowflake生成id的結果是一個64bit大小的整數。由一位標識位,41個比特位的時間戳,10位的機器位,能夠標識1024臺機器,還有就是10比特位的自增序列化組成。結構以下:

image

優勢:趨勢遞增,不依賴第三方組件(數據庫等),性能更高,能夠根據自身業務特色動態分配bit位。
缺點:強依賴機器時鐘,若是出現時鐘回撥,那麼整個系統生成的ID將會不可用。

美團Leaf

leaf提供了的兩種模式。

Segment模式

Segment模式在以前數據庫方案基礎之上進行了優化。該模式不是每次都獲取ID都操做一次數據庫,而是異步的一次性的從數據庫中取N個ID組成一個號段,而後放入本地緩存。同時採用雙buffer 方法,在第一個號段下發了必定的百分比時,就會有另外一個線程啓動來獲取並更新下一個號段的緩存數據。

優勢

  1. Id單調遞增,經過內部有號段緩存,數據庫掛了依舊可以支持一段時間。

缺點

  1. 號段太短會致使DB宕機容忍時間變短,號段過長會致使ID號跨度過大。能夠根據號段使用狀況動態調整號段跨度。
  2. 最終仍是強依賴DB

Snowflake模式

美團Leaf的Snowflake像較於普通的Snowflake,有兩點改進。

  1. 10位的workID是應用在zookeeper註冊的順序節點的序號。
  2. 時鐘回撥問題:應用會將定時將本身的時間寫入zookeeper中,同時會將本地時間和zookeeper的存儲的時間作比較。若是差值超過設置閾值則認爲是本地服務的時間發生回撥。

服務限流算法:

計數器

從第一個請求進來開始計時,在接下去的1s內,每來一個請求,就把計數加1,若是累加的數字達到了100,那麼後續的請求就會被所有拒絕。等到1s結束後,把計數恢復成0,從新開始計數.可以使用redis的incr原子自增性和線程安全便可輕鬆實現。
若是我在單位時間1ms內的前10ms,已經經過了100個請求,那後面的990ms,請求所有會被拒絕,即:突刺現象。

滑動窗口算法

滑動窗口算法是將時間週期分爲N個小週期,分別記錄每一個小週期內訪問次數,而且根據時間滑動刪除過時的小週期,滑動窗口的格子劃分的越多,那麼滑動窗口的滾動就越平滑,限流的統計就會越精確

漏桶算法

算法內部有一個容器,無論上面流量多大,下面流出的速度始終保持不變。能夠準備一個隊列,用來保存請求,另外經過一個線程池按期從隊列中獲取請求並執行,能夠一次性獲取多個併發執行。

令牌桶算法:

算法中存在一種機制,以必定的速率往桶中放令牌。每次請求調用須要先獲取令牌,只有拿到令牌,纔有機會繼續執行,不然選擇選擇等待可用的令牌、或者直接拒絕。能夠準備一個隊列,用來保存令牌,另外經過一個線程池按期生成令牌放到隊列中,每來一個請求,就從隊列中獲取一個令牌,並繼續執行。guava的RateLimiter能夠簡單生成一個令牌限流器。
集羣限流:每次有相關操做的時候,就向redis服務器發送一個incr命令,好比須要限制某個用戶訪問/index接口的次數,只須要拼接用戶id和接口名生成redis的key,每次該用戶訪問此接口時,只須要對這個key執行incr命令,在這個key帶上過時時間,就能夠實現指定時間的訪問頻率。

一致性Hash算法:

使用Hash算法讓固定的一部分請求落到同一臺服務器上,這樣每臺服務器固定處理一部分請求起到負載均衡的做用。可是普通的hash算法伸縮性不好,當新增或者下線服務器機器時候,用戶id與服務器的映射關係會大量失效。一致性hash則利用hash環對其進行了改進。
一致性hash:將全部的服務器散列值當作一個從0開始的順時針環,而後看請求的hash值落到了hash環的那個地方,在hash環上的位置順時針找距離最近的ip做爲處理服務器

image.png

相關文章
相關標籤/搜索