隨著互聯(lián)網(wǎng)技術的發(fā)展,大型網(wǎng)站需要的計算能力和存儲能力越來越高,網(wǎng)站架構逐漸從集中式轉變成分布式系統(tǒng)。
雖然分布式相對于集中式系統(tǒng)有比較多的優(yōu)勢,比如更高更強的計算、存儲、處理能力等。但同時也引入了其他一些問題,比如如何在分布式系統(tǒng)中保證數(shù)據(jù)的一致性和可用性。
在日常中,如果兩個員工或用戶對某件事產生了分歧,通常我們的做法是找上級,去做數(shù)據(jù)和信息的同步。
那么對于我們的服務呢,多個節(jié)點之間數(shù)據(jù)不同步如何處理?
在單機發(fā)展到集群、分布式服務的過程中,每一件技術或工具都走向了系統(tǒng)化,專一化的道路。舉個例子,在單體應用中,如果多個線程想對同一個變量進行修改,我們通常的做法是對要修改的變量或資源加鎖。那么對于集群來說,并沒有這樣的東西,我們應該怎么做?
對于分布式集群來說,這個時候,我們通常需要一個能夠在各個服務或節(jié)點之間進行協(xié)調的服務或中間人
架構設計中,沒有一個問題不能通過一層抽象層來解決,如果有,那就是兩層。

我們可以一起看看,協(xié)調服務中的佼佼者--ZooKeeper
zookeeper起源

最初,在Hadoop生態(tài)中,會存在很多的服務或組件(比如hive、pig等),每個服務或組件之間進行協(xié)調處理是很麻煩的一件事情,急需一種高可用高性能數(shù)據(jù)強一致性的協(xié)調框架。因此雅虎的工程師們創(chuàng)造了這個中間程序,但中間程序的命名卻愁死了開發(fā)人員,突然想到hadoop中的大多是動物名字,似乎缺乏一個管理員,這個程序的功能有是如此的相似。因此zookeeper誕生。
zookeeper提供了哪些特性,以便于能夠很好的完成協(xié)調能力的處理呢?
功能與特性
數(shù)據(jù)存儲
zookeeper提供了類似Linux文件系統(tǒng)一樣的數(shù)據(jù)結構。每一個節(jié)點對應一個Znode節(jié)點,每一個Znode節(jié)點都可以存儲1MB(默認)的數(shù)據(jù)。
客戶端對zk的操作就是對Znode節(jié)點的操作。

- Znode:包含ACL權限控制、修改/訪問時間、最后一次操作的事務Id(zxid)等等
- 說有數(shù)據(jù)存儲在內存中,在內存中維護這么一顆樹。
- 每次對Znode節(jié)點修改都是保證順序和原子性的操作。寫操作是原子性操作。
舉個例子,在注冊中心中,可以通過路徑"/fsof/服務名1/providers"找到"服務1"的所有提供者。
每一個Znode節(jié)點又根據(jù)節(jié)點的生命周期與類型分為4種節(jié)點。

- 生命周期:當客戶端會話結束的時候,是否清理掉這個會話創(chuàng)建的節(jié)點。持久-不清理,臨時-清理。
- 類型:每一個會話,創(chuàng)建單獨的節(jié)點(例子:正常節(jié)點:rudytan,順序編號節(jié)點:rudytan001,rudytan002等等)
監(jiān)聽機制
zookeeper除了提供對Znode節(jié)點的處理能力,還提供了對節(jié)點的變更進行監(jiān)聽通知的能力。

監(jiān)聽機制的步驟如下:
- 任何session(session1,session2)都可以對自己感興趣的znode監(jiān)聽。
- 當znode通過session1對節(jié)點進行了修改。
- session1,session2都會收到znode的變更事件通知。
節(jié)點常見的事件通知有:
- session建立成功事件
- 節(jié)點添加
- 節(jié)點刪除
- 節(jié)點變更
- 子節(jié)點列表變化
需要特別說明的是:
一次監(jiān)聽事件,只會被觸發(fā)一次,如果想要監(jiān)聽到znode的第二次變更,需要重新注冊監(jiān)聽。
到這里,我們了解到zookeeper提供的能力,那我們在哪些場景可以使用它?如何使用它呢?
應用場景
zookeeper用得比較多的地方可能是,微服務的集群管理與服務注冊與發(fā)現(xiàn)。
注冊中心

