系統由集中式到分佈式進化。java
通訊異常:因爲網絡的不可靠,致使分佈式系統各個節點之間通信伴隨着網絡不可用風險。mysql
網絡分區:網絡異常致使部分節點之間延時過大,最終致使只有部分節點之間能正常通訊,這種現象就是網絡分區---俗稱「腦裂」。算法
單機系統很容易知足ACID,可是分佈式系統對這些數據進行事務處理則面臨很大挑戰。sql
一致性(C):數據在多個副本之間可以保持一致的特性。數據庫
可用性(A):服務一直處於可用的狀態,對於用戶的每一個操做總能在有限的時間內返回結果。設計模式
分區容錯性(P):分佈式系統在遇到任何網絡分區故障的時候,仍然須要可以保證對外提供知足一致性和可用性的服務,除非是整個網絡環境發生故障。bash
分區容錯性是分佈式系統的基本須要。服務器
BASE是 Basically Avaiable(基本可用)、Soft State(軟狀態)、Eventually consistent(最終一致性)的簡稱。是基於CAP理論演化來的,核心思想是即便沒法作到強一致性,但每一個應用均可以根據自身的業務特色,採用適當的方式使系統達到最終一致性。網絡
基本可用:系統在出現不可預知故障的時候,容許損失部分可用性(響應時間損失、功能上的損失等)。app
軟狀態:容許系統中的數據存在中間狀態,並認爲該中間狀態的存在不會影響系統的總體性,即容許系統在不一樣節點的數據副本之間進行數據同步的過程存在延時。
最終一致性:指系統全部的數據副本,在通過一段時間的同步後,最終都能達到一個一致的狀態。
由數據一致性問題,提出一致性協議與算法。闡述了兩階段提交協議和三階段提交協議,重點講述Paxos一致性算法。
簡單來說,兩階段提交將一個事務的處理過程分爲投票和執行兩個階段,其核心是對每一個事務都採用先嚐試後提交的處理方式。
事務提交與事務中斷過程如圖所示:
原理簡單,實現方便。
同步阻塞,單點問題,腦裂,太過保守。
三階段提交協議將二階段提交協議的「提交事務請求」過程一分爲二,造成了由Cancommit
,Precommit
,Docommit
三個階段。
過程以下:
下降了參與者的阻塞範圍,而且能在單點故障後繼續達成一致。
若是參與者收到了preCommit
消息後出現了網絡分區,此時協調者所在的節點和參與者沒法進行正常的網絡通訊,在這種狀況下,該參與者依然會進行事務的提交,必然會形成數據的不一致。
是一種基於消息傳遞且具備高度容錯特性的一致性算法,是目前公認的解決分佈式一致性問題最有效的算法之一。
參考了一篇文章,寫的仍是挺容易理解的。
Paxos的主要的兩個組件:
Proposer
提議發起者,處理客戶端請求,將客戶端的請求發送到集羣中,以便決定這個值是否能夠被批准。
Acceptor
提議批准者,負責處理接收到的提議,他們的回覆就是一次投票。會存儲一些狀態來決定是否接收一個值。
規則以下:
1 一個Acceptor必須接受它收到的第一個提案。
2 一個提案被選定須要被半數以上的Acceptor接受。
3 若是某個value爲v的提案被選定了,那麼每一個編號更高的被選定提案的value必須也是v。
一個提案被選定要通過第一階段(prepare),若是一半Acceptor贊成提案,Proposer才能第二階段(Accept)。若是一半Acceptor贊成提案,提案才能被選定。
第一階段(prepare)
1)提議者獲取一個提案號(proposal number)n;
2)提議者向全部節點廣播prepare(n)請求;
3)接收者接收消息此時要分幾種狀況處理:
a.若是接收者尚未收到任何提議,記錄minProposal.並返回提議者OK,承認該提案。
b.若是接收者已有minProposal,比較n,和minProposal,若是n>minPno,表示有更新的提案,更新minProposal=n。
c.若是此時已經有一個認定值(accptedValue),返回提議者已肯定的(acceptedProposal,acceptedValue)。
d.若是接收者已有minProposal,比較n,和minProposal,若是n<minProposal.拒絕而且返回minProposal。
4)提議者根據請求返回的響應作處理。只有收到超過一半接收者響應才能發起第二階段請求。一樣也分爲如下幾種狀況:
a.超過一半接收者返回已肯定的提案(acceptedProposal,acceptedValue),表示有承認的提議,保存最高acceptedProposal編號的acceptedValue到本地。
b.超過一半接收者贊成提議者的提案。
c.未獲得一半接收者贊成提案,這種請求跳轉1,對n進行累加,從新prepare。
第二階段(Accept)
5)提議者廣播accept(n,value)到全部提議者節點;
6) 接收者比較n和minProposal,若是n>=minProposal,則acceptedProposal=minProposal=n,acceptedValue=value,本地持久化後,返回; 不然,拒絕而且返回minProposal。
7) 提議者接收到過半數請求後,若是發現有返回值>n,表示有更新的提議,跳轉1(從新發起提議);不然value達成一致,提案被選定
如下經過實例說明:
示例一:
P1發送提議【1,a】至A1,A2 , A1,A2由於此時是第一個提議,因此接收提議,返回OK。由於P1收到一半Acceptor的響應,因此發送第二階段請求。
A1,A2 比較本地的minProposal 發現n=minProposal 選定【1,a】並返回給P1,P1發現有一半接受者贊成提案,選定【1,a】。 P2發送提案【2,b】給A1,A2 由於此時有選定值【1,a】,返回【1,a】,P2發現有一半接收者返回選定值,更新本地提案。示例二:
P1發送提議【1,a】至A1,A2 , A1,A2由於此時是第一個提議,因此接收提議,返回OK。
P2發送提議【2,b】至A1,A2 , 由於【2,b】大, A1,A2更新本地minProposal 返回OK。
由於一半接收者贊成P1的提案,因此發送accept請求【1,a】,A1,A2比較本地minProposal 發現本地minProposal爲【2,b】。因此拒絕提案,並返回【2,b】給P1,表示有更新提案。
P1更新提案【3,a】繼續第一階段prepare請求。
可是,若是此時P2在進行accept請求【2,b】發現又有更新請求。 如此反覆就會出現活鎖的狀況,解決的方法就是提出主Proposer,並規定只有主Proposer能夠提出議案。
這樣,只要主Proposer和過半的Acceptor可以正常進行網絡通訊,但凡主Proposer提出一個編號更高的提案,該提案終將會被批准。整套Paxos算法流程就能保持活性。
本章主要經過對Google Chubby 和 Hypertable這兩款經典的分佈式產品中的Paxos算法應用的介紹,闡述了Paxos算法在實際工業實踐的應用。在此,不過多贅述。
本章首先對Zookeeper進行一個總體上的介紹,包括設計目標、由來及基本概念,重點介紹Zookeeper中的ZAB一致性協議。
是一個典型的分佈式數據一致性的解決方案,分佈式應用程序能夠基於它實現諸如數據發佈/訂閱,負載均衡,命名服務,分佈式協調/通知,集羣管理,master選舉,分佈式鎖和分佈式隊列等功能。
Zookeeper能夠保證以下分佈式一致性特性:
順序一致性
從同一個客戶端發起的事務請求,最終將會嚴格按照發起的順序被應用到Zookeeper中。
原子性
全部事務請求的處理結果在整個集羣中全部機器上的應用狀況是一致的。
單一視圖
不管客戶端連接的是那個Zookeeper服務器,看到的服務端數據模型都是一致的。
可靠性
一旦服務端成功的應用了一個事務,並完成對客戶端的響應,那麼該事務所引發的服務端狀態變動將會被一直保留下來,除非有另外一個事務又對其進行了變動。
實時性
Zookeeper僅僅保證在必定的時間段內,客戶端最終必定可以從服務端讀取到最新的數據狀態。
不少讀者認爲Zookeeper是Paxos算法的一個實現,事實上,並無徹底採用Paxos算法,而是使用了ZAB協議。ZAB協議是爲Zookeeper設計的一種支持崩潰恢復的原子廣播協議。
ZAB協議主要包含兩種基本模式:崩潰恢復,消息廣播。
消息廣播
ZAB協議的消息廣播過程是使用一個原子廣播協議,相似於二階段提交。針對客戶端的請求,Leader服務器會爲其生成對應的事務Proposal,並將其發送給集羣中其他全部機器,而後在分別收集各自的票選,最後進行事務提交。
以下圖所示:
但與二階段提交不一樣的是,ZAB在二階段提交過程當中,移除了中斷邏輯,全部的Follower要麼正常反饋Leader提出的Proposal,要麼拋棄Leader。同時,意味着咱們能夠在過半的Follower服務器已經反饋ACK以後就能夠提交事務,不須要等待全部的Follower都反饋。整個消息廣播協議是基於具備FIFO特性的TCP協議進行網絡通信的,所以保證了消息的有序性。
可是,這樣的二階段提交不能保證Leader崩潰後數據的一致性,所以還須要崩潰恢復模式。
崩潰恢復
在ZAB協議中,爲了保證程序的正確運行,整個恢復過程結束後須要選舉出一個新的Leader服務器。所以,ZAB協議須要一個高效且可靠的Leader選舉算法,從而確保能快速的選舉出新的Leader。同時,Leader選舉算法不只僅須要讓Leader本身知道其自身被選舉爲Leader,還須要讓集羣中的其餘機器知道。
數據同步
全部正常運行的服務器,要麼成爲Leader,要麼成爲Follower並和Leader保持同步。 Leader服務器須要確保全部的Follower服務器可以接收到每一條事務Proposal,而且能 夠正確地將全部已經提交了的事務Proposal應用到內存數據庫中去。具體的,Leader服 務器會爲每個Follower服務器都準備一個隊列,並將那些沒有被各Follower服務器同 步的事務以Proposal消息的形式逐個發送給Follower服務器,並在每個Proposal消息 後面緊接着再發送一個Commit消息,以表示該事務已經被提交。等到Follower服務器 將全部其還沒有冋步的事務Proposal都從Leader服務器上同步過來併成功應用到本地數 據庫中後,Leader服務器就會將該Follower服務器加入到真正的可用Follower列表中, 並開始以後的其餘流程。
聯繫:
區別:
二者設計目標不太同樣,ZAB用於構建一個分佈式數據主備系統,而Paxos用於構建一個分佈式一致性狀態機系統。
本章主要介紹如何使用Zookeeper,包括部署與運行,以及Java客戶端的調用,都是一些比較基礎的使用方法,在此不過多贅述。
本章主要介紹Zookeeper的典型應用場景以及實現。
數據發佈、訂閱系統,即所謂的配置中心,顧名思義就是發佈者將數據發佈到Zookeeper的一個或一系列節點上,供訂閱者進行數據訂閱,從而達到動態獲取數據的目的,實現配置信息的集中式管理和數據的動態更新。
發佈、訂閱系統通常有兩種設計模式,分別是推(PUSH)模式和拉(PULL)模式。在推模式中,服務器主動將數據更新發送給全部訂閱的客戶端;而拉模式則是由客戶端主動發起請求來獲取最新數據,一般客戶端都採用定時進行輪訓拉取的方式。
Zookeeper採用的是推拉相結合的方式:客戶端向服務端註冊本身須要關注的節點,一旦該節點的數據發生變動,那麼服務端就會向相應的客戶端發送Wather事件通知,客戶端接收到這個消息通知以後,須要主動到服務端獲取最新的數據。
Zookeeper實現配置管理的幾個步驟:
配置存儲
選取一個數據節點用於配置的存儲,例如 /app1/database_config(如下簡稱「配置節點」),以下圖:
咱們須要將配置信息寫入該數據節點中,例如:
master0.jdbc.driverclass=com.mysql.jdbc.Driver
master0.jdbc.url=jdbc:mysql://xx.xx.xx.xx:3306/xx
master0.jdbc.username=xxxxxx
master0.jdbc.password=xxxxxx
slave0.jdbc.driverclass=com.mysql.jdbc.Driver
slave0.jdbc.url=jdbc:mysql://xx.xx.xx.xx:3306/xx
slave0.jdbc.username=xxxxxx
slave0.jdbc.password=xxxxxx
複製代碼
配置獲取 集羣中每臺機器在啓動初始化階段,首先會從這個數據節點中讀取數據庫的信息,同時,客戶端還須要在該配置節點上註冊一個數據變動的Watcher監聽,一旦發生節點數據變動,全部訂閱的客戶端都可以獲取到數據變動通知。
配置變動 系統運行過程當中,可能出現須要進行數據庫切換的狀況,這個時候就須要進行配置變動。咱們只須要對節點上的內容進行更新,Zookeeper就可以幫咱們將數據變動通知發送到各個客戶端。
分佈式服務的名字相似於數據庫中的惟一主鍵。利用Zookeeper節點建立的API能夠建立一個順序節點,而且在API返回值中會返回這個節點的完整名字。利用這個特性,能夠藉助Zookeeper來生成全局惟一ID。
如圖所示,會有如下步驟:
1.全部客戶端都會根據本身的任務類型,在指定類型的任務下面經過調用create()接口建立一個順序節點,例如建立「job-」節點。
2.節點建立完畢後,create()接口返回完整節點名,例如「job-00000001」。
3.客戶端拿到這個返回值後,拼接上type,例如「type2-job-0000001」,這就能夠做爲全局惟一ID。
使用Zookeeper來實現分佈式鎖,主要實現排它鎖和共享鎖兩種。
又稱寫鎖或獨佔鎖,核心是保證當前有且只有一個事務得到鎖。
定義
經過在Zookeeper上創建一個數據節點,例如/exciusive_lock/lock節點就被定義爲一個鎖,如圖:
獲取
全部客戶端試圖建立臨時節點,建立成功的任務該客戶端獲取了鎖。同時,沒有獲取到鎖的客戶端在該節點上註冊一個子節點變動的Watcher監聽。
釋放
由於是個臨時節點,所以在兩種狀況下會刪除此節點:
1.獲取鎖的客戶端宕機
2.獲取鎖的客戶端完成邏輯,主動刪除
不管這兩種哪一種方式釋放鎖,都會通知其餘註冊了的監聽的客戶端去從新獲取鎖。
所以,整個流程以下圖所示:
又稱讀鎖,若是事務1對對象A加鎖,那麼其餘的事務只能對A讀,不能寫。
定義
利用Zookeeper建立臨時節點,名稱相似爲「/shared_lock/[Hostname]-請求類型-序號」。
以下圖:
獲取
在須要獲取鎖時,全部請求都在該節點下建立臨時有序節點,若是是讀的話就建立請求類型爲R的,若是是寫的就建立請求類型爲W的。
鎖的獲取順序
因爲共享鎖的定義,能夠同時讀,可是寫時不能讀也不能寫的。鎖的獲取順序分爲以下四步:
1.建立節點後,獲取/shared_lock節點下的全部子節點,並註冊監聽。
2.肯定本身的序號在全部節點中的順序。
3.對於讀請求:
若是沒有比本身小的節點,或是比本身小的節點都是讀節點的話,就得到鎖。
若是比本身小的節點中有寫節點,則繼續等待。
對於寫請求:
若是本身不是最小的節點,則繼續等。
4.接收到監聽的通知,繼續1步驟。
複製代碼
釋放
釋放邏輯和排它鎖一致,很少贅述。
總體流程如圖:
問題:羊羣效應
上述共享鎖的實現能知足10臺機器之內的集羣模型,若是規模擴大以後,會產生什麼問題呢?
上述共享鎖在競爭過程當中,存在大量的「watcher通知」和「子節點列表獲取」兩個重複操做,而且絕大數的運行結果都是判斷本身不是最小的節點,從而繼續等待。若是在規模較大的集羣中,若是同一時間有多個節點對應的客戶端完成事務或是事務中斷引發節點消失,Zookeeper服務器就會在短期向客戶端發送大量的通知---這就是羊羣效應。
這裏只須要改動:每一個鎖競爭者,只需關注/shared_lock節點下序號比本身小的節點是否存在便可,具體實現以下:
1.客戶端調用create()方法建立一個相似於「/shared_lock/[hostname]-請求類型-序號」的**臨時有序節點**。
2.客戶端調用getChildren()接口來獲取全部已經建立的子節點列表,注意,這裏不註冊任何Watcher。
3.若是沒法獲取共享鎖,那麼就調用exist()來對比本身小的那個節點註冊Watcher。
注意,這裏比本身小是個籠統的說法,具體對於讀請求和寫請求不同。
讀請求:向比本身序號小的最後一個寫請求節點註冊。
寫請求:向比本身小的最後一個節點註冊。
4.等待Watcher通知,繼續進入步驟2。
複製代碼
流程如圖所示: