日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Zookeeper的典型应用场景(2)

發布時間:2024/4/13 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Zookeeper的典型应用场景(2) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

此文知識來自于:《從Paxos到Zookeeper分布式一致性原理與實踐》第六章

  • 集群管理(子節點)
  • Master選舉(同時創建節點)
  • 分布式鎖(同時創建節點)
  • 分布式隊列(創建順序節點)

  • 5.1 集群管理

    隨著分布式系統規模的日益擴大,集群中的機器規模也隨之變大,因此,如何更好地進行集群管理也顯得越來越重要了。

    所謂集群管理,包括集群監控集群控制兩大塊、前者側重對集群運行時狀態的收集,后者則是對集群進行操作與控制。在日常開發和運維過程中,我們經常會有
    類似于如下的需求。

    • 希望知道當前集群中究竟有多少機器在工作。
    • 對集群中每臺機器的運行時狀態進行數據收集。
    • 對集群中機器進行上下線操作。

    在傳統的基于Agent的分布式集群管理體系中,都是通過在集群中的每臺機器上部署一個Agent,由這個Agent負責主動向指定的一個監控中心系統(監控中心
    系統負責將所有數據進行集中處理,形成一系列報表,并負責實時報警,以下簡稱“監控中心”)匯報自己所在機器的狀態。在集群規模適中的場景下,這確實
    是一種在生產實踐中廣泛使用的解決方案,能夠快速有效地實現分布式環境集群監控,但是一旦系統的業務場景增多,集群規模變大,該解決方案的弊端也就顯現出來了:

    • 大規模升級困難:以客戶端形式存在的Agent,在大規模使用后,一旦遇到需要大規模升級的情況,就非常麻煩,在升級成本和升級進度的控制上面臨巨大的挑戰。
    • 統一的Agent無法滿足多樣的需求:對于機器的CPU使用率、負載(Load)、內存使用率、網絡吞吐以及磁盤容量等機器基本的物理狀態,使用統一的Agent
      來進行監控或許都可以滿足。但是,如果需要深入應用內部,對一些業務狀態進行監控,例如,在一個分布式消息中間件中,希望監控到每個消費者對消息的消費狀態;
      或者在一個分布式任務調度系統中,需要對每個機器上任務的執行情況進行監控。很顯然,對于這些業務耦合緊密的監控需求,不適合由一個統一的Agent來提供。
    • 編程語言多樣性:隨著越來越多編程語言的出現,各種異構系統層出不窮。如果使用傳統的Agent方式,那么需要提供各種語言的Agent客戶端。另一方面,
      “監控中心”在對異構系統的數據進行整合上面臨巨大挑戰。

    ZooKeeper具有以下兩大特性:

    • 客戶端如果對ZooKeeper的一個數據節點注冊Watcher監聽,那么當該數據節點的內容或是其子節點列表發生變更時,ZooKeeper服務器就會向訂閱的
      客戶端發送變更通知。
    • 對在ZooKeeper上創建的臨時節點,一旦客戶端與服務器之間的會話失效,那么該臨時節點也就被自動清除。

    利用ZooKeeper的這兩大特性,就可以實現另一種集群機器存活性監控的系統。例如,監控系統在/clusterServers節點上注冊一個Watcher監聽,
    那么但凡進行動態添加機器的操作,就會在/clusterServers節點下創建一個臨時節點/clusterServers/[Hostname]。這樣一來監控系統就能夠實時
    檢測到機器的變動情況,至于后續處理就是監控系統的業務了。下面我們就通過分布式日志收集系統和在線云主機管理這兩個典型例子來看看如何使用ZooKeeper
    實現集群管理。

    5.1.1 分布式日志收集系統

    分布式日志收集系統的核心工作就是收集分布在不同機器上的系統日志,在這里我們重點來看分布式日志系統的收集器模塊。

    在一個典型的日志系統的架構設計中,整個日志系統會把所有需要收集的日志機器(下文以“日志源機器”代表此類機器)分為多個組別,每個組別對應一個收集器,
    這個收集器其實就是一個后臺機器(下文以“收集器機器”代表此類機器),用于收集日志。對于大規模的分布式日志收集系統場景,通常需要解決如下兩個問題。

    • 變化的日志源機器:在生產環境中,伴隨著機器的變動,每個應用的機器幾乎每天都是在變化的(機器硬件問題、擴容、機房遷移或是網絡問題都會導致一個應用的機器變化),
      也就是說每個組別中的日志源機器通常是在不斷變化的。
    • 變化的收集器機器:日志收集系統自身也會有機器的變更或擴容,于是會出現新的收集器加入或是老的收集器機器退出的情況。

    上面兩個問題,無論是日志源機器還是收集器機器的變更,最終都歸結為一點:如何快速、合理、動態地為每個收集器分配對應的日志源機器,這也成為了整個
    日志系統正確穩定運轉的前提,也是日志收集過程中最大的技術挑戰。在這種情況下,引入ZooKeeper是個不錯的選擇,下面我們來看ZooKeeper在這個
    場景中的使用。

    .1 注冊收集器機器

    使用ZooKeeper來進行日志系統收集器的注冊、典型做法是在ZooKeeper上創建一個節點作為收集器的根節點,例如/logs/collector(下文我們以“收集器
    節點”代表該數據節點),每個收集器機器在啟動的時候,都會在收集器節點下創建自己的節點,例如logs/collector/[Hostname]。

    .2 任務分發

    待所有收集器機器都創建好自己對應的節點后,系統根據收集器節點下子節點的個數,將所有日志源機器分成對應的若干組,然后將分組后的機器列表分別寫到
    這些收集器機器創建的子節點(例如/logs/collector/host1)上去。這樣一來,每個收集器機器都能夠從自己對應的收集器節點獲取日志源機器列表,
    進而開始進行日志收集工作。

    .3 狀態匯報

    完成收集器機器的注冊以及任務分發后,我們還要考慮到這些機器隨時都有掛掉的可能。因此,針對這個問題,我們需要有一個收集器的狀態匯報機制:
    每個收集器機器在創建完自己的專屬節點后,還需要在對應的子節點上創建一個狀態子節點,例如/logs/collector/host1/status,每個收集器都需要定期向
    該節點寫入自己的狀態信息。我們可以把這種策略看作是一種檢測機制,通常收集器機器都會在這個節點寫入日志收集進度信息。日志系統根據該狀態子節點的最后更新時間
    來判斷對應的收集器機器是否存活。

    .4 動態分配

    如果收集器機器掛掉或是擴容了,就需要動態地進行收集任務的分配。在運行過程中,日志系統始終關注著/logs/collector這個節點下所有子節點的變更,
    一旦檢測到有收集器機器停止匯報或是有新的收集器機器加入,就要開始進行任務的重新分配。無論是針對收集器機器停止匯報還是新機器加入的情況,
    日志系統都需要將之前分配給該收集器的所有任務轉移。為了解決這個問題,通常有兩種做法。

    .4.1 全局動態分配

    這是一種簡單粗暴的做法,在出現收集器機器掛掉或是新機器加入的時候,日志系統需要根據新的收集器機器列表,立即對所有的日志源機器重新進行一次分組,
    然后將其分配給剩下的收集器機器。

    .4.2 局部動態分配

    全局動態分配方式雖然策略簡單,但是存在一個問題:一個或部分收集器機器的變更,就會導致全局動態任務的分配,影響面比較大,因此風險也就比較大。
    所謂局部動態分配,顧名思義就是在小范圍內進行任務的動態分配。在這種策略中,每個收集器機器在匯報自己日志收集狀態的同時,也會把自己的負載匯報上去。
    請注意,這里提到的負載并不僅僅只是簡單地指機器CPU負載(Load),而是一個對當前收集器任務執行的綜合評估。

    在這種策略中,如果一個收集器機器掛了,那么日志系統就會把之前分配給這個機器的任務重新分配到那些負載較低的機器上去。同樣,如果有新的收集器機器加入,
    會從那些負載高的機器上轉移部分任務給這個新加入的機器。

    .5 注意事項

    .5.1 節點類型

    首先看/logs/collector這個節點下面子節點的節點類型。這個節點下面的所有子節點都代表了每個收集器機器,那么初步認為這些子節點必須選擇臨時節點,
    原因是日志系統可以根據這些臨時節點來判斷收集器機器的存活性。但是,同時還需要注意的一點是:在分布式日志收集這個場景中,收集器節點上還會存放所有
    已經分配給該收集器機器的日志源機器列表,如果只是簡單地依靠ZooKeeper自身的臨時節點機制,那么當一個收集器掛掉或是當這個收集器機器中斷“心跳匯報”
    的時候,待該收集器節點的會話失效后,ZooKeeper就會立即刪除該節點,于是,記錄在該節點上的所有日志源機器列表也就隨之被清除掉了。

    從上面的描述中可以知道,臨時節點顯然無法滿足這里的業務需求,所以我們選擇了使用持久節點來標識每一個收集器機器,同時在這個持久節點下面分別創建
    /logs/collector/[Hostname]/status節點來表征每一個收集器機器的狀態。這樣一來,既能實現日志系統對所有收集器的監控,同時在收集器機器掛掉
    后,依然能夠準確地將分配于其中的任務還原。

    .5.2 日志系統節點監聽

    在實際生產運行過程中,每一個收集器機器更改自己狀態節點的頻率可能非常高(如每秒1次或更短),而且收集器的數量可能非常大,如果日志系統監聽所有
    這些節點變化,那么通知的消息量可能會非常大。另一方面,在收集器機器正常工作的情況下,日志系統沒有必要去實時地接收每次節點狀態變更,因此大部分
    這些變更通知都是無用的。因此我們考慮放棄監聽設置,而是采用日志系統主動輪詢收集器節點的策略,這樣就節省了不少網卡流量,唯一的缺陷就是有
    一定的延時(考慮到分布式日志收集系統的定位,這個延時是可以接受的)。

    5.1.2 在線云主機管理

    在線云主機管理通常出現在那些虛擬主機提供商的應用場景中。在這類集群管理中,有很重要的一塊就是集群機器的監控。這個場景通常對于集群中的機器狀態,
    尤其是機器在線率的統計有較高的要求,同時需要能夠快速地對集群中機器的變更做出響應。

    在傳統的實現方案中,監控系統通過某種手段(比如檢測主機的指定端口)來對每臺機器進行定時檢測,或者每臺機器自己定時向監控系統匯報“我還活著”。
    但是這種方式需要每個業務系統的開發人員自己來處理網絡通信、協議設計、調度和容災等諸多瑣碎的問題。下面來看看使用ZooKeeper實現的另一種集群機器
    存活性監控系統。針對這個系統,我們的需求點通常如下。

    • 如何快速地統計當前生產環境一共有多少臺機器?
    • 如何快速地獲取到機器上/下線的情況?
    • 如何實時監控集群中每臺主機的運行時狀態?

    .1 機器上/下線

    為了實現自動化的線上運維,我們必須對機器的上/下線情況有一個全局的監控。通常在新增機器的時候,需要首先將指定的Agent部署到這些機器上去。
    Agent部署啟動之后,會首先向ZooKeeper的指定節點進行注冊,具體的做法就是在機器列表節點下面創建一個臨時子節點,例如/XAE/machine/[Hostname]
    (下文以“主機節點”代表這個節點),如下圖:


    當Agent在ZooKeeper上創建完這個臨時子節點后,對/XAE/machines節點關注的監控中心就會接收到“子節點變更”事件,即上線通知,于是就可以對這個
    新加入的機器開啟相應的后臺管理邏輯。另一方面,監控中心同樣可以獲取到機器下線的通知,這樣便實現了對機器上/下線的檢測,同時能夠很容易地獲取
    到在線的機器列表,對于大規模的擴容和容量評估都有很大的幫助。

    ?

    .2 機器監控

    對于一個在線云主機系統,不僅要對機器的在線狀態進行檢測,還需要對機器的運行時狀態進行監控。在運行的過程中,Agent會定時將主機的運行狀態信息
    寫入ZooKeeper上的主機節點,監控中心通過訂閱這些節點的數據變更通知來間接地獲取主機的運行時信息。

    隨著分布式系統規模變得越來越龐大,對集群機器的監控和管理顯得越來越重要。上面提到的這種借助ZooKeeper來實現的方式,不僅能夠實時地檢測到集群
    中機器的上/下線情況,而且能夠實時地獲取到主機的運行時信息,從而能夠構建出一個大規模集群的主機圖譜。

    6.1 Master選舉

    Master選舉是一個在分布式系統中非常常見的應用場景。分布式最核心的特性就是能夠將具有獨立計算能力的系統單元部署在不同的機器上,構成一個完整的
    分布式系統。而與此同時,實際場景中往往也需要在這些分布在不同機器上的獨立系統單元中選出一個所謂的“老大”,在計算機科學中,我們稱之為“Master”。

    在分布式系統中,Master往往用來協調集群中其他系統單元,具有對分布式系統狀態變更的決定權。例如,在一些讀寫分離的應用場景中,客戶端的寫請求往往
    是由Master來處理的;而在另一些場景中,Master則常常負責處理一些復雜的邏輯,并將處理結果同步給集群中其它系統單元。Master選舉可以說是ZooKeeper
    最典型的應用場景了,在本節中,我們就結合“一種海量數據處理與共享模型”這個具體例子來看看ZooKeeper在集群Master選舉中的應用場景。

    在分布式環境中,經常會碰到這樣的應用場景:集群中的所有系統單元需要對前端業務提供數據,比如一個商品ID,或者是一個網站輪播廣告的廣告ID(通常
    出現在一些廣告投放系統中)等,而這些商品ID或是廣告ID往往需要從一系列的海量數據處理中計算得到————這通常是一個非常耗費I/O和CPU資源的過程。
    鑒于該計算過程的復雜性,如果讓集群中的所有機器都執行這個計算邏輯的話,那么將耗費非常多的資源。一種比較好的方法就是只讓集群中的部分,甚至只
    讓其中的一臺機器去處理數據計算,一旦計算出數據結果,就可以共享給整個集群中的其他所有客戶端機器,這樣可以大大減少重復勞動,提升性能。

    這里我們以一個簡單的廣告投放系統后臺場景為例來講解這個模型。整個系統大體上可以分成客戶端集群、分布式緩存系統、海量數據處理總線和ZooKeeper
    四個部分,如下圖:


    Client集群每天定時會通過ZooKeeper來實現Master選舉。選舉產生Master客戶端之后,這個Master就會負責進行一系列的海量數據處理,最終計算得到
    一個數據結果,并將其放置在一個內存/數據庫中。同時,Master還需要通知集群中其它所有的客戶端從這個內存/數據庫中共享計算結果。

    ?

    接下去,我們將重點來看Master選舉的過程,首先來明確下Master選舉的需求:在集群的所有機器中選舉出一臺機器作為Master。針對這個需求,通常情況
    下,我們可以選擇常見的關系型數據庫中的主鍵特性來實現:集群中的所有機器都向數據庫中插入一條相同主鍵ID的記錄,數據庫會幫助我們自動進行主鍵沖突
    檢查,也就是說,所有進行插入操作的客戶端機器中,只有一臺機器能夠成功————那么,我們就認為向數據庫中成功插入數據的客戶端機器成為Master。

    乍一看,這個方案確實可行,依靠關系型數據庫的主鍵特性能夠很好地保證在集群中選舉出唯一的一個Master。但是我們需要考慮的另一個問題是,如果當前
    選舉出的Master掛了,那么該如何處理?誰來告訴我Master掛了呢?顯然,關系型數據庫沒法通知我們這個事件。

    ZooKeeper的強一致性,能夠很好地保證在分布式高并發情況下節點的創建一定能夠保證全局唯一性,即ZooKeeper將會保證客戶端無法重復創建一個已經存在
    的數據節點。也就是說,如果同時有多個客戶端請求創建同一個節點,那么最終一定只有一個客戶端請求能夠創建成功。利用這個特性,就能很容易地在分布式
    環境中進行Master選舉了。

    在這個系統中,首先會在ZooKeeper上創建一個日期節點,如下圖:


    客戶端集群每天都會定時往ZooKeeper上創建一個臨時節點,例如/master_election/2017-09-03/binding。在這個過程中,只有一個客戶端能夠成功
    創建這個節點,那么這個客戶端所在機器就稱為了Master。同時,其他沒有在ZooKeeper上成功創建節點的客戶端,都會在節點/master_ecection/2017-09-03
    上注冊一個子節點變更的Watcher,用于監控當前的Master機器是否存活,一旦發現當前的Master掛了,那么其余的客戶端將會重新進行Master選舉。

    ?

    從上面的講解中,我們可以看到,如果僅僅只是想實現Master選舉的話,那么其實只需要有一個能夠保證唯一性的組件即可,例如關系型數據庫的主鍵模型
    就是不錯的選擇。但是,如果希望能夠快速地進行集群Master動態選舉,那么基于ZooKeeper來實現是一個不錯的新思路。

    7.1 分布式鎖

    分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源,那么訪問這些資源的
    時候,往往需要通過一些互斥手段來防止彼此之間的干擾,以保證一致性,在這種情況下,就需要使用分布式鎖了。

    在平時的實際項目開發中,我們往往很少會去在意分布式鎖,而是依賴于關系型數據庫固有的排他性來實現不同進程之間的互斥。這確實是一種非常簡便且被
    廣泛使用的分布式鎖實現方式。然而有一個不爭的事實是,目前絕大多數大型分布式系統的性能瓶頸都集中在數據庫操作上。因此,如果上層業務再給數據庫
    添加一些額外的鎖,例如行鎖、表鎖甚至是繁重的事務處理,那么是不是會讓數據庫更加不堪重負呢?下面我們來看看使用ZooKeeper如何實現分布式鎖,
    這里主要講解排他鎖和共享鎖兩類分布式鎖。

    7.1.1 排他鎖

    排他鎖(Exclusive Locks,簡稱X鎖),又稱為寫鎖或獨占鎖,是一種基本的鎖類型。如果事務T1對數據對象O1加上了排他鎖,那么在整個加鎖期間,只允許
    事務T1對O1進行讀取和更新操作,其他任何事務都不能再對這個數據對象進行任何類型的操作————直到T1釋放了排他鎖。

    從上面講解的排他鎖的基本概念中,我們可以看到,排他鎖的核心是如何保證當前有且僅有一個事務獲得鎖,并且鎖被釋放后,所有正在等待獲取鎖的事務都
    能夠被通知到。下面我們就看看如何借助ZooKeeper實現排他鎖。

    .1 定義鎖

    有兩種常見的方式可以用來定義鎖,分別是synchronized機制和JDK5提供的ReentrantLock。然而,在ZooKeeper中,沒有類似于這樣的API可以直接使用,
    而是通過ZooKeeper上的數據節點來表示一個鎖,例如/exclusive_lock/lock節點就可以被定義為一個鎖,如下圖:

    ?

    .2 獲取鎖

    在需要獲取排他鎖時,所有的客戶端都會試圖通過調用create()接口,在/exclusive_lock節點下創建臨時子節點/exclusive_lock/lock。而ZooKeeper
    會保證在所有的客戶端中,最終只有一個客戶端能夠創建成功,那么就可以認為該客戶端獲取了鎖。同時,所有沒有獲取到鎖的客戶端就需要到/exclusive_lock
    節點上注冊一個子節點變更的Watcher監聽,以便實時監聽到lock節點的變更情況。

    .3 釋放鎖

    由于是臨時節點,有下面兩種情況,可能釋放鎖:

    • 當前獲取鎖的客戶端機器發生宕機
    • 正常執行完業務邏輯后,客戶端主動將臨時節點刪除。

    無論在上面情況下移除了lock節點,ZooKeeper都會通知所有在/exclusive_lock節點上注冊了子節點變更Watcher監聽的客戶端。這些客戶端在接收到通知后,
    再次重新發起分布式鎖獲取,即重復“獲取鎖”過程。如下圖:

    ?

    7.1.2 共享鎖

    共享鎖(Shared Locks,簡稱S鎖),又稱讀鎖,同樣是一種基本的鎖類型。如果事務T1對數據對象O1加上了共享鎖,那么當前事務只能對O1進行讀取操作,
    其他事務也只能對這個數據對象加共享鎖————直到該數據對象上的所有共享鎖都被釋放。

    共享鎖和排他鎖最根本的區別在于,加上排他鎖后,數據對象只對一個事務可見,而加上共享鎖后,數據對所有事務都可見。

    .1 定義鎖

    和排他鎖一樣,同樣是通過ZooKeeper上的數據節點來表示一個鎖,是一個類似于/shared_lock/[Hostname]-請求類型-序號的臨時順序節點,例如
    /shared_lock/192.168.0.1-R-0000000001,那么,這個節點就代表了一個共享鎖,如下圖:

    ?

    .2 獲取鎖

    在需要獲取共享鎖時,所有客戶端都會到/shared_lock這個節點下面創建一個臨時順序節點,如果當前是讀請求,那么就創建例如/shared_lock/192.168.0.1-R-000000001/
    的節點;如果是寫請求,那么就創建例如/shared_lock/192.168.0.1-W-000000001的節點。

    .3 判斷讀寫順序

    根據共享鎖的定義,不同的事務都可以同時對同一數據對象進行讀取操作,而更新操作必須在當前沒有任何事務進行讀寫操作的情況下進行。基于這個原則,
    我們來看看如何通過ZooKeeper的節點來確定分布式讀寫順序,大致可以分為如下4個步驟。

  • 創建完節點后,獲取/shared_lock節點下的所有子節點,并對該節點注冊子節點變更的Watcher監聽。
  • 確定自己的節點序號在所有子節點中的順序。
  • 如果當前節點業務為讀請求:如果沒有比自己序號小的子節點,或是所有比自己序號小的子節點都是讀請求,那么表明自己已經成功獲取到了共享鎖,同時
    開始執行讀取邏輯。如果比自己序號小的子節點有寫請求,那么就需要進入等待。
    如果當前節點業務為寫請求:如果自己不是序號最小的子節點, 那么就需要進入等待。
  • 接收到Watcher通知后,重復步驟1。
  • .4 釋放鎖

    釋放鎖的邏輯和排他鎖是一致的。

    .5 羊群效應

    上面講解的這個共享鎖實現,大體上能夠滿足一般的分布式集群競爭鎖的需求,并且性能都還可以————這里說的一般場景是指集群規模不是特別大,一般是在
    10臺機器以內。但是如果機器規模擴大之后,會有什么問題呢?我們著重來看上面“判斷讀寫順序”過程的步驟3,如下圖,看看實際運行中的情況。

    ?

  • 192.168.0.1這臺機器首先進行讀操作,完成讀操作后將節點/192.168.0.1-R-000000001刪除。
  • 余下的4臺機器均收到了這個節點被移除的通知,然后重新從/shared_lock/節點上獲取一份新的子節點列表。
  • 每個機器判斷自己的讀寫順序。其中192.168.0.2這臺機器檢測到自己已經是序號最小的機器了,于是開始進行寫操作,而余下的其他機器發現沒有輪到
    自己進行讀取或更新操作,于是繼續等待。
  • 繼續......
  • 上面這個過程就是共享鎖在實際運行中最主要的步驟了,我們著重看下上面步驟3中提到的:“而余下的其他機器發現沒有輪到自己進行讀取或更新操作,于是繼續等待。”
    很明顯,我們看到,192.168.0.1這個客戶端在移除自己的共享鎖后,ZooKeeper發送了子節點變更Watcher通知給所有機器,然而這個通知除了給192.168.0.2
    這臺機器產生實際影響外,對于余下的其他所有機器都沒有任何作用。

    相信讀者也已經意思到了,在這整個分布式鎖的競爭過程中,大量的“Watcher通知”和“子節點列表獲取”兩個操作重復運行,并且絕大多數的運行結果都是
    判斷出自己并非是序號最小的節點,從而繼續等待下一次通知————這個看起來顯然不怎么科學。客戶端無端地接收到過多和自己并不相關的事件通知,如果在集群
    規模比較大的情況下,不僅會對ZooKeeper服務器造成巨大的性能影響和網絡沖擊,更為嚴重的是,如果同一時間有多個節點對應的客戶端完成事務或是事務
    中斷引起節點消息,ZooKeeper服務器就會在短時間內向其余客戶端發送大量的事件通知————這就是所謂的羊群效應。

    上面這個ZooKeeper分布式共享鎖實現中出現羊群效應的根源在于,沒有找準客戶端真正的關注點。我們再來回顧一下上面的分布式鎖競爭過程,它和核心
    邏輯在于:判斷自己是否是所有子節點中序號最小的。于是,很容易可以聯想到,每個節點對應的客戶端只需要關注比自己序號小的那個相關節點的變更情況
    就可以了————而不需要關注全局的子列表變更情況。

    .6 改進后的分布式鎖實現

    現在我們來看看如何改進上面的分布式鎖實現。首先,我們需要肯定的一點是,上面提到的共享鎖實現,從整體思路上來說完全正確。這里主要的改動在于:
    每個鎖競爭者,只需要關注/shared_lock/節點下序號比自己小的那個節點是否存在即可,具體實現如下:

  • 客戶端調用create()方法創建一個類似于/shared_lock/[Hostname]-請求類型-序號的臨時順序節點。
  • 客戶端調用getChildren()接口來獲取所有已經創建的子節點列表,注意,這里不注冊任何Watcher。
  • 如果無法獲取共享鎖,那么就調用exist()來對比自己小的那個節點注冊Watcher。注意,這里“比自己小的節點”只是一個籠統的說法,具體對于讀請求和寫請求不一樣。
    讀請求:向比自己序號小的最后一個寫請求節點注冊Watcher監聽。
    寫請求:向比自己序號小的最后一個節點注冊Watcher監聽。
  • 等待Watcher通知,繼續進入步驟2。
  • 流程圖如下:

    ?

    .7 注意

    看到這里,相信很多讀者都會覺得改進后的分布式鎖實現相對來說比較麻煩。確實如此,如同在多線程并發編程實踐中,我們會去盡量縮小鎖的范圍————對于
    分布式鎖實現的改進其實也是同樣的思路。那么對于開發人員來說,是否必須按照改進后的思路來設計實現自己的分布式鎖呢?答案是否定的。在具體的實際開發
    過程中,我們提倡根據具體的業務場景和集群規模來選擇適合自己的分布式鎖實現:在集群規模不大、網絡資源豐富的情況下,第一種分布式鎖實現方式是
    簡單實用的選擇;而如果集群規模達到一定程度,并且希望能夠精細化地控制分布式鎖機制,那么不妨試試改進版的分布式鎖實現。

    8.1 分布式隊列

    業界有不少分布式隊列產品,不過絕大多數都是類似于ActiveMQ、Kafka等的消息中間件。在本節中,我們主要介紹基于ZooKeeper實現的分布式隊列。
    分布式隊列,簡單地講分為兩大類,一種是常規的先入先出隊列,另一種則是要等到隊列元素集聚之后才統一安排執行的Barrier模型。

    8.1.1 FIFO:先進先出

    使用ZooKeeper實現FIFO隊列,和共享鎖的實現非常類似。FIFO隊列就類似于一個全寫的共享鎖模型,大體的設計思想其實非常簡單:所有客戶端都會到
    /queue_fifo這個節點下面創建一個臨時順序節點,例如/queue_fifo/192.168.0.1-0000000001,如下圖:


    創建完節點之后,根據如下4個步驟來確定執行順序。

    ?

  • 通過調用getChildren()接口來獲取/queue_fifo節點下的所有子節點,即獲取隊列中所有的元素。
  • 確定自己的節點序號在所有子節點中的順序。
  • 如果自己不是序號最小的子節點,那么就需要進入等待,同時向比自己序號小的最后一個節點注冊Watcher監聽。
  • 接收到Watcher通知到,重復步驟1。
  • 整個FIFO隊列的工作流程,如下圖:

    ?

    8.1.2 Barrier:分布式屏障

    Barrier原意是指障礙物、屏障,而在分布式系統中,特指系統之間的一個協調條件,規定了一個隊列的元素必須都集聚后才能統一進行安排,否則一直等待。
    這往往出現在那些大規模分布式并行計算的應用場景了:最終的合并計算需要基于很多并行計算的子結果來進行。這些隊列其實是FIFO隊列的基礎上進行了
    增強,大致的設計思想如下:開始時,/queue_barrier節點是一個已經存在的默認節點,并且將其節點的數據內容賦值為一個數字n來代表Barrier值,
    例如n=10表示只有當/queue_barrier節點下的子節點個數達到10后,才會打開Barrier。之后,所有的客戶端都會到/queue_barrier節點下創建一個
    臨時節點,例如/queue_barrier/192.168.0.1,如下圖:


    創建完節點之后,根據如下5個步驟來確定執行順序。

    ?

  • 通過調用getDate()接口獲取/queue_barrier節點的數據內容:10。
  • 通過調用getChildren()接口獲取/queue_barrier節點下的所有子節點,即獲取隊列中所有元素,同時注冊對子節點列表變更的Watcher監聽。
  • 統計子節點的個數。
  • 如果子節點個數還不足10個,那么就需要進入等待。
  • 接收到Watcher通知后,重復步驟2。


    博主理解為,如果在很少的時間內,同時超過了10個以上的業務機創建了臨時節點,那么業務處理的速度并不是恒定的,因為有可能這個業務被11個機器處理,
    下一個被12個業務機處理?


  • 作者:李文文丶
    鏈接:https://www.jianshu.com/p/bd01abf2eaae
    來源:簡書
    簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。

    總結

    以上是生活随笔為你收集整理的Zookeeper的典型应用场景(2)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。