- 依賴于臨時節(jié)點
- 消費者啟動的時候,會先去注冊中心中全量拉取服務的注冊列表。
- 當某個服務節(jié)點有變化的時候,通過監(jiān)聽機制做數(shù)據(jù)更新。
- zookeeper掛了,不影響消費者的服務調用。
目前還有個比較流行的服務Eureka也可以做注冊中心,他們有什么優(yōu)勢和劣勢呢?留個疑問,哈哈哈。
分布式鎖

- 依賴于臨時順序節(jié)點
- 判斷當前client的順序號是否是最小的,如果是獲取到鎖。
- 沒有獲取到鎖的節(jié)點監(jiān)聽最小節(jié)點的刪除事件(比如lock_key_001)
- 鎖釋放,最小節(jié)點刪除,剩余節(jié)點重新開始獲取鎖。
- 重復步驟二到四。
redis和db也能創(chuàng)建分布式鎖,哪有什么異同呢?留個疑問,哈哈哈。
分布式鎖可以參考我的另一篇文章:分布式鎖 http://www.itdecent.cn/p/e4174e499798
集群管理與master選舉

- 依賴于臨時節(jié)點
- zookeeper保證無法重復創(chuàng)建一個已存在的數(shù)據(jù)節(jié)點,創(chuàng)建成功的client為master。
- 非master,在已經創(chuàng)建的節(jié)點上注冊節(jié)點刪除事件監(jiān)聽。
- 當master掛掉后,其他集群節(jié)點收到節(jié)點刪除事件,進行重新選舉
- 重復步驟二到四
當然還有其他應用場景,不一一列舉了。
有人說,zookeeper可以做分布式配置中心、分布式消息隊列,看到這里的小伙伴們,你們覺得合適么?
到這里,可以基本上滿足基于zk應用開發(fā)的理論知識儲備。對原理或有更強求知欲的小伙伴可以繼續(xù)往下看,接下來聊聊zookeeper如何做到高性能高可用強一致性的。
高性能高可用強一致性保障
高性能-分布式集群

高性能,我們通常想到的是通過集群部署來突破單機的性能瓶頸。對于zk來說,就是通過部署多個節(jié)點共同對外提供服務,來提供讀的高性能。
- Master/Slave模式。
- 在zookeeper中部署多臺節(jié)點對外提供服務,客戶端可以連接到任意一個節(jié)點。
- 每個節(jié)點的數(shù)據(jù)都是一樣的。
- 節(jié)點根據(jù)角色分為Leader節(jié)點與Learner節(jié)點(包括Follower節(jié)點與Observer節(jié)點)。
- 集群中,只有一個Leader節(jié)點,完成所有的寫請求處理。
- 每次寫請求都會生成一個全局的唯一的64位整型的事務ID(可以理解為全局的數(shù)據(jù)的版本號)。
- Learner節(jié)點可以有很多,每個Leaner可以獨自處理讀請求,轉寫請求到Leader節(jié)點。
- 當Leader節(jié)點掛掉后,會從Follower節(jié)點中通過選舉方式選出一個Leader提供對外服務。
- Follower節(jié)點與Observer節(jié)點區(qū)別在于不參與選舉和提議的事務過半處理。
- 集群通常是按照奇數(shù)個節(jié)點進行部署(偶然太對容災沒啥影響,浪費機器)。
數(shù)據(jù)一致性(zab協(xié)議-原子廣播協(xié)議)
通過集群的部署,根據(jù)CAP原理,這樣,可能導致同一個數(shù)據(jù)在不同節(jié)點上的數(shù)據(jù)不一致。zookeeper通過zab原子廣播協(xié)議來保證數(shù)據(jù)在每一個節(jié)點上的一致性。原子廣播協(xié)議(類似2PC提交協(xié)議)大概分為3個步驟。

