1.Zookeeper簡介
ZooKeeper 是一個開源的分布式協(xié)調(diào)框架怨咪,是Apache Hadoop 的一個子項目,主要用來解決分布式集群中應用系統(tǒng)的一致性問題。
ZooKeeper本質(zhì)上是一個分布式的小文件存儲系統(tǒng)(Zookeeper=文件系統(tǒng)+監(jiān)聽機制)。提供基于類似于文件系統(tǒng)的目錄樹方式的數(shù)據(jù)存儲,并且可以對樹中的節(jié)點進行有效管理,從而用來維護和監(jiān)控存儲的數(shù)據(jù)的狀態(tài)變化敢课。通過監(jiān)控這些數(shù)據(jù)狀態(tài)的變化阶祭,從而可以達到基于數(shù)據(jù)的集群管理、統(tǒng)一命名服務直秆、分布式配置管理濒募、分布式消息隊列、分布式鎖圾结、分布式協(xié)調(diào)等功能瑰剃。
Zookeeper從設計模式角度來理解:是一個基于觀察者模式設計的分布式服務管理框架,它負責存儲和管理大家都關(guān)心的數(shù)據(jù)筝野,然后接受觀察者的注冊晌姚,一旦這些數(shù)據(jù)的狀態(tài)發(fā)生變化粤剧,Zookeeper 就將負責通知已經(jīng)在Zookeeper上注冊的那些觀察者做出相應的反應。
2.順序一致性
強一致性:又稱線性一致性(linearizability )
1.任意時刻挥唠,所有節(jié)點中的數(shù)據(jù)是一樣的,
2.一個集群需要對外部提供強一致性抵恋,所以只要集群內(nèi)部某一臺服務器的數(shù)據(jù)發(fā)生了改變,那么就需要等待集群內(nèi)其他服務器的數(shù)據(jù)同步完成后宝磨,才能正常的對外提供服務
3.保證了強一致性弧关,務必會損耗可用性
弱一致性:
1.系統(tǒng)中的某個數(shù)據(jù)被更新后,后續(xù)對該數(shù)據(jù)的讀取操作可能得到更新后的值唤锉,也可能是更改前的值世囊。
2.即使過了不一致時間窗口,后續(xù)的讀取也不一定能保證一致窿祥。
最終一致性:
1.弱一致性的特殊形式,不保證在任意時刻任意節(jié)點上的同一份數(shù)據(jù)都是相同的株憾,但是隨著時間的遷移,不同節(jié)點上的同一份數(shù)據(jù)總是在向趨同的方向變化
2.存儲系統(tǒng)保證在沒有新的更新的條件下壁肋,最終所有的訪問都是最后更新的值
順序一致性:
1.任何一次讀都能讀到某個數(shù)據(jù)的最近一次寫的數(shù)據(jù)号胚。
2.對其他節(jié)點之前的修改是可見(已同步)且確定的,并且新的寫入建立在已經(jīng)達成同步的基礎(chǔ)上。
Zookeeper是順序一致性浸遗。
3.ZooKeeper數(shù)據(jù)結(jié)構(gòu)
ZooKeeper的數(shù)據(jù)模型是層次模型猫胁,層次模型常見于文件系統(tǒng)。層次模型和key-value模型是兩種主流的數(shù)據(jù)模型跛锌。ZooKeeper使用文件系統(tǒng)模型主要基于以下兩點考慮:
- 文件系統(tǒng)的樹形結(jié)構(gòu)便于表達數(shù)據(jù)之間的層次關(guān)系
- 文件系統(tǒng)的樹形結(jié)構(gòu)便于為不同的應用分配獨立的命名空間( namespace )
ZooKeeper的層次模型稱作Data Tree弃秆,Data Tree的每個節(jié)點叫作Znode。不同于文件系統(tǒng)髓帽,每個節(jié)點都可以保存數(shù)據(jù)菠赚,每一個 ZNode 默認能夠存儲 1MB 的數(shù)據(jù),每個 ZNode 都可以通過其路徑唯一標識郑藏,每個節(jié)點都有一個版本(version)衡查,版本從0開始計數(shù)。
節(jié)點分類
- 1)持久節(jié)點(PERSISTENT): 這樣的znode在創(chuàng)建之后即使發(fā)生ZooKeeper集群宕機或者client宕機也不會丟失必盖。
- 2)臨時節(jié)點(EPHEMERAL ): client宕機或者client在指定的timeout時間內(nèi)沒有給ZooKeeper集群發(fā)消息拌牲,這樣的znode就會消失。
- 3)持久順序節(jié)點(PERSISTENT_SEQUENTIAL): znode除了具備持久性znode的特點之外歌粥,znode的名字具備順序性塌忽。
- 4)臨時順序節(jié)點(EPHEMERAL_SEQUENTIAL): znode除了具備臨時性znode的特點之外,zorde的名字具備順序性失驶。
- 5)Container節(jié)點 (3.5.3版本新增):Container容器節(jié)點土居,當容器中沒有任何子節(jié)點,該容器節(jié)點會被zk定期刪除(定時任務默認60s 檢查一次)。 和持久節(jié)點的區(qū)別是 ZK 服務端啟動后擦耀,會有一個單獨的線程去掃描棉圈,所有的容器節(jié)點,當發(fā)現(xiàn)容器節(jié)點的子節(jié)點數(shù)量為 0 時埂奈,會自動刪除該節(jié)點迄损。可以用于 leader 或者鎖的場景中账磺。
- 6) TTL節(jié)點: 帶過期時間節(jié)點芹敌,默認禁用,需要在zoo.cfg中添加 extendedTypesEnabled=true 開啟垮抗。 注意:ttl不能用于臨時節(jié)點
節(jié)點狀態(tài)信息:
- cZxid :Znode創(chuàng)建的事務id氏捞。
- ctime:節(jié)點創(chuàng)建時的時間戳。
- mZxid :Znode被修改的事務id冒版,即每次對znode的修改都會更新mZxid液茎。
對于zk來說,每次的變化都會產(chǎn)生一個唯一的事務id辞嗡,zxid(ZooKeeper Transaction Id)捆等,通過zxid,可以確定更新操作的先后順序续室。例如栋烤,如果zxid1小于zxid2,說明zxid1操作先于zxid2發(fā)生挺狰,zxid對于整個zk都是唯一的明郭,即使操作的是不同的znode。 - pZxid: 表示該節(jié)點的子節(jié)點列表最后一次修改的事務ID丰泊,添加子節(jié)點或刪除子節(jié)點就會影響子節(jié)點列表薯定,但是修改子節(jié)點的數(shù)據(jù)內(nèi)容則不影響該ID(注意: 只有子節(jié)點列表變更了才會變更pzxid,子節(jié)點內(nèi)容變更不會影響pzxid)
- mtime:節(jié)點最新一次更新發(fā)生時的時間戳.
- cversion :子節(jié)點的版本號瞳购。當znode的子節(jié)點有變化時话侄,cversion 的值就會增加1。
- dataVersion:數(shù)據(jù)版本號学赛,每次對節(jié)點進行set操作年堆,dataVersion的值都會增加1(即使設置的是相同的數(shù)據(jù)),可有效避免了數(shù)據(jù)更新時出現(xiàn)的先后順序問題罢屈。
- ephemeralOwner:如果該節(jié)點為臨時節(jié)點, ephemeralOwner值表示與該節(jié)點綁定的session id嘀韧。如果不是, ephemeralOwner值為0(持久節(jié)點)篇亭。
在client和server通信之前,首先需要建立連接,該連接稱為session缠捌。連接建立后,如果發(fā)生連接超時、授權(quán)失敗,或者顯式關(guān)閉連接,連接便處于closed狀態(tài), 此時session結(jié)束。 - dataLength : 數(shù)據(jù)的長度
- numChildren :子節(jié)點的數(shù)量(只統(tǒng)計直接子節(jié)點的數(shù)量)
監(jiān)聽通知(watcher)機制
- 一個Watch事件是一個一次性的觸發(fā)器曼月,當被設置了Watch的數(shù)據(jù)發(fā)生了改變的時候谊却,則服務器將這個改變發(fā)送給設置了Watch的客戶端,以便通知它們哑芹。
- Zookeeper采用了 Watcher機制實現(xiàn)數(shù)據(jù)的發(fā)布訂閱功能炎辨,多個訂閱者可同時監(jiān)聽某一特定主題對象,當該主題對象的自身狀態(tài)發(fā)生變化時例如節(jié)點內(nèi)容改變聪姿、節(jié)點下的子節(jié)點列表改變等碴萧,會實時、主動通知所有訂閱者末购。
- watcher機制事件上與觀察者模式類似破喻,也可看作是一種觀察者模式在分布式場景下的實現(xiàn)方式。
watcher的過程:
- 客戶端向服務端注冊watcher
- 服務端事件發(fā)生觸發(fā)watcher
- 客戶端回調(diào)watcher得到觸發(fā)事件情況
注意:Zookeeper中的watch機制盟榴,必須客戶端先去服務端注冊監(jiān)聽曹质,這樣事件發(fā)送才會觸發(fā)監(jiān)聽,通知給客戶端擎场。
支持的事件類型:
- None: 連接建立事件
- NodeCreated: 節(jié)點創(chuàng)建
- NodeDeleted: 節(jié)點刪除
- NodeDataChanged:節(jié)點數(shù)據(jù)變化
- NodeChildrenChanged:子節(jié)點列表變化
- DataWatchRemoved:節(jié)點監(jiān)聽被移除
- ChildWatchRemoved:子節(jié)點監(jiān)聽被移除
永久性Watch
在被觸發(fā)之后羽德,仍然保留,可以繼續(xù)監(jiān)聽ZNode上的變更迅办,是Zookeeper 3.6.0版本新增的功能
addWatch的作用是針對指定節(jié)點添加事件監(jiān)聽宅静,支持兩種模式
- PERSISTENT,持久化訂閱礼饱,針對當前節(jié)點的修改和刪除事件坏为,以及當前節(jié)點的子節(jié)點的刪除和新增事件。
- PERSISTENT_RECURSIVE镊绪,持久化遞歸訂閱匀伏,在PERSISTENT的基礎(chǔ)上,增加了子節(jié)點修改的事件觸發(fā)蝴韭,以及子節(jié)點的子節(jié)點的數(shù)據(jù)變化都會觸發(fā)相關(guān)事件(滿足遞歸訂閱特性)
4.Zookeeper集群
集群角色
- Leader: 領(lǐng)導者够颠。
事務請求(寫操作)的唯一調(diào)度者和處理者,保證集群事務處理的順序性榄鉴;集群內(nèi)部各個服務器的調(diào)度者履磨。對于create、setData庆尘、delete等有寫操作的請求剃诅,則要統(tǒng)一轉(zhuǎn)發(fā)給leader處理,leader需要決定編號驶忌、執(zhí)行操作矛辕,這個過程稱為事務。 - Follower: 跟隨者
處理客戶端非事務(讀操作)請求(可以直接響應),轉(zhuǎn)發(fā)事務請求給Leader聊品;參與集群Leader選舉投票飞蹂。 - Observer: 觀察者
對于非事務請求可以獨立處理(讀操作),對于事務性請求會轉(zhuǎn)發(fā)給leader處理翻屈。Observer節(jié)點接收來自leader的inform信息陈哑,更新自己的本地存儲,不參與提交和選舉投票伸眶。通常在不影響集群事務處理能力的前提下提升集群的非事務處理能力惊窖。
Observer應用場景:
- 提升集群的讀性能。因為Observer和不參與提交和選舉的投票過程厘贼,所以可以通過往集群里面添加observer節(jié)點來提高整個集群的讀性能爬坑。
- 跨數(shù)據(jù)中心部署。 比如需要部署一個北京和香港兩地都可以使用的zookeeper集群服務涂臣,并且要求北京和香港客戶的讀請求延遲都很低盾计。解決方案就是把香港的節(jié)點都設置為observer。
leader節(jié)點可以處理讀寫請求赁遗,follower只可以處理讀請求署辉。follower在接到寫請求時會把寫請求轉(zhuǎn)發(fā)給leader來處理。
Zookeeper數(shù)據(jù)一致性保證:
- 全局可線性化(Linearizable )寫入∶先到達leader的寫請求會被先處理岩四,leader決定寫請求的執(zhí)行順序哭尝。
- 客戶端FIFO順序∶來自給定客戶端的請求按照發(fā)送順序執(zhí)行。
4.1 Zookeeper Leader 選舉原理
服務器啟動時的 leader 選舉
每個節(jié)點啟動的時候都 LOOKING 觀望狀態(tài)剖煌,接下來就開始進行選舉主流程材鹦。這里選取三臺機器組成的集群為例。第一臺服務器 server1啟動時耕姊,無法進行 leader 選舉桶唐,當?shù)诙_服務器 server2 啟動時,兩臺機器可以相互通信茉兰,進入 leader 選舉過程尤泽。
- 1)每臺 server 發(fā)出一個投票,由于是初始情況规脸,server1 和 server2 都將自己作為 leader 服務器進行投票坯约,每次投票包含所推舉的服務器myid、zxid莫鸭、epoch闹丐,使用(myid,zxid)表示被因,此時 server1 投票為(1,0)卿拴,server2 投票為(2,0)滥玷,然后將各自投票發(fā)送給集群中其他機器。
- 2)接收來自各個服務器的投票巍棱。集群中的每個服務器收到投票后,首先判斷該投票的有效性蛋欣,如檢查是否是本輪投票(epoch)航徙、是否來自 LOOKING 狀態(tài)的服務器。
- 3)分別處理投票陷虎。針對每一次投票到踏,服務器都需要將其他服務器的投票和自己的投票進行對比,對比規(guī)則如下:
?a. 優(yōu)先比較 epoch
?b. 檢查 zxid尚猿,zxid 比較大的服務器優(yōu)先作為 leader
?c. 如果 zxid 相同窝稿,那么就比較 myid,myid 較大的服務器作為 leader 服務器 - 4)統(tǒng)計投票凿掂。每次投票后伴榔,服務器統(tǒng)計投票信息,判斷是都有過半機器接收到相同的投票信息庄萎。server1踪少、server2 都統(tǒng)計出集群中有兩臺機器接受了(2,0)的投票信息,此時已經(jīng)選出了 server2 為 leader 節(jié)點糠涛。
- 5)改變服務器狀態(tài)援奢。一旦確定了 leader,每個服務器響應更新自己的狀態(tài)忍捡,如果是 follower集漾,那么就變更為 FOLLOWING,如果是 Leader砸脊,變更為 LEADING具篇。此時 server3繼續(xù)啟動,直接加入變更自己為 FOLLOWING凌埂。
運行過程中的 leader 選舉
當集群中 leader 服務器出現(xiàn)宕機或者不可用情況時栽连,整個集群無法對外提供服務,進入新一輪的 leader 選舉侨舆。
- 1)變更狀態(tài)秒紧。leader 掛后,其他非 Oberver服務器將自身服務器狀態(tài)變更為 LOOKING挨下。
- 2)每個 server 發(fā)出一個投票熔恢。在運行期間,每個服務器上 zxid 可能不同臭笆。
- 3)處理投票叙淌。規(guī)則同啟動過程秤掌。
- 4)統(tǒng)計投票。與啟動過程相同鹰霍。
- 5)改變服務器狀態(tài)闻鉴。與啟動過程相同。
4.2 Zookeeper 數(shù)據(jù)同步流程
在 Zookeeper 中茂洒,主要依賴 ZAB 協(xié)議來實現(xiàn)分布式數(shù)據(jù)一致性孟岛。
ZAB 協(xié)議分為兩部分:
- 消息廣播
- 崩潰恢復
消息廣播
Zookeeper 使用單一的主進程 Leader 來接收和處理客戶端所有事務請求,并采用 ZAB 協(xié)議的原子廣播協(xié)議督勺,將事務請求以 Proposal 提議廣播到所有 Follower 節(jié)點渠羞,當集群中有過半的Follower 服務器進行正確的 ACK 反饋,那么Leader就會再次向所有的 Follower 服務器發(fā)送commit 消息智哀,將此次提案進行提交次询。這個過程可以簡稱為 2pc 事務提交,整個流程可以參考下圖瓷叫,注意 Observer 節(jié)點只負責同步 Leader 數(shù)據(jù)屯吊,不參與 2PC 數(shù)據(jù)同步過程。
崩潰恢復
在正常情況消息下廣播能運行良好摹菠,但是一旦 Leader 服務器出現(xiàn)崩潰雌芽,或者由于網(wǎng)絡原理導致 Leader 服務器失去了與過半 Follower 的通信,那么就會進入崩潰恢復模式辨嗽,需要選舉出一個新的 Leader 服務器世落。在這個過程中可能會出現(xiàn)兩種數(shù)據(jù)不一致性的隱患,需要 ZAB 協(xié)議的特性進行避免糟需。
- Leader 服務器將消息 commit 發(fā)出后屉佳,立即崩潰
- Leader 服務器剛提出 proposal 后,立即崩潰
ZAB 協(xié)議的恢復模式使用了以下策略:
- 選舉 zxid 最大的節(jié)點作為新的 leader
- 新 leader 將事務日志中尚未提交的消息進行處理
5.客戶端
5.1 原生客戶端
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, ZKClientConfig)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider, ZKClientConfig)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, ZKClientConfig)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long, byte[])
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long, byte[], boolean, HostProvider)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long, byte[], boolean, HostProvider, ZKClientConfig)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long, byte[], boolean)
5.2 Curator
Curator 是一套由netflix 公司開源的洲押,Java 語言編程的 ZooKeeper 客戶端框架武花,Curator項目是現(xiàn)在ZooKeeper 客戶端中使用最多,對ZooKeeper 版本支持最好的第三方客戶端杈帐,并推薦使用体箕,Curator 把我們平時常用的很多 ZooKeeper 服務開發(fā)功能做了封裝,例如 Leader 選舉挑童、分布式計數(shù)器累铅、分布式鎖。這就減少了技術(shù)人員在使用 ZooKeeper 時的大部分底層細節(jié)開發(fā)工作站叼。在會話重新連接娃兽、Watch 反復注冊、多種異常處理等使用場景中尽楔,用原生的 ZooKeeper 處理比較復雜投储。而在使用 Curator 時第练,由于其對這些功能都做了高度的封裝,使用起來更加簡單玛荞,不但減少了開發(fā)時間娇掏,而且增強了程序的可靠性。
curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPath(pathWithParent);
說明:
- guaranteed:該函數(shù)的功能如字面意思一樣勋眯,主要起到一個保障刪除成功的作用婴梧,其底層工作方式是:只要該客戶端的會話有效,就會在后臺持續(xù)發(fā)起刪除請求凡恍,直到該數(shù)據(jù)節(jié)點在 ZooKeeper 服務端被刪除。
- deletingChildrenIfNeeded:指定了該函數(shù)后怔球,系統(tǒng)在刪除該數(shù)據(jù)節(jié)點的時候會以遞歸的方式直接刪除其子節(jié)點嚼酝,以及子節(jié)點的子節(jié)點。
Curator Caches
Curator 引入了 Cache 來實現(xiàn)對 Zookeeper 服務端事件監(jiān)聽竟坛,Cache 事件監(jiān)聽可以理解為一個本地緩存視圖與遠程 Zookeeper 視圖的對比過程闽巩。Cache 提供了反復注冊的功能。Cache 分為兩類注冊類型:節(jié)點監(jiān)聽和子節(jié)點監(jiān)聽担汤。
6.應用場景
ZooKeeper適用于存儲和協(xié)同相關(guān)的關(guān)鍵數(shù)據(jù)涎跨,不適合用于大數(shù)據(jù)量存儲。
有了上述眾多節(jié)點特性崭歧,使得 zookeeper 能開發(fā)不出不同的經(jīng)典應用場景隅很,比如:
- 注冊中心
- 數(shù)據(jù)發(fā)布/訂閱(常用于實現(xiàn)配置中心)
- 負載均衡
- 命名服務
- 分布式協(xié)調(diào)/通知
- 集群管理
- Master選舉
- 分布式鎖
- 分布式隊列
6.1分布式鎖
目前分布式鎖,比較成熟率碾、主流的方案:
- 1)基于數(shù)據(jù)庫的分布式鎖叔营。db操作性能較差,并且有鎖表的風險所宰,一般不考慮绒尊。
- 2)基于Redis的分布式鎖。適用于并發(fā)量很大仔粥、性能要求很高而可靠性問題可以通過其他方案去彌補的場景婴谱。
- 3)基于ZooKeeper的分布式鎖。適用于高可靠(高可用)躯泰,而并發(fā)量不是太高的場景谭羔。
基于Zookeeper設計思路一
使用臨時 znode 來表示獲取鎖的請求,創(chuàng)建 znode成功的用戶拿到鎖麦向。
思考:上述設計存在什么問題口糕?
如果所有的鎖請求者都 watch 鎖持有者,當代表鎖持有者的 znode 被刪除以后磕蛇,所有的鎖請求者都會通知到景描,但是只有一個鎖請求者能拿到鎖十办。這就是羊群效應。
基于Zookeeper設計思路二
使用臨時順序 znode 來表示獲取鎖的請求超棺,創(chuàng)建最小后綴數(shù)字 znode 的用戶成功拿到鎖向族。
Curator 可重入分布式鎖工作流程
總結(jié)
- 優(yōu)點:ZooKeeper分布式鎖(如InterProcessMutex),具備高可用棠绘、可重入件相、阻塞鎖特性,可解決失效死鎖問題氧苍,使用起來也較為簡單夜矗。
- 缺點:因為需要頻繁的創(chuàng)建和刪除節(jié)點,性能上不如Redis让虐。
在高性能紊撕、高并發(fā)的應用場景下,不建議使用ZooKeeper的分布式鎖赡突。而由于ZooKeeper的高可用性对扶,因此在并發(fā)量不是太高的應用場景中,還是推薦使用ZooKeeper的分布式鎖惭缰。
6.2 注冊中心
Spring Cloud整合Zookeeper注冊中心核心源碼入口: ZookeeperDiscoveryClientConfiguration浪南。
參考
- 圖靈學院課程vip.tulingxueyuan.cn