第一章 分布式架構
1.1 從集中式到分布式
集中式的特點:
部署結構簡單(因為基於底層性能卓越的大型主機,不需考慮對服務多個節點的部署,也就不用考慮多個節點之間分布式協調問題)
分布式系統是一個硬件或軟件組件分布在不同的網絡計算機上,彼此之間僅僅通過消息傳遞進行通信和協調的系統。
分布式的特點:
- 分布性:在空間隨意分布
- 對等性:沒有主從之分,都是對等的
- 並發性
- 缺乏全局時鍾:很難定義兩個事件誰先誰后
- 故障總是會發生
分布式環境的各種問題:
- 通信異常:主要是因為網絡本身的不可靠性
- 網絡分區:當網絡發生異常時,導致部分節點之間的網絡延時不斷增大,最終導致部分節點可以通信,而另一部分節點不能。
- 三態(成功、失敗與超時)
- 節點故障:組成分布式系統的服務器節點出現宕機或“僵死”現象
1.2 從ACID到CAP/BASE
事務是由一系列對系統中數據進行訪問與更新的操作所組成的一個程序執行邏輯單元,狹義上的事務特指數據庫事務。
事務有四個特性,分別是原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability),簡稱為事務的ACID特性。
分布式事務是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於分布式系統的不同節點之上。也可以被定義為一種嵌套型的事務,同時也具有了ACID事務特性。
CAP理論:一個分布式系統不可能同時滿足一致性、可用性和分區容錯性。
- 一致性:數據在多個副本之間是否能夠保持一致的特性。
- 可用性:系統提供的服務必須一致處於可用的狀態,對於用戶的每一個操作請求總是能夠在有限的時間內返回結果。
- 分區容錯性:分布式系統在遇到任何網絡分區故障的時候,仍然需要能夠保證對外提供滿足一致性和可用性的服務,除非是整個網絡環境都發生了故障。
從CAP定理看出,一個分布式系統不可能同時滿足一致性、可用性和分區容錯性這三個需求。對於一個分布式系統,分區容錯性是一個最基本的需求。
BASE理論:是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)三個短語的簡寫。核心思想是即使無法做到強一致性,但每個應用都可以根據自身的業務特點,采用適當的方式來使系統達到最終一致性。
- 基本可用:分布式系統在出現不可預知故障的時候,允許損失部分可用性。
- 弱狀態:也稱軟狀態,允許系統在不同節點的數據副本之間進行數據同步存在延時。
- 最終一致性:系統中所有的數據副本,在經過一段時間的同步后,最終能夠達到一個一致的狀態。
總得來說BASE理論面向的是大型高可用可擴展的分布式系統,完全不同於ACID的強一致性模型,而是通過犧牲強一致性來獲取可用性,並允許數據在一段時間內是不一致的,但最終達到一致狀態。
實際分布式場景中,ACID特性和BASE理論會結合在一起。
第二章 一致性協議
2.1 2PC與3PC
協調者:用來統一調度所有分布式節點的執行邏輯
參與者:被調度的分布式節點
2PC:
二階段提交(Two-phaseCommit)是指,在計算機網絡以及數據庫領域內,為了使基於分布式系統架構下的所有節點在進行事務提交時保持原子性和一致性而設計的一種算法。
通常,二階段提交也被稱為是一種一致性協議。
大部分關系型數據庫都是采用二階段提交協議來完成分布式事務處理的。
協調者節點向所有參與者節點詢問是否可以執行提交操作(vote),並開始等待各參與者節點的響應。
參與者節點執行詢問發起為止的所有事務操作,並將Undo信息和Redo信息寫入日志。
各參與者節點響應協調者節點發起的詢問。如果參與者節點的事務操作實際執行成功,則它返回一個“YES”消息;如果參與者節點的事務操作實際執行失敗,則它返回一個"NO”消息。
協調者節點向所有參與者節點發出”正式提交(commit)”的請求。
參與者節點接受到COMMIT請求后正式完成操作,並釋放在整個事務期間內占用的資源。
參與者節點向協調者節點發送"ACK”消息。
協調者節點受到所有參與者節點反饋的"ACK”消息后,完成事務。
協調者節點向所有參與者節點發出”回滾操作(rollback)”的請求。
參與者節點利用之前寫入的Undo信息執行回滾,並釋放在整個事務期間內占用的資源。
參與者節點向協調者節點發送"ACK”消息。
協調者節點受到所有參與者節點反饋的"ACK”消息后,完成事務中斷。
同步阻塞問題:執行過程中,所有參與節點都是事務阻塞型的。當參與者占有公共資源時,其他第三方節點訪問公共資源不得不處於阻塞狀態。
單點故障:由於協調者的重要性,一旦協調者發生故障。參與者會一直阻塞下去。尤其在第二階段,協調者發生故障,那么所有的參與者還都處於鎖定事務資源的狀態中,而無法繼續完成事務操作。
數據不一致:在二階段提交的階段二中,當協調者向參與者發送commit請求之后,發生了局部網絡異常或者在發送commit請求過程中協調者發生了故障,這回導致只有一部分參與者接受到了commit請求。而在這部分參與者接到commit請求之后就會執行commit操作。但是其他部分未接到commit請求的機器則無法執行事務提交。於是整個分布式系統便出現了數據部一致性的現象。
太過保守:二階段提交缺乏較為完善的容錯機制,任意一個節點的失敗導致整個事務的失敗。
3PC:
階段一:CanCommit階段
協調者向參與者發送CanCommit請求。詢問是否可以執行事務提交操作。然后開始等待參與者的響應。
參與者接到CanCommit請求之后,正常情況下,如果其自身認為可以順利執行事務,則返回Yes響應,並進入預備狀態。否則反饋No
階段二:PreCommit階段
有以下兩種可能:
假如協調者從所有的參與者獲得的反饋都是Yes響應,那么就會執行事務的預執行:
發送預提交請求協調者向參與者發送PreCommit請求,並進入Prepared階段。
事務預提交參與者接收到PreCommit請求后,會執行事務操作,並將undo和redo信息記錄到事務日志中。
響應反饋如果參與者成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。
假如有任何一個參與者向協調者發送了No響應,或者等待超時之后,協調者都沒有接到參與者的響應,那么就執行事務的中斷:
發送中斷請求協調者向所有參與者發送abort請求。
中斷事務參與者收到來自協調者的abort請求之后(或超時之后,仍未收到協調者的請求),執行事務的中斷。
階段三:doCommit階段
分為以下兩種情況:
執行提交
發送提交請求協調接收到參與者發送的ACK響應,那么他將從預提交狀態進入到提交狀態。並向所有參與者發送doCommit請求。
事務提交參與者接收到doCommit請求之后,執行正式的事務提交。並在完成事務提交之后釋放所有事務資源。
響應反饋事務提交完之后,向協調者發送Ack響應。
完成事務協調者接收到所有參與者的ack響應之后,完成事務。
協調者沒有接收到參與者發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應超時),那么就會執行中斷事務。
發送中斷請求協調者向所有參與者發送abort請求
事務回滾參與者接收到abort請求之后,利用其在階段二記錄的undo信息來執行事務的回滾操作,並在完成回滾之后釋放所有的事務資源。
反饋結果參與者完成事務回滾之后,向協調者發送ACK消息
中斷事務協調者接收到參與者反饋的ACK消息之后,執行事務的中斷。
階段三,可能會出現:
協調者出現問題,協調者和參與者網絡出現故障。
無論哪種異常都會導致參與者無法及時接收到來自協調者的doCommit或者rebort請求時,參與者會在等待超時之后,會繼續進行事務的提交。
優缺點:
優點:3PC主要解決的單點故障問題,並減少阻塞范圍,因為一旦參與者無法及時收到來自協調者的信息之后,他會默認執行commit。而不會一直持有事務資源並處於阻塞狀態。
缺點:引入新問題,如果出現網絡分區,協調者發送的abort響應沒有及時被參與者接收到,那么參與者在等待超時之后執行了commit操作。這樣就和其他接到abort命令並執行回滾的參與者之間存在數據不一致的情況。
2.2 Paxos算法
算法中的參與者主要分為三個角色,同時每個參與者又可兼領多個角色:
proposer 提出提案,提案信息包括提案編號和提議的value;
acceptor 收到提案后可以接受(accept)提案;
learner 只能"學習"被批准的提案;
階段一:
Proposer選擇一個提案編號Mn,向Acceptor的某個超過半數的子集成員發送編號為Mn的Prepare請求
如果一個Acceptor收到一個Mn的請求,且Mn大於它已經響應的所有請求的編號,它會將它已批准過的最大編號的提案作為響應反饋給Proposer,同時它承諾不會再批准任何編號小於Mn的提案
比如一個Acceptor已經響應過的提案編號分別為1,2,7,那么它在收到一個編號為8的Prepare請求后,會將編號為7的提案反饋給Proposer
階段二 :
如果Proposer收到半數以上的Acceptor的響應,它會發一個針對[Mn,Vn]提案的Accept請求給Acceptor。Vn是收到響應中編號最大提案的值,如果響應中不包含任何提案,那么它就是任意值
如果Acceptor收到這個針對[Mn,Vn]提案的Accept請求,只要它尚未對編號大於Mn的Prepare請求做出響應,它就可以通過這個提案
提案獲取:
方案一:一旦 Acceptor批准了一個提案,就發送給所有Learner
方案二:批准的提案先發給主Learner,再由其同步給其他Learner。(主Learner可能出現故障)
方案三:批准的提案先發給一個特定的Learner集合,再由集合里的Learner通知其他Learner。
活鎖問題:
P1提出M1提案,並通過階段一,同時P2提出M2提案,並通過階段一,P1進入階段二,會被忽略
然后P1提出M3,P2提出M4,以此類推,則會陷入死循環
解決辦法:選擇一個主Proposer,只有主Proposer才能提出提案
小結:
二階段提交協議解決了分布式事務的原子性問題
三階段提交協議避免了2PC中的無限期等待問題
Paxos算法引入少數服從多數原則,支持角色之間的輪換,避免了單點故障,無限期等待,網絡分區問題,是最優秀的分布式一致性協議之一。
第四章 ZooKeeper與Paxos
ZooKeeper沒有采用Paxos算法,而是采用了一種被稱為ZAB的一致性協議。
4.1 初識ZooKeeper
ZooKeeper是一個分布式協調服務。
設計目標是將那些復雜的分布式一致性服務封裝起來,以接口的形式提供給用戶使用。
ZooKeeper是一個分布式數據一致性的解決方案,分布式應用程序可以基於它實現諸如數據發布/訂閱,分布式鎖,Master選舉等功能。
ZooKeeper作為一個分布式協調框架,主要用於解決分布式數據一致性的問題。
ZooKeeper可以保證如下分布式一致性特性:
順序一致性:同一客戶端發起的事務請求,會嚴格地按照其發起的順序送到ZooKeeper
原子性:要么所有節點都成功執行了事務,要么全都沒有執行
單一視圖:無論客戶端連接的是哪個ZooKeeper服務器,看到的數據模型都是一樣的
可靠性:事務引起的服務端狀態變更會被一直保留下來
實時性:ZooKeeper僅僅保證一定時間內,客戶端能讀到最新數據
設計目標:
ZooKeeper致力於提供一個高性能,高可用,有嚴格順序訪問控制能力的分布式協調服務。
簡單的數據模型:使得分布式程序能通過一個共享的,樹形結構的名字空間來相互協調。其數據模型類似於一個文件系統。
可以構建集群:集群的每台機器都會在內存中維護服務器狀態,並且每台機器互相保持通信,只要有半數以上的機器正常工作,就能正常對外服務。
順序訪問:每個更新請求,都有有一個全局唯一的遞增編號。
高性能:ZooKeeper將全量數據存儲在內存中,適用於以讀為主的應用場景。
ZooKeeper的基本概念:
集群角色:Leader:提供讀和寫。Follower和Observer都能提供讀服務。
區別:Observer不參與Leader選舉,也不參與寫操作的“過半寫成功”策略。主要用於提升讀性能。
客戶端連接:客戶端與服務器之間的一個TCP長連接。
會話:會話周期從第一次建立連接開始,客戶端能通過心跳檢測與服務器保持有效會話。
4.2 ZooKeeper的ZAB協議
ZooKeeper並沒有完全采用Paxos算法,而是采用一種稱為ZooKeeper Atomic Broadcast(ZAB 原子消息廣播協議)作為其數據一致性的核心算法。
ZAB是一種支持崩潰恢復的原子廣播協議。
ZooKeeper使用一個單一主進程來處理所有事物請求,並采用ZAB,將服務器數據的狀態變更廣播到所有副本進程上。
協議介紹:
ZAB包括兩種基本模式,分別是崩潰恢復和消息廣播。
當Leader服務器出現網絡中斷或者崩潰,ZAB協議會進入恢復模式並選舉新的Leader。
當集群中過半的Follower完成了Leader的狀態同步,整個服務框架就可以進入消息廣播模式了。
如果非Leader節點收到客戶端的事務請求,會轉發給Leader。
消息廣播:
在ZAB的二階段提交過程中,移除了中斷邏輯,意味着可以在過半節點反饋ACK之后提交事務。
需要崩潰恢復模式來解決Leader崩潰帶來的數據不一致問題。
在消息廣播的過程中,Leader會為每一個事務Proposal分配一個全局遞增唯一的ID。
每一個Follower在接收到這個事務的Proposal之后,會將其以事務日志的形式寫入到磁盤中去,並向Leader反饋ACK。
當Leader收到半數節點的ACK時,會廣播一個commit消息通知其他節點進行事務提交,同時自己也完成事務提交。
崩潰恢復:
Leader選舉算法應該保證:已經在Leader上提交的事務最終也被其他節點都提交,即使出現了Leader掛掉,Commit消息沒發出去這種情況。
確保丟棄只在Leader上被提出的事務。Leader提出一個事務后掛了,集群中別的節點都沒收到,當掛掉的節點恢復后,要確保丟棄那個事務。
讓Leader選舉算法能夠保證新選舉出來的Leader擁有最大的事務ID的Proposal。
數據同步:
在完成Leader選舉后,Leader會首先確認事務日志中所有Proposal是否都被集群中過半的節點提交了,即是否完成數據同步。
事務ID:
是一個64位數,低32位是一個單調遞增的計數器,每一個新的Proposal產生,該計數器都會+1.
高32位代表Leader周期epoch編號,每當選舉出新Leader,會取得這個Leader的最大事務ID,對其高32位+1.低32位清零。
ZAB通過epoch來區分Leader周期變化的策略,簡化和提升了數據恢復的流程。
Leader和Follower通過心跳檢測來感知彼此的情況。
如果Leader在指定時間內無法從過半的Follower那收到心跳檢測,或者是TCP連接本身斷開了,會重新選舉Leader。
心跳是從follower向leader發送心跳。
ZAB主要用於構建一個高可用的分布式數據主備系統。
Paxos算法是用於構建一個分布式的一致性狀態機系統。
第六章 ZooKeeper的典型應用場景
數據發布/訂閱:
兩種模式:推模式和拉模式。
推模式:服務端主動將數據更新發送給所有訂閱的客戶端。
拉模式:客戶端主動發起請求來獲取最新數據。
ZooKeeper采用推拉結合的方式:客戶端向服務端注冊自己需要關注的節點,一旦節點的數據發生變更,那么服務端就會向相應的客戶端發送Watcher事件通知。客戶端收到這個通知之后,需要主動到服務端獲取最新的數據。
心跳檢測:
可以讓不同的機器都在ZooKeeper的一個指定節點下創建臨時子節點。不同機器之間可以根據這個臨時節點來判斷對應的客戶端機器是否存活。
Master選舉:
利用ZooKeeper的強一致性,能夠保證在分布式高並發環境下節點的創建是全局唯一的。
選擇一個根節點,例如/master_select,多台機器同時向該節點創建一個子節點/master_select/lock,利用ZooKeeper的特性,最終只有一台機器能夠創建成功,成功的那台就是master。
同時,其他沒能成功創建節點的客戶端,都會在根節點上注冊一個子節點變更的Watcher,用於監控當前master是否存活。
分布式鎖:
分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。
排他鎖:
定義鎖:通過ZooKeeper上的一個數據節點來表示一個鎖,例如/exclusive_lock/lock節點就可以被定義為一個鎖。
獲取鎖:在需要獲取鎖時,所有客戶端都會試圖調用create()接口,在/exclusive_lock節點下創建臨時節點/exclusive_lock/lock。
最終只有一個客戶端能成功,就可以認為該客戶端獲取了鎖。其他沒獲取鎖的客戶端會在/exclusive_lock節點上注冊監聽,實時監聽lock節點的變更情況。
釋放鎖:獲取鎖的客戶端宕機了,臨時節點會被移除。
正常執行完業務邏輯后,客戶端會主動刪除臨時節點。
共享鎖:
定義鎖:通過ZooKeeper上的一個數據節點來表示一個鎖,例如/shared_lock/host1-R-000001
獲取鎖:在需要獲取鎖時,所有客戶端都會到/shared_lock下創建臨時順序節點。
如果是讀請求,就創建例如/shared_lock/host1-R-000001的節點
如果是寫請求,就創建例如/shared_lock/host2-W-000002的節點
寫操作必須在當前沒有任何事務進行讀寫操作的情況下進行。
對於讀請求,如果比自己序號小的節點中有寫請求,就進入等待。
對於寫請求,如果自己不是序號最小的子序號,就進入等待。
釋放鎖:和排他鎖一樣。
羊群效應:
客戶端無端收到過多和自己不相關的事件通知。
每個節點應該只需要關注比自己小的那個節點的變更。而不需要關心全局。
改進(當集群規模較大時適用):
讀請求:向比自己序號小的最后一個寫請求節點注冊監聽。
寫請求:向比自己序號小的節點注冊監聽