- Leader包裝寫請求,生成唯一zxid,發(fā)起提議,廣播給所有Follower。
- Follower收到提議后,寫入本地事務日志,根據(jù)自身情況,是否同意該事務的提交。
- Leader收到過半的Follower同意,自己先添加事務。然后對所有的Learner節(jié)點發(fā)送提交事務請求。
需要說明的是,zookeeper對數(shù)據(jù)一致性的要求是:
- 順序一致性:嚴格按照事務發(fā)起的順序執(zhí)行寫操作。
- 原子性:所有事務請求的結果在集群中的所有節(jié)點上的應用情況是一致的。
- 單一視圖:客戶端訪問任何一個節(jié)點,看到的數(shù)據(jù)模型都是一致的。
- 實時性:保證在極小一段時間客戶端最終可以從服務讀取最新數(shù)據(jù)狀態(tài)(如果要實時,需要客戶端調用syn方法)。
可用性-leader選舉(zab協(xié)議-崩潰恢復協(xié)議)
在整個集群中,寫請求都集中在一個Leader節(jié)點上,如果Leader節(jié)點掛了咋辦呢?

當集群初始化或Follower無法聯(lián)系上Leader節(jié)點的時候,每個Follower開始進入選舉模式。選舉步驟如下:
- Follower節(jié)點第一次投票先投自己,然后將自己的選票廣播給剩余的Follower節(jié)點。
- Follower節(jié)點接收到其他的選票。
- 選票比較:比較自己的與接收的選票的投票更有。
- 如果資金的選票不是最優(yōu)選票,變更自己的選票,投最優(yōu)選票的節(jié)點。
- 統(tǒng)計自己收到的選票,如果某個節(jié)點獲得了過半的節(jié)點的投票。確認該節(jié)點為新的Leader節(jié)點。
- 確認Leader節(jié)點后,每個節(jié)點變更自己的角色。完成投票選舉。
選舉原則:誰的數(shù)據(jù)最新,誰就有優(yōu)先被選為Leader的資格。
舉個例子,假如現(xiàn)在zk集群有5個節(jié)點,然后掛掉了2個節(jié)點。剩余節(jié)點S3,S4,S6開始進行選舉,他們的最大事務ID分別是6,2,6。定義投票結構為(投票的節(jié)點ID,被投節(jié)點ID,被投節(jié)點最大事務ID)。

- 初始狀態(tài),S3,S4,S5分別投自己,并帶上自己的最大事務ID。
- S3,S4,S5分別對自己收到的2票與自己的1票做比較。
- S5發(fā)現(xiàn)自己的是最優(yōu)投票,不變更投票,S3,S4發(fā)現(xiàn)S5的投票是最優(yōu)解,更改投票。
- S3,S4廣播自己變更的投票。
- 最后大家都確認了S5是Leader,S5節(jié)點狀態(tài)變更為Leader節(jié)點,S3,S4變更為Follower節(jié)點。
到這里,就是選舉的主要過程。
數(shù)據(jù)的持久化

- zookeeper所有數(shù)據(jù)都存在內存中。
- zookeeper會定期將內存dump到磁盤中,形成數(shù)據(jù)快照。
- zookeeper每次的事務請求,都會先接入到磁盤中,形成事務日志。
- 全量數(shù)據(jù) = 數(shù)據(jù)快照 + 事務日志。
比較
前面也提到過,不同的應用場景似乎有不少替代品,我們就大概做個簡單的比較。
注冊中心的比較(與Netflix的Eureka方案)

通過上面的架構圖,可以發(fā)現(xiàn)Eureka不同于zk中的節(jié)點,Eureka中的節(jié)點每一個節(jié)點對等。是個AP系統(tǒng),而不是zk的CP系統(tǒng)。在注冊中心的應用場景下,相對于與強數(shù)據(jù)一致性,更加關心可用性。
分布式鎖(與redis、mysql比較)
分布式鎖可以參考我的另一篇文章:分布式鎖(redis/mysql) http://www.itdecent.cn/p/e4174e499798
具體差異比較:
- Redis:簡單,可靠性不高
- DB:穩(wěn)定、性能有所不足
- ZK: 比較高的性能和完整的鎖的實現(xiàn),但實現(xiàn)復雜度高,并發(fā)不高。
通過這些比較,我們?yōu)槭裁催€需要或在什么場景下使用zookeeper呢。我覺得就一句話:
zookeeper,一種通用的分布式協(xié)調服務解決方案。
感悟
最后,說說在整個學習和使用zk過程中的一個感悟吧。
- 沒有銀彈,每一種技術或方案都有其優(yōu)點和缺點。
- 做一件事情很簡單,做好一件事件很難。