1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        ZooKeeper 原理 | ZooKeeper 狀態(tài)變化應(yīng)對(duì)法

        共 6133字,需瀏覽 13分鐘

         ·

        2021-03-14 16:45

        本文基于 ZooKeeper(ZK) 3.6.0 版本介紹應(yīng)對(duì)狀態(tài)變化的策略。

        ZK 的常見(jiàn)用途包括同步配置、服務(wù)發(fā)現(xiàn)和協(xié)同分布式過(guò)程等,這些用途都要求應(yīng)用程序能夠監(jiān)聽(tīng) ZK 節(jié)點(diǎn)集合的狀態(tài)。

        為了達(dá)到這個(gè)目的,ZK 客戶端可以輪詢 ZK 集合以獲取狀態(tài)。然而,輪詢并不是最佳的狀態(tài)監(jiān)聽(tīng)方式。對(duì)于頻繁變化的狀態(tài),輪詢可能會(huì)錯(cuò)過(guò)某些狀態(tài)變化;對(duì)于偶爾變化的狀態(tài),輪詢可能會(huì)導(dǎo)致額外的開(kāi)銷。

        基于這樣的觀察,ZK 提供了 Watcher 機(jī)制,能夠避開(kāi)輪詢來(lái)應(yīng)對(duì)狀態(tài)變化。這個(gè)機(jī)制允許應(yīng)用程序在特定的 znode 上注冊(cè) Watcher 以在 znode 的狀態(tài)發(fā)生變化的時(shí)候收到通知。

        通過(guò) Watcher 機(jī)制應(yīng)對(duì)狀態(tài)變化可以采用如下的框架代碼。

        zk.exist("/myZnode", myWatcher, existsCallback, ctx);
        Watcher myWatcher = new Watcher() { public void process(WatchedEvent e) { // process the watch event }}
        StatCallback existsCallback = new StatCallback() {  public void processResult(int rc, String path, Object ctx, Stat stat) { // process the result of the exist call }}

        這里我們?cè)O(shè)置了兩個(gè)回調(diào)邏輯。

        ?StatCallback 對(duì)應(yīng) ZK 客戶端異步請(qǐng)求的回調(diào),具體地說(shuō),是 exist 請(qǐng)求。?Watcher 對(duì)應(yīng)節(jié)點(diǎn)變化時(shí) ZK 客戶端收到 WatchedEvent 后的回調(diào)。

        這是兩個(gè)不同的回調(diào)邏輯,一個(gè)在客戶端請(qǐng)求完成是被調(diào)用,一個(gè)在監(jiān)視 znode 狀態(tài)發(fā)生變化是被調(diào)用。

        WatchedEvent 的類型

        可以看到,Watcher 的回調(diào)邏輯主要處理的是 WatchedEvent 對(duì)象,它是一個(gè)包括 KeeperState 和 EventType 兩個(gè)字段的數(shù)據(jù)對(duì)象。我們從分類的角度來(lái)看需要處理的 WatchedEvent 對(duì)象都有哪些可能。

        不同 KeeperState 的事件

        從 KeeperState 的角度來(lái)說(shuō),總共包含這幾種會(huì)話狀態(tài)。

        ?SyncConnected?ConnectedReadOnly?Disconnected?Expired?Closed?AuthFailed?SaslAuthFailed

        所有的會(huì)話狀態(tài)從名字即可看出其含義。除了 SyncConnected 之外,所有的 KeeperState 類型都會(huì)對(duì)應(yīng)到 EventType.None 事件類型,這代表沒(méi)有節(jié)點(diǎn)狀態(tài)變化,而是會(huì)話狀態(tài)發(fā)生了變化。

        ZK 使用了相同的 Watcher 機(jī)制來(lái)處理應(yīng)用程序相關(guān)事件的通知。這是某種程度的重載,在簡(jiǎn)化了類別區(qū)分和工程實(shí)現(xiàn)的同時(shí)也增加了用戶區(qū)分重載的心智負(fù)擔(dān)。

        不同 EventType 的事件

        SyncConnected 狀態(tài)代表會(huì)話正常,除了第一次連接上 ZK 時(shí)有一個(gè) EventType.None 事件類型的通知,其后都會(huì)與下列事件類型的某一種相關(guān)聯(lián)。

        ?NodeCreated?NodeDeleted?NodeDataChanged?NodeChildrenChanged?DataWatchRemove?ChildWatchRemoved?PersistentWatchRemoved

        其中最后三種事件對(duì)應(yīng) 3.5 和 3.6 版本以后支持的 Watcher 移除功能和永久 Watcher 對(duì)象的移除功能,我們稍后展開(kāi)其細(xì)節(jié)。

        前四種事件從名字即可看出其內(nèi)容,分別代表節(jié)點(diǎn)被創(chuàng)建、刪除、改變內(nèi)容或節(jié)點(diǎn)的子節(jié)點(diǎn)發(fā)生改變。對(duì)于 ZK 對(duì)節(jié)點(diǎn)狀態(tài)變化的抽象,有三點(diǎn)需要注意。

        第一點(diǎn),節(jié)點(diǎn)狀態(tài)變化事件實(shí)現(xiàn)上是一個(gè)單純的不帶信息的枚舉。

        換句話說(shuō),ZK 產(chǎn)生的節(jié)點(diǎn)狀態(tài)變化事件僅僅表達(dá)某事件已發(fā)生,而無(wú)法確定事件的具體內(nèi)容。特別是對(duì)于 NodeChildrenChanged 事件,僅代表節(jié)點(diǎn)的子節(jié)點(diǎn)發(fā)生改變,到底是新增子節(jié)點(diǎn)、子節(jié)點(diǎn)刪除還是子節(jié)點(diǎn)數(shù)據(jù)變化,都不清楚。這就要求 ZK 客戶端在收到事件時(shí)必須主動(dòng)再次發(fā)出請(qǐng)求查詢節(jié)點(diǎn)的狀態(tài)或數(shù)據(jù)內(nèi)容。

        這不同于 etcd 中帶有變更內(nèi)容的事件。如果通知中包含變更內(nèi)容,我們就有可能在客戶端僅接受一次通知,即在本地維護(hù)數(shù)據(jù)緩存的狀態(tài),從而無(wú)需再次請(qǐng)求查詢節(jié)點(diǎn)。

        第二點(diǎn),由于上述原因,ZK 原始的 Watcher 機(jī)制可能導(dǎo)致應(yīng)用程序錯(cuò)過(guò)中間過(guò)程的節(jié)點(diǎn)狀態(tài)變化。

        由于 ZK 實(shí)現(xiàn)上采用單次觸發(fā)語(yǔ)義,即設(shè)置的 Watcher 在出現(xiàn)狀態(tài)變化時(shí)觸發(fā)一次并被移除,Watcher 被移除后該節(jié)點(diǎn)發(fā)生的事件不再被監(jiān)視。即使 ZK 客戶端在收到事件后重新設(shè)置 Watcher 監(jiān)視,由于網(wǎng)絡(luò)傳輸天然的異步性質(zhì),仍然有可能錯(cuò)過(guò)事件。

        在《ZooKeeper 分布式過(guò)程協(xié)同技術(shù)詳解》一書(shū)中為這一點(diǎn)開(kāi)脫時(shí)提到,既然每次都需要重新拉取狀態(tài),那么單次觸發(fā)能夠在事件頻發(fā)的情況下減少事件平均產(chǎn)生的通知數(shù)量。但是實(shí)踐當(dāng)中這幾乎不成為一個(gè)好處,而從用戶角度來(lái)說(shuō)卻要麻煩地處理復(fù)雜的異步情況。

        ZK 3.6.0 版本引入了 Persistent (Recursive) Watcher 類型,能夠支持 Watcher 多次觸發(fā),使 ZK 客戶端不會(huì)錯(cuò)過(guò)任何一個(gè)事件。

        第三點(diǎn),Watcher 在服務(wù)器分為 data/child 兩類,在客戶端對(duì)其注冊(cè)分為 exist/data/child 三類。

        從客戶端角度來(lái)看,exist 請(qǐng)求能夠?qū)θ我饴窂接绕涫巧形磩?chuàng)建的路徑設(shè)置 Watcher 監(jiān)視,因此獨(dú)立擁有一類 Watcher 來(lái)處理。getData 和 getChildren 只能對(duì)已經(jīng)存在的節(jié)點(diǎn)路徑設(shè)置 Watcher 監(jiān)視,又根據(jù)其監(jiān)視的是節(jié)點(diǎn)本身或節(jié)點(diǎn)的子節(jié)點(diǎn),分別擁有一類 Watcher 來(lái)處理。

        從服務(wù)器角度來(lái)看,DataWatcher 僅監(jiān)視確切路徑對(duì)應(yīng)的節(jié)點(diǎn),在節(jié)點(diǎn)創(chuàng)建、刪除或者數(shù)據(jù)更改時(shí)產(chǎn)生通知,而 ChildWatcher 只有在節(jié)點(diǎn)的子節(jié)點(diǎn)發(fā)生變化時(shí)產(chǎn)生通知。由于服務(wù)器端的 DataWatcher 無(wú)所謂是否路徑對(duì)應(yīng)的節(jié)點(diǎn)是否存在,因此就少了一個(gè)分類。換個(gè)角度看,也可以認(rèn)為在 ZK 客戶端驅(qū)動(dòng)設(shè)置 Watcher 時(shí)已經(jīng)進(jìn)行過(guò)檢查,所以服務(wù)器端的 DataWatcher 可以合并情況而不會(huì)錯(cuò)誤的產(chǎn)生節(jié)點(diǎn)狀態(tài)變化事件。

        服務(wù)器的實(shí)現(xiàn)里,DataWatcher 和 ChildWatcher 由不同的集合管理,它們會(huì)響應(yīng)不同的事件。在移除 Watcher 時(shí),可以指定 Watcher 的類型是 DataWatcher 還是 ChildWatcher 又或者不做區(qū)分來(lái)移除。另外,上面提到的 PersistentWatcher 首先屬于 DataWatcher 類型,如果設(shè)置了 Recursive 參數(shù),則同時(shí)還是 ChildWatcher 類型。

        Watcher 的生命周期

        接下來(lái),我們針對(duì) Watcher 的設(shè)置到觸發(fā)的整個(gè)生命周期及其中可能遇到的異常做一個(gè)介紹。

        客戶端上 Watcher 的生命周期

        Watcher 的設(shè)置從 ZK 客戶端開(kāi)始,具體的分類不再做闡述,主要追蹤代碼的調(diào)用路徑。

        以 exist 為例,用戶傳入的 Watcher 對(duì)象或創(chuàng)建此客戶端時(shí)設(shè)置的默認(rèn) Watcher 被使用時(shí),有兩個(gè)代碼路徑被激活。

        其之一是發(fā)送到服務(wù)器的請(qǐng)求的 watch 字段將被設(shè)置為 true 以代表這次請(qǐng)求包含一個(gè) Watcher 設(shè)置的請(qǐng)求。

        其之二是 Watcher 被包裝在 ExistsWatchRegistration 中。經(jīng)過(guò)網(wǎng)絡(luò)層面的等候,在設(shè)置 Watcher 的請(qǐng)求成功時(shí),調(diào)用鏈進(jìn)入ClientCnxn#finishPacket 方法中,調(diào)用 ExistsWatchRegistration 的 register 方法在客戶端緩存 Watcher 的信息。

        緩存的 Watcher 信息主要用于支持 Watcher 在網(wǎng)絡(luò)錯(cuò)誤的情況下重新設(shè)置以及 Watcher 的移除。

        重新設(shè)置 Watcher 具體來(lái)說(shuō),是在發(fā)生 ConnectionLoss 異常時(shí),當(dāng) ZK 客戶端成功重新連接到服務(wù)器之后,根據(jù)自己緩存的 Watcher 信息,向服務(wù)器發(fā)送一個(gè) SetWatches 請(qǐng)求以重新設(shè)置此前還未觸發(fā)的 Watcher 集合。

        這個(gè)功能避免了用戶必須為了重新設(shè)置 Watcher 而響應(yīng) ConnectionLoss 異常的負(fù)擔(dān),尤其是在 ConnectionLoss 異常發(fā)生時(shí),無(wú)法預(yù)知節(jié)點(diǎn)發(fā)生了何種變化。

        另一方面,服務(wù)器端會(huì)比對(duì) SetWatches 請(qǐng)求的各個(gè) Watcher 對(duì)應(yīng)路徑節(jié)點(diǎn)的信息,根據(jù)是否存在節(jié)點(diǎn)以及節(jié)點(diǎn)最后處理的事務(wù) ID 和子節(jié)點(diǎn)事務(wù) ID 來(lái)判斷是否應(yīng)該產(chǎn)生事件。

        這里有兩個(gè)需要注意的點(diǎn)。

        一個(gè)是,服務(wù)器判斷是否應(yīng)該產(chǎn)生事件存在 False Negative 誤判的情況。具體來(lái)說(shuō),在通過(guò) exist 設(shè)置 Watcher 時(shí),對(duì)應(yīng)節(jié)點(diǎn)如果此前不存在,對(duì)應(yīng) Watcher 將被分類為 SetWatches 請(qǐng)求中的 existWatcher 類型。此時(shí),如果該節(jié)點(diǎn)經(jīng)歷了創(chuàng)建后又刪除的事件,則無(wú)法被 Watcher 所感知。這也是分布式系統(tǒng)常見(jiàn)問(wèn)題中所謂的 ABA 問(wèn)題。

        另一個(gè)是 SetWatchers 包含的 Watcher 集合是設(shè)置 Watcher 的成功請(qǐng)求的 Watcher 集合。前文提到,如果使用 ZK 客戶端的異步請(qǐng)求接口,實(shí)際上會(huì)有兩個(gè)回調(diào)。雖然 ZK 能夠自動(dòng)處理已經(jīng)注冊(cè)上的 Watcher 的容錯(cuò),但是如果異步請(qǐng)求本身沒(méi)有成功,則無(wú)法被這個(gè)自動(dòng)重設(shè)機(jī)制覆蓋。在這種情況下,應(yīng)用程序應(yīng)該自行重新執(zhí)行請(qǐng)求和設(shè)置 Watcher 的操作。

        關(guān)于 Watcher 的移除,這是在 3.5.0 版本引入的新功能。

        在此前的 ZK 版本中,Watcher 一旦設(shè)置就無(wú)法主動(dòng)移除。Watcher 的移除只有兩種途徑,一是 Watcher 被觸發(fā),二是會(huì)話超時(shí)或被關(guān)閉。應(yīng)用程序在某些情況下希望根據(jù)外部狀態(tài)的變化主動(dòng)移除 Watcher,尤其是 Watcher 數(shù)量巨大,而外部狀態(tài)指示這些 Watcher 再也不可能被觸發(fā)的情況下防止內(nèi)存泄漏的場(chǎng)景。移除 Watcher 指令確定移除集合也依賴于前文提到的本地緩存信息。同時(shí)需要定義新的客戶端和服務(wù)器的通信文本,在成功執(zhí)行時(shí)將產(chǎn)生前文所提的幾種 WatchRemoved 事件。

        服務(wù)器上 Watcher 的生命周期

        前面提到,在客戶端調(diào)用接口是設(shè)置 Watcher 的場(chǎng)景下,網(wǎng)絡(luò)請(qǐng)求包中的 watch 字段將被設(shè)置。這一信息將在服務(wù)器被處理。

        簡(jiǎn)單來(lái)說(shuō),經(jīng)過(guò)一系列序列化和請(qǐng)求處理責(zé)任鏈的檢查和裝飾后,設(shè)置 Watcher 的請(qǐng)求最終被發(fā)往 ZKDatabase 并委托給 DataTree 處理。DataTree 是服務(wù)器管理節(jié)點(diǎn)視圖的類。在進(jìn)行請(qǐng)求響應(yīng)的調(diào)用時(shí),會(huì)根據(jù)請(qǐng)求中 watch 字段的布爾值來(lái)判斷是否要設(shè)置一個(gè)服務(wù)器一側(cè)的 Watcher 對(duì)象。

        服務(wù)器一側(cè)的 Watcher 對(duì)象實(shí)際上是一個(gè) ServerCnxn 的實(shí)例,它繼承自 Watcher 接口。當(dāng)然,概念上來(lái)說(shuō)這樣的繼承有點(diǎn)勉強(qiáng),更好的實(shí)現(xiàn)方式是采用組合來(lái)替代繼承,以明確 ServerCnxn 本身不是 Watcher 接口,而是代行 Watcher 的職責(zé)。

        具體來(lái)說(shuō),ServerCnxn 是服務(wù)器一側(cè)維護(hù)的網(wǎng)絡(luò)連接對(duì)象,DataTree 在處理設(shè)置 Watcher 的請(qǐng)求時(shí)會(huì)將路徑和 ServerCnxn 這個(gè) Watcher 相關(guān)聯(lián),并且在 DataTree 的內(nèi)部維護(hù)一系列的 Watcher 對(duì)象的信息。在處理節(jié)點(diǎn)創(chuàng)建、刪除或數(shù)據(jù)改變等操作時(shí),根據(jù)節(jié)點(diǎn)視圖和 Watcher 信息判斷應(yīng)該觸發(fā)哪些 Watcher,而 Watcher 的觸發(fā)正是執(zhí)行 ServerCnxn 的 process 方法的邏輯,即向客戶端發(fā)送相應(yīng)的事件。

        DataTree 觸發(fā)相應(yīng)事件將委托到服務(wù)器一側(cè)的 WatchManager 處理,WatcherManager 在事件產(chǎn)生時(shí)調(diào)用 triggerWatch 方法來(lái)調(diào)用 ServerCnxn 的 process 方法。同時(shí)對(duì)于單次觸發(fā)的 Watcher 對(duì)象,將其從 Watcher 集合中移除,而對(duì)于 Persistent 的 Watcher 對(duì)象,則進(jìn)行保留。

        關(guān)于 Persistent Watcher,最后還有幾個(gè)需要討論的點(diǎn)。具體內(nèi)容可以參考 ISSUE [^1][^2] 和 GitHub PR[^3] 上的討論。

        第一個(gè),Persistent Watcher 提供是否是 Recursive 的選項(xiàng),設(shè)置 Recursive 將同時(shí)監(jiān)聽(tīng)給定路徑對(duì)應(yīng)的節(jié)點(diǎn)及其子節(jié)點(diǎn),否則只監(jiān)聽(tīng)給定路徑對(duì)應(yīng)的節(jié)點(diǎn)。對(duì)于監(jiān)聽(tīng)子節(jié)點(diǎn)的情況,為了減少每次遞歸查找的負(fù)擔(dān),實(shí)現(xiàn)上有一個(gè) PathParentIterator 的類來(lái)迭代獲取變更節(jié)點(diǎn)的父節(jié)點(diǎn)的優(yōu)化。

        第二個(gè),Persistent Watcher 對(duì)于前文和 etcd 做對(duì)比的情況,僅解除了單次觸發(fā)的限制,能夠?qū)Χ鄠€(gè)狀態(tài)變化對(duì)應(yīng)的產(chǎn)生事件。但是,事件的內(nèi)容仍然僅是事件已發(fā)生,而不包括事件的具體變更內(nèi)容??蛻舳嗽谑盏绞录笕匀灰匦抡?qǐng)求獲取節(jié)點(diǎn)狀態(tài)。為了追溯變更內(nèi)容,通常來(lái)說(shuō)需要引入某種 MVCC 的存儲(chǔ)。

        第三個(gè),Persistent Watcher 和前文所提的 Watcher 自動(dòng)容忍網(wǎng)絡(luò)錯(cuò)誤有一定的沖突。在具體的實(shí)現(xiàn)中,發(fā)生網(wǎng)絡(luò)錯(cuò)誤并重設(shè) Watcher 集合的時(shí)候,僅僅把 Persistent Watcher 重新設(shè)置,而不會(huì)檢測(cè)是否需要觸發(fā)期間錯(cuò)過(guò)的事件。這并沒(méi)有什么功能上的考量或者優(yōu)點(diǎn),僅僅是實(shí)現(xiàn)上會(huì)更復(fù)雜而需要 Persistent Watcher 這個(gè)特性的 Curator 庫(kù)能夠主動(dòng)的處理這個(gè)問(wèn)題。

        [^1] https://issues.apache.org/jira/browse/ZOOKEEPER-153

        [^2] https://issues.apache.org/jira/browse/ZOOKEEPER-1416

        [^3] https://github.com/apache/zookeeper/pull/1106


        瀏覽 61
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美老妇人性爱视频 | 噜噜噜躁狠狠躁夜夜踟木 | 九七色色大香蕉视频 | 自慰网站在线看 | 操逼视频无码 | 男人插女人视频软件 | 日韩精品人妻一区二区三区 | 台湾又色又爽又黄的大片网站 | 麻豆成人精品国产免费 | 国产黄色自拍 |