什麼是分佈式呢?node
起初,咱們的應用流量比較小,全部東西所有部署在一個服務器,好比所有丟給一個tomcat來處理,頂多作一個tomcat的多節點部署多分,再掛一臺Nginx作一下負載均衡就OK了。可是隨着業務功能複雜度上升,訪問流程的上升,單體架構就不行了。這個時候就該分佈式上場了,將業務模塊作必定拆分,各業務組件分佈在網絡上不一樣的計算機節點上,同時爲了保證高可用性和性能,單個組件模塊也會作集羣部署。算法
分佈式雖然爽了,可是隨之而來的就是分佈式帶來的複雜性,好比在分佈式系統中網絡故障問題幾乎是必然存在的;事務也再也不是數據庫幫咱們保證了,由於可能每一個業務有本身的庫,但不一樣業務之間又有保證事務的需求,這是就須要考慮實現分佈式事務了;還有數據一致性問題,集羣中副本節點不能及時同步到主節點的數據,會有數據一致性問題須要解決。spring
實踐須要理論的知道,一樣地,在分佈式軟件開發領域內,也是有前輩大神們作了基礎的理論研究。下面將要介紹的就是分佈式相關的兩個基礎理論:CAP定理和BASE理論。數據庫
在聊CAP定理前,咱們先簡單瞭解下分佈式事務。數據庫的事務咱們知道。假如銀行轉帳,轉出操做和轉入操做在同一個數據庫中,就很好實現了,只須要在方法上增長一個@Transactional,剩下的事情數據庫會幫咱們作好。可是在分佈式環境中,咱們對比現實中的銀行轉帳,誇行轉帳,不止是誇庫,更是誇不一樣的銀行系統的。在這種場景,咱們須要保證ACID的特性,就是須要分佈式事務解決方案了。好了,這裏只是作個告終。下面說CAP定理。tomcat
CAP定理是說,在一個分佈式系統中,不可能同時知足一致性(Consistency)、可用性(Availiablity)和分區容錯性(Partition tolerance)這三個基本的需求。最多隻能知足其中的兩項。安全
一致性bash
在分佈式環境中,一致性是指數據在多個副本之間是否可以保持一致的特性。在一致性的需求下,當一個系統在數據一致的狀態下執行更新操做後,應該保持系統的數據仍然處於一致的狀態。若是對第一個節點上的數據更新成功後,第二個節點上的數據並無獲得相應的更新,那麼若是從第二個節點讀取數據,則獲取到的就是舊數據(或者或是髒數據)。這就是典型的分佈式數據不一致的場景。服務器
可用性網絡
可用性是指系統提供的服務必須一直處於可用的狀態,對於用戶的每個操做請求老是能在有限的時間內返回結果。這裏有限的時間就是系統響應時間session
分區容錯性
分佈式系統在遇到任何網絡分區故障的時候,仍然須要保證可以對外提供知足一致性和可用性的服務,除非是整個網絡環境發生了故障。
網絡分區是指在分佈式系統中,不一樣節點分佈在不一樣的子網絡中,因爲一些特殊的緣由致使這些子網絡之間出現了網絡不連通的狀況,但各個子網絡的內部網絡是正常的,從而致使整個系統的網絡環境被切分紅了若干個孤立的區域。
上面說到,一個分佈式系統不可能同時知足CAP的特性。可是,須要說明的是,分區容錯性P是一個最基本的需求。由於分佈式系統中的組件必然部署在不一樣的網絡節點上,網絡問題是必然會出現的一個問題。所以就剩下兩種選擇了,即CP和AP。系統架構須要在C和A之間尋求平衡。
BASE是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)的縮寫。BASE是對CAP中一致性和可用性權衡的結果。是基於CAP定理逐步演化而來,其核心思想是即便沒法作到強一致性,但每一個應用均可以根據自身的業務特色,採用適當的方式來使系統達到最終的一致性。
基本可用
分佈式系統在出現不可預知的故障的時候,容許損失部分可用性。好比響應時間上的損失,原來0.2s返回的,如今可能須要2s返回。或者是部分功能上的損失,好比秒殺場景下部分用戶可能會被引導到一個降級的頁面。
軟狀態
是和硬狀態相對的,是指容許系統中的數據存在中間的狀態。可是中間的狀態並不會影響系統的總體可用性。
最終一致性
是指系統中的全部數據副本,通過必定時間的同步後,最終可以達到一個一致的狀態。而不是要實時保證系統數據的強一致性。
Apache Zookeeper是由Apache Hadoop的子項目發展而來,2011年正式成爲Apache的頂級項目。Zookeeper爲分佈式應用提供了高效且可靠的分佈式協調服務。在解決分佈式數據一致性方面,Zookeeper並無使用Paxos算法(一種一致性算法),而是採用了ZAB(Zookeeper Atomic Broadcast)的一致性協議。
分佈式應用程序能夠基於它實現諸如數據發佈/訂閱、負載均衡、命名服務、分佈式協調、集羣管理、Master選舉、分佈式鎖和分佈式隊列等功能。Zookeeper能夠保證以下分佈式一致性特性。
順序一致性
同一個客戶端發起的事務請求,最終將會嚴格的按照其發起的順序被應用到Zookeeper中去。
原子性
全部事務請求的處理結果在整個集羣中全部機器上的應用狀況是一致的,也就是說,也麼集羣中的全部節點都應用了一個事務,要麼都沒有應用。
單一視圖
不管客戶端鏈接的是哪一個Zookeeper服務器,其看到的服務端數據模型都是一致的。
可靠性
一旦服務端成功的應用了一個事務,並完成對客戶端的響應那麼該事務所引發的服務端狀態改變將會被一直保留下來,除非有另外一個事務又對其進行了變動。
實時性
Zookeeper僅僅保證在必定的時間段內,客戶端最終必定可以從服務端讀取到最新的數據狀態。
Zookeeper中有數據節點的概念,咱們稱之爲ZNode,ZNode是Zookeeper中數據的最小單元。每一個ZNode上均可以保存數據,同時還能夠掛載子節點,所以就構成了一個層次化的命名空間,咱們叫作樹。相似於Linux文件系統中的目錄樹。
看圖就明白了。
Zookeeper事務
Zookeeper中的事務,和數據庫中具備ACID特性的事務有所區別。在Zookeeper中,事務是指可以改變Zookeeper服務器狀態的操做,咱們叫作事務操做或者更新操做,通常包括數據節點的建立和刪除、數據節點內容更新和客戶端會話建立與失效操做。對於每個事務請求,Zookeeper都會爲其分配一個全局惟一的事務ID,用ZXID來表示,一般是一個64位的數字。每個ZXID對應一次更新操做,從這些ZXID中能夠間接地識別出Zookeeper處理這些更新操做請求的全局順序。
數據節點的類型
在Zookeeper中,節點類型能夠分爲持久節點(PERSISTENT),臨時節點(EPHEMERAL)和順序節點(SEQUENTIAL)。在具體的節點建立中,經過組合,有下面四種組合節點類型:
持久節點
是最多見的一種節點類型,是指數據節點被建立後,就會一直存在於Zookeeper服務器中,直到有刪除操做來主動刪除這個節點。
持久順序節點
和持久節點的特性是同樣的,額外的特性表如今順序性上。在Zookeeper中,每一個父節點都會爲它的第一級子節點維護一份順序,用於記錄每一個子節點建立的順序。基於這個順序特性,在建立子節點的時候,能夠設置這個標記,那麼在建立節點過程當中,Zookeeper會自動爲節點名加上一個數字後綴,做爲一個新的完整的節點名。數字後綴的上限是整型的最大值。
臨時節點
和持久節點不一樣的是,臨時節點的生命週期和客戶端的會話綁定在一塊兒,也就是說,若是客戶端的會話失效,那麼這個節點就會被自動清理掉。這裏提到的是客戶端會話失效,而非TCP鏈接斷開。
臨時有序節點
臨時有序節點的特性和臨時節點的特性同樣,只是增長了有序的特性。
狀態信息
在Zookeeper客戶端中,咱們經過stat命令,能夠查看節點的狀態信息。
1[zk: localhost:2181(CONNECTED) 6] stat /test/node1
2cZxid = 0x8
3ctime = Sun May 03 21:12:24 CST 2020
4mZxid = 0xb
5mtime = Sun May 03 21:13:08 CST 2020
6pZxid = 0x8
7cversion = 0
8dataVersion = 1
9aclVersion = 0
10ephemeralOwner = 0x0
11dataLength = 5
12numChildren = 0
下面簡要介紹下狀態信息中各字段含義:
1cZxid: Created ZXID ,表示數據節點被建立時的事務ID。
2ctime: Created Time,表示節點被建立的時間。
3mZxid: Modified ZXID,表示該節點最後一次被更新時的事務ID。
4mtime: Modified Time,表示該節點最後一次被更新的時間。
5pZxid: 表示該節點的子節點列表最後一次被修改時的事務ID,注意只有子節點列表變動了纔會變動pZxid,子節點內容的變動不會影響pZxid。
6cversion: 子節點的版本號。
7dataVersion: 數據的版本號。
8aclVersion: acl權限的版本號。
9ephemeralOwner: 建立該臨時節點的會話的sessionID,若是該節點是持久節點,那麼這個屬性值爲0。
10dataLength: 數據內容的長度。
11numChildren: 當前節點的子節點個數。
上面狀態中的version字段是一種樂觀鎖機制的保證,保證併發更新數據的安全性。
在Zookeeper中,引入了Watcher機制來實現這種分佈式的通知功能。Zookeeper容許客戶端向服務器註冊一個Watcher監聽,當服務端的一個特定事件觸發了這個Watcher,那麼就會向客戶端發送一個事件通知來實現分佈式的通知功能。這個過程能夠看下面的圖。
構成集羣的每一臺機器都有本身的角色,最典型的集羣模式就是Master/Slave模式。在這種集羣模式中,Master節點複雜讀寫操做,Slave負責提供讀服務,並以異步的方式從Master同步數據。
而在Zookeeper集羣中,沒有使用傳統的Master/Slave集羣模式,而是引入了Leader、Follower和Observer三種角色。ZK集羣中的全部機器經過一個Leader選舉過程來選定一臺機器做爲「Leader」的機器。Leader服務器爲客戶端提供讀和寫服務。Follower和Observer都可以提供讀服務,惟一區別在於Observer機器不參與Leader選舉過程。
Leader
Leader服務器是整個zk集羣工做機制中的核心,其主要工做是如下兩個:
Follower
Follower服務器時集羣狀態的跟隨者,其主要的工做有一下三個。
Observer
在zk集羣中充當了一個觀察者的角色,觀察集羣的最新狀態並將這些狀態變動同步過來。Observer服務器的工做原理和Follower服務器基本是一致的,對於非事務的請求均可以進行獨立的處理,對於事務請求會轉發給Leader處理。和Follower惟一的區別在於,Observer不參與任何形式的投票,包括事務請求Proposal投票和Leader選舉投票。
安裝好Zookeeper以後,就可使用zk自帶的客戶端腳原本進行操做了。進入Zookeeper的bin目錄以後,直接執行以下命令:
1sh zkCli.sh
當看到出現下面這句話時,說明已經成功鏈接到了zkserver。
1WatchedEvent state:SyncConnected type:None path:null
進入客戶端後,能夠直接使用help命令看下支持哪些命令。
下面說一下在zk客戶端中怎麼操做節點和數據。
增長節點
使用create命令新建一個節點。命令格式以下:
1create [-s] [-e] [-c] [-t ttl] path [data] [acl]
-s是順序特性,-e是臨時節點。
好比執行下面命令
1[zk: localhost:2181(CONNECTED) 10] create /Java hello
2Created /Java
會在根節點下建立一個/Java節點,而且節點的數據內容是hello。默認建立的是持久節點。
能夠繼續在/Java節點下建立子節點,好比:
1[zk: localhost:2181(CONNECTED) 11] create /Java/spring 123
2Created /Java/spring
讀取
使用ls命令能夠看到指定節點下的全部子節點。只能看一級,不能列出子節點樹。命令格式爲:
1ls [-s] [-w] [-R] path
好比執行 ls / 命令,能夠看下根節點下的子節點狀況。
1[zk: localhost:2181(CONNECTED) 12] ls /
2[Java, aaa, happy, test, zookeeper]
可使用get 命令獲取節點的數據內容,命令格式爲:
1get [-s] [-w] path
好比咱們獲取一下/Java節點中的數據內容:
1[zk: localhost:2181(CONNECTED) 14] get /Java
2hello
獲取節點狀態信息
使用stat命令能夠獲取節點的狀態信息,命令格式以下:
1stat [-w] path
例如,咱們獲取一下/Java節點的狀態信息
1[zk: localhost:2181(CONNECTED) 15] stat /Java
2cZxid = 0x14
3ctime = Mon May 11 21:40:38 CST 2020
4mZxid = 0x14
5mtime = Mon May 11 21:40:38 CST 2020
6pZxid = 0x15
7cversion = 1
8dataVersion = 0
9aclVersion = 0
10ephemeralOwner = 0x0
11dataLength = 5
12numChildren = 1
上面這些信息咱們在以前聊到Zookeeper數據節點是有講過是什麼意思,能夠回顧下。
更新
使用set命令能夠更新指定節點的數據內容。命令格式以下:
1 set [-s] [-v version] path data
好比咱們將/Java節點的內容更新爲 world。
1[zk: localhost:2181(CONNECTED) 16] set /Java world
2[zk: localhost:2181(CONNECTED) 17] get /Java
3world
數據更新完成以後,咱們可使用stat命令,再看一下節點的狀態信息,發現dataVersion已經由0變爲1了。
1[zk: localhost:2181(CONNECTED) 18] stat /Java
2cZxid = 0x14
3ctime = Mon May 11 21:40:38 CST 2020
4mZxid = 0x16
5mtime = Mon May 11 21:52:19 CST 2020
6pZxid = 0x15
7cversion = 1
8dataVersion = 1
9aclVersion = 0
10ephemeralOwner = 0x0
11dataLength = 5
12numChildren = 1
由於剛纔更新數據內容的操做致使數據版本升級。
刪除
使用delete命令刪除Zookeeper節點,用法以下:
1 delete [-v version] path
好比咱們將/Java節點刪除掉。不過須要注意的是,要刪除的節點必須沒有子節點才能夠。下面直接刪除/Java節點是刪除不掉的。由於它下面有子節點。
1[zk: localhost:2181(CONNECTED) 20] delete /Java
2Node not empty: /Java
3[zk: localhost:2181(CONNECTED) 21] ls /
4[Java, aaa, happy, test, zookeeper]
能夠刪除/Java/spring節點
1[zk: localhost:2181(CONNECTED) 22] ls /Java
2[spring]
3[zk: localhost:2181(CONNECTED) 23] delete /Java/spring
4[zk: localhost:2181(CONNECTED) 24] ls /Java
5[]
ZAB協議是爲分佈式協調服務Zookeeper專門設計的一種支持崩潰恢復的原子廣播協議。它並非Paxos算法的一種實現。
在Zookeeper中,主要依賴ZAB協議來實現分佈式數據一致性,基於該協議,Zookeeper實現了一種主備模式的系統架構來保證集羣模中各副本之間數據的一致性。ZAB協議要知足下面一些核心需求。
ZAB協議的核心機制
其核心機制是定義了對於那些會改變Zookeeper服務器數據狀態的事務請求的處理方式,即:
全部的事務請求必須由一個全局惟一的服務器來協調處理,這樣的服務器稱爲Leader服務器,而餘下的其餘服務器是Follower(這裏暫不說Observer,由於不參與投票)。Leader服務器複雜將一個客戶端的事務請求轉換成一個事務提議(Proposal),並將該Proposal分發給集羣中全部的Follower服務器。以後Leader服務器須要等待全部Follower服務器的反饋,一旦超過半數的Follower服務器進行了正確地反饋後,那麼Leader就會再次向全部的Follower服務器分發Commit消息,要求其將前一個Proposal進行提交。
針對ZAB協議這裏只作簡要介紹,至於崩潰恢復和消息廣播的具體內容不詳細展開。
前面說到在ZK集羣中,有一個Leader負責處理事務請求。Leader是經過一種選舉算法選出來的一個Zookeeper服務器節點。下面簡要介紹下Leader選舉的過程。
Leader選舉有兩個時機:
一、服務器啓動時Leader選舉
二、運行期Leader節點掛了,須要選舉新的Leader。
服務器啓動時的Leader選舉
這裏以3臺機器組成的集羣爲例子。Server1(myid爲1)、Server2(myid爲2)、Server3(myid爲3)。myid是在zk集羣中用來標識每一臺機器的,不能重複。假設Server1最早啓動,而後Server2,再Server3。
一、首先每臺Server會發出一個投票
因爲是初始的狀況,每臺機器都會選本身做爲Leader。每次投票的最基本信息就是服務器的myid和ZXID,咱們用(myid,zxid)這種形式標識。那Server1發出的投票就是(1,0),Server2發出的投票就是(2,0),Server3(3,0)而後將各自的投票發送給集羣中剩下的其餘全部機器。
二、接收來自各個服務器的投票
每一個服務器都會接收來自其餘服務器的投票。集羣中每一個服務器在接收到投票後,首先會判斷該投票的有效性,包括檢查投票是不是本輪投票,是否來自LOOKING狀態的服務器。
三、處理投票
主要是拿本身的投票和其餘服務器發送過來的投票作一個PK,PK的規則以下:
這裏對於Server1來講,本身的投票是(1,0),收到的投票是(2,0),通過PK以後,Server1會更新本身的投票(2,0),而後將票從新發出去。而對於Server2來講,不須要更新本身的投票信息。
四、統計投票信息
每次投票後,服務器都會統計全部的投票,判斷是否已經有過半的機器收到了相同的投票信息。這裏對於Server1,Server2來講,都統計出集羣中已經有兩臺機器接收了(2,0)這個投票信息。此時,就認爲已經選舉出了Leader
五、改變服務器狀態
一旦肯定了Leader,每一個服務器都會更新本身的狀態:若是是Follower,那麼就變爲FOLLOWING,若是是Leader,那麼就變爲LEADING。
運行期Leader節點掛了,須要選舉新的Leader。
zk集羣正常運行的過程當中,一旦選出了Leader,那它一直就是Leader,除非這個Leader掛了,纔會進入新一輪的Leader選舉,也就是下面要說的這種狀況。這個過程其實和啓動期間Leader選擇過程基本是一致的。
一、變動服務器狀態
當Leader掛了以後,餘下的非Observer服務器都會將本身的服務器狀態變爲LOOKING,而後開始進入Leader選舉流程。
二、每一個Server會發出一個投票
這裏ZXID可能就會是不同的,由於是運行期,每臺機器上的數據同步狀況可能會有差別。
三、接收來自各個服務器的投票
四、處理投票
五、統計投票
六、改變服務器的狀態
一、使用Zookeeper能夠作配置中心
能夠將配置信息集中存儲在zk的節點中,客戶端能夠註冊一些監聽,一旦節點數據發生變動,服務端就會向相應的客戶端發送Watcher事件通知,客戶端接收到這個通知以後,能夠主動到服務端獲取最新的數據。
二、做爲註冊中心
Dubbo中就是默認用zk做爲註冊中心的。將服務的url信息註冊到zk的節點上。利用臨時節點和watcher機制實現服務的動態感知。
三、做爲分佈式鎖
分佈式鎖是分佈式場景下保證資源同步的一種方式。在zk中,全部客戶端都在一個節點(好比/lock)下調用create()方法建立臨時子節點,只會有一個建立成功,這個建立成功的就認爲是獲取了鎖。其餘客戶端就須要在/lock節點上註冊一個子節點變動的watcher監聽。