<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ZooKeeper 原理 | ZooKeeper 網絡故障應對法

          共 5257字,需瀏覽 11分鐘

           ·

          2021-03-14 16:45

          網絡故障可以說是分布式系統(tǒng)設計的一生之敵。如果永遠不發(fā)生網絡故障,我們實際上可以設計出高可用強一致的分布式系統(tǒng)??上У氖蔷W絡故障在長時間運行的分布式系統(tǒng)中必然發(fā)生,ZooKeeper(ZK) 在運行過程中也會遇到網絡故障。

          首先,我們看看沒有故障的時候,ZK 如何處理網絡連接。

          ZK 客戶端啟動時,會從配置文件中讀取所有可用服務器的位置信息,隨后隨機地嘗試和其中一臺服務器連接。如果成功建立起連接,ZK 客戶端和服務器會建立起一個會話(session),在會話超時之前,服務器會響應客戶端的請求。每次新的請求都會刷新會話超時的時間,沒有業(yè)務請求的時候,客戶端也會通過定期的心跳來維持會話。當 ZK 客戶端和當前連接的服務器失聯(lián)時,客戶端會嘗試重新連接到可用服務器列表中的一臺服務器上。

          接下來,我們來了解網絡故障在 ZK 的世界里如何被抽象。

          網絡故障在 ZK 的層面被抽象為兩種異常,一種是 ConnectionLossException,另一種是 SessionExpireException。前者發(fā)生在 ZK 客戶端與當前服務器斷開之后,后者發(fā)生在 ZK 服務器通知客戶端會話超時的時候。

          ConnectionLossException

          這個異常是 ZK 中最讓人頭痛的異常之一。

          ZK 客戶端通過 socket 和 ZK 集群的某臺服務器連接,這個連接在客戶端由 ClientCnxn 管理,在服務器由 ServerCnxn 管理。ConnectionLossException 在 ZK 客戶端與當前服務器的連接異常關閉時拋出,它僅僅表明 ZK 客戶端發(fā)現(xiàn)自己與當前服務器的連接斷開,除此之外什么也不知道。因為不知道更多信息,實踐中,我們需要對不同的斷開原因進行探測和處理。

          從可恢復的故障中恢復

          ConnectionLossException 是一個可恢復的異常,它僅代表 ZK 客戶端與當前服務器的連接斷開,ZK 客戶端完全有可能稍后連接上另一個服務器并重新開始發(fā)送請求。

          在 ZK 集群網絡不穩(wěn)定的情況下,我們要特別小心地處理這類異常,不能直接層層外拋。否則,因為網絡抖動導致上層應用崩潰是不可接受的。

          同時,在這種異常情況下重新創(chuàng)建一個 ZK 客戶端開啟一個新的會話,只會加劇網絡的不穩(wěn)定性。這是因為 ZK 客戶端不重連的情況下,服務器只能通過會話超時來釋放與客戶端的連接。如果由于連接過多導致響應不穩(wěn)定,開啟新的會話只會惡化這個情況,原理類似于 DDOS 攻擊。

          一種常見的容忍 ConnectionLossException 的方式是重做動作,也就是形如下面代碼的處理邏輯。

          operation(...) {    zk.create(path, data, ids, mode, callback, data);}
          callback = (rc, path, ctx, name) -> { switch (Code.get(rc)) { case CONNECTIONLOSS: operation(...); break; }}

          操作可能已經在服務器上成功

          在上一節(jié)中,我們介紹了通過重做動作來從 ConnectionLossException 中恢復的方法。然而,重做動作是有風險的,這是因為先前的動作可能在客戶端上已經成功。如果當前動作是寫動作且不冪等,就可能在應用層面觀察到意圖與實際執(zhí)行的操作不一致。

          ConnectionLossException 僅代表 ZK 客戶端與當前服務器的連接斷開。但是,在斷開之前,對應的請求可能已經發(fā)送出去,已經到達服務器,并被處理。只是由于客戶端與服務器的連接斷開,導致 ZK 客戶端在收到回應之前拋出 ConnectionLossException。

          對于讀操作,重試通常沒有什么問題,因為我們總能得到重試成功的時候讀操作應有的返回值或異常。

          對于寫操作,情況則復雜一些,我們分開來討論。

          對于 setData 操作,在重試成功的情況下,不考慮具體的業(yè)務邏輯,我們可以認為問題不大。因為兩次把節(jié)點設置為同一個值是冪等操作,對于前一次操作更新了 version 從而導致重試操作 version 不匹配的情況,我們也可以對應處理這種可解釋的異常。

          對于 delete 操作,重試可能導致意外的 NoNodeException,我們可以吞掉這個異常或者觸發(fā)業(yè)務相關的異常邏輯。

          對于 create 操作,情況則再復雜一點。在不帶 sequential 要求的情況下,create 可能成功或者觸發(fā)一個 NodeExistException,可以采取跟 delete 對應的處理方式;在 sequential 的情況下,有可能先前的操作已經成功,而重試的操作也成功,也就是創(chuàng)建了兩個 sequential 節(jié)點。由于我們丟失了先前操作的返回值,因此先前操作的 sequential 節(jié)點就成了孤兒,這有可能導致資源泄露或者一致性問題。

          例如,基于 ZK 的一種 leader 選舉算法依賴于 sequential 節(jié)點的排序,一個序號最小的孤兒節(jié)點將導致整個算法無法推進且無法產生 leader。在這種情況下,孤兒節(jié)點獲取了 leader 權限,但其 callback 卻在早前被 ConnectionLossException 觸發(fā)了,因此當選 leader 及后續(xù)響應無法觸發(fā)。同時,由于該節(jié)點成為孤兒,它也不會被刪除,從而算法不再往下運行。

          Curator 作為 ZK 的客戶端庫,提供了 withProtection 和 idempotent 兩種設置方法來處理上面提到的問題。

          客戶端可能錯過狀態(tài)變化

          ZK 的 Watcher 是單次觸發(fā)的,前一次 Watcher 觸發(fā)到重新設置 Watcher 并觸發(fā)的間隔之間的事件可能會丟失。這本身是 ZK 上層應用需要考慮的一個重要的問題。

          ConnectionLossException 會觸發(fā) Watcher 接收到一個 WatchedEvent(EventType.None, KeeperState.Disconnected) 的事件。一旦收到這個事件,ZK 客戶端必須假定 ZK 上的狀態(tài)可能發(fā)生任意變化。對于依賴于某些狀態(tài)的回調,需要先被掛起,在恢復連接并確認狀態(tài)無誤之后再執(zhí)行回調。

          這里有一個技術細節(jié)需要注意,不同于一般的 WatchedEvent 會在觸發(fā) Watcher 后將其移除,EventType.None 的 WatchedEvent 在 disableAutoWatchReset 默認不啟動的情況下只會觸發(fā) Watcher 而不將其移除。同時,在成功重新連接服務器之后會將當前的所有 Watcher 通過 setWatches 請求重新注冊到服務器上。服務器通過對比 zxid 的數(shù)值來判斷是否觸發(fā) Watcher。從而避免了由于網絡抖動而強迫用戶代碼在 Watcher 的處理邏輯中處理 ConnectionLossException 并重新執(zhí)行操作設置 Watcher 的負擔。特別的,當前客戶端上注冊的所有的 Watcher 都將受到網絡抖動的影響。但是要注意重新注冊的 Watcher 中監(jiān)聽 NodeCreated 事件的 Watcher 可能會錯過該事件,這是因為在重新建立連接的過程中該節(jié)點由于其他客戶端的動作可能先被創(chuàng)建后被刪除,由于僅就有無節(jié)點判斷而沒有 zxid 來幫助判斷。也就是常說的 ABA 問題。

          SessionExpiredException

          這個異常比起 ConnectionLossException 來說是更加嚴重的故障,但卻更好處理一些。因為它是不可恢復的故障,所以我們無需考慮狀態(tài)恢復的問題。

          ZK 客戶端會話超時之后無法重新和服務器取回連接。因此,我們通常只需要重新創(chuàng)建一個 ZK 客戶端實例并重新開始開始工作。但是會話超時會導致 ephemeral 節(jié)點被刪除,如果上層應用邏輯與此相關的話,就需要相應地處理 SessionExpiredException。

          會話超時的檢測

          ZK 客戶端與服務器成功建立連接后,ClientCnxn.SendThread 線程會周期性的向服務器發(fā)送心跳請求,服務器在處理心跳請求時重置會話超時的時間。

          如果服務器在超時時間內沒有收到客戶端發(fā)來的任何新的請求,包括心跳請求和查詢、寫入等業(yè)務請求,那么它將宣布這個會話超時,并顯式的關掉對應的鏈接。

          ZK 會話超時相關的邏輯在 SessionTracker 類中。所有會話檢查和超時的判斷都是由 ZK 集群的 leader 作出的,也就是所謂的仲裁動作(quorum operation)。換句話說,客戶端超時是所有服務器的共識。

          如果 ZK 客戶端嘗試重新連接服務器,當它重新連接上某個服務器時,該服務器查詢會話列表,發(fā)現(xiàn)這個重連請求屬于超時會話,通過返回非正整數(shù)的超時剩余時間通知客戶端會話已超時。隨后,ZK 客戶端得知自己已經超時并執(zhí)行相應的退出邏輯。

          這就引出一個 tricky 的邏輯,ZK 客戶端的會話超時永遠是由服務器通知的。考慮這樣一種超時情況,在服務器掛了或者客戶端與服務器網絡分區(qū)的情況下,ZK 客戶端是無法得知自己的會話已經超時的。ZK 目前沒有辦法處理這一情況,只能依賴上層應用自己去處理。例如,通過其他邏輯確定會話已超時之后,主動地關閉 ZK 客戶端并重啟。

          Curator 作為 ZK 的客戶端庫,通過 ConnectionStateManager#processEvents 周期性檢測在收到最后一個 disconnect 事件后過去的時間,從而在必然超時的時候通過反射向 ZK 客戶端注入會話超時事件。

          ephemeral 節(jié)點的刪除

          ephemeral 節(jié)點的刪除發(fā)生在會話超時之后。實踐中與 ephemeral 節(jié)點的刪除相關的問題,主要是關于基于 ZK 的 leader 選舉的。

          ZK 提供了 leader 選舉的參考實現(xiàn)[1],這是一個基于有序的 ephemral sequential 節(jié)點隊列的算法。當上層應用采用這種算法做 leader 選舉時,如果 ZK 客戶端與服務器超時,由于處理 ZK 相關操作的線程與上層應用的線程常常是分開的,在上層應用異步地得知自己不是 leader 之前,就可能誤認自己還是 leader,從而作出越權的操作。

          例如,在 Flink 的設計中,只有 JobManager 中的 leader 才有權限寫 checkpoint 數(shù)據(jù)。

          但是,由于丟失 leadership 的消息,也就是對應 ephemral sequential 節(jié)點被刪除的消息,從 ZK 集群傳遞到 ZK 客戶端,在從 ZK 客戶端的線程通知到上層應用,這幾個步驟之間是異步的,丟失 leadership 的 JobManager leader 并不能第一時間得知這一情況。同時,其他的 JobManager 可能在同一時間段被通知當選 leader。此時,集群中就會有兩個 JobManager 認為自己是 leader,也就是常說的腦裂。

          如果對它們寫 checkpoint 數(shù)據(jù)的動作不做其他限制,就可能導致兩個 leader 并發(fā)地寫 checkpoint 數(shù)據(jù)而導致狀態(tài)不一致。這個由于響應時間帶來的問題在 Curator 的技術注意事項中已有提及[2],由于發(fā)生概率較小,而且僅在未能及時響應 ZK 服務器信息時才會發(fā)生,因此雖然不少系統(tǒng)都有這個理論上的 BUG,但是卻不愿意付出額外的努力來修復。

          FLINK-10333[3] 和 ZK 郵件列表上我發(fā)起的這個討論[4]詳細討論了這種情況下面臨的挑戰(zhàn)和解決方法。

          [1] https://zookeeper.apache.org/doc/r3.5.5/recipes.html#sc_leaderElection

          [2] https://cwiki.apache.org/confluence/display/CURATOR/TN10

          [3] https://issues.apache.org/jira/browse/FLINK-10333

          [4] https://lists.apache.org/x/thread.html/594b66ecb1d60b560a5c4c08ed1b2a67bc29143cb4e8d368da8c39b2@%3Cuser.zookeeper.apache.org%3E

          瀏覽 192
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩va中文字幕无码免费 | 成人午夜又粗又硬又大 | 日日射av. | 九九毛片| 精品久久久无码中文字幕 |