官术网_书友最值得收藏!

6.1 ZooKeeper簡介

ZooKeeper是一個分布式應(yīng)用程序協(xié)調(diào)服務(wù),主要用于解決分布式集群中應(yīng)用系統(tǒng)的一致性問題。它能提供類似文件系統(tǒng)的目錄節(jié)點樹方式的數(shù)據(jù)存儲,主要用途是維護(hù)和監(jiān)控所存數(shù)據(jù)的狀態(tài)變化,以實現(xiàn)對集群的管理。

6.1.1 應(yīng)用場景

在分布式環(huán)境里,往往會有很多服務(wù)器都需要同樣的配置來保證信息的一致性和集群的可靠性,而一個分布式集群往往動輒上百臺服務(wù)器,一旦配置信息改變,就需要對每臺服務(wù)器進(jìn)行修改,這樣會消耗大量時間,那么有沒有一種簡單的方法統(tǒng)一對其修改呢?像這樣的配置信息完全可以交給 ZooKeeper來管理,將配置信息保存在 ZooKeeper的某個目錄節(jié)點中,然后所有應(yīng)用服務(wù)器都監(jiān)控配置信息的狀態(tài),一旦配置信息發(fā)生變化,每臺應(yīng)用服務(wù)器就會收到 ZooKeeper的通知,然后從 ZooKeeper獲取新的配置信息應(yīng)用到系統(tǒng)中即可。

1. 統(tǒng)一命名服務(wù)

利用ZooKeeper中的樹形分層結(jié)構(gòu),可以把系統(tǒng)中的各種服務(wù)的名稱、地址以及目錄信息存放在ZooKeeper中,需要的時候去ZooKeeper中讀取就可以了。

此外,ZooKeeper中有一種節(jié)點類型是順序節(jié)點,可以利用它的這個特性制作序列號。我們都知道,數(shù)據(jù)庫有主鍵ID可以自動生成,但是在分布式環(huán)境中就無法使用了,于是我們可以使用ZooKeeper的命名服務(wù),它可以生成有順序的編號,而且支持分布式,非常方便。

2. 集群管理

ZooKeeper能夠很容易地實現(xiàn)集群管理的功能,如有多臺服務(wù)器組成一個服務(wù)集群,那么必須要有一個“總管”知道當(dāng)前集群中每臺機(jī)器的服務(wù)狀態(tài),一旦有服務(wù)器不能提供服務(wù),集群中其他服務(wù)器必須知道,從而做出調(diào)整,重新分配服務(wù)策略。當(dāng)增加一臺或多臺服務(wù)器時,同樣也必須讓“總管”知道。

ZooKeeper不僅能夠幫助我們維護(hù)當(dāng)前集群中服務(wù)器的服務(wù)狀態(tài),而且能夠選舉出一個“總管”,讓這個“總管”來管理集群,這種選舉方式稱為“Leader選舉”。

3. 分布式鎖

在一個分布式環(huán)境中,為了提高可靠性,集群的每臺服務(wù)器上都部署著同樣的服務(wù)。但是一個常見的問題就是,如果集群中的每臺服務(wù)器都進(jìn)行同一件事情的話,它們相互之間就要協(xié)調(diào),編程起來將非常復(fù)雜。這個時候可以使用分布式鎖,我們可以利用ZooKeeper來協(xié)調(diào)多個分布式進(jìn)程之間的活動,在某個時刻只讓一個服務(wù)去工作,當(dāng)這個服務(wù)出現(xiàn)問題的時候?qū)㈡i釋放,立即切換到另外的服務(wù)。

6.1.2 架構(gòu)原理

ZooKeeper集群的總體架構(gòu)如圖6-1所示。

ZooKeeper集群由一組服務(wù)器(Server)節(jié)點組成,在這些服務(wù)器節(jié)點中有一個節(jié)點的角色為Leader,其他節(jié)點的角色為Follower。當(dāng)客戶端(Client)連接到ZooKeeper集群并執(zhí)行寫請求時,這些請求首先會被發(fā)送到Leader節(jié)點。Leader節(jié)點在接收到數(shù)據(jù)變更請求后,首先會將該變更寫入到本地磁盤以作恢復(fù)使用,當(dāng)所有的寫請求持久化到磁盤后,會將數(shù)據(jù)變更應(yīng)用到內(nèi)存中,以加快數(shù)據(jù)讀取速度,最后Leader節(jié)點上的數(shù)據(jù)變更會同步(廣播)到集群的其他Follower節(jié)點上。

圖6-1 ZooKeeper集群的總體架構(gòu)

當(dāng)Leader節(jié)點發(fā)生故障而失效時,F(xiàn)ollower節(jié)點會快速響應(yīng),由消息層重新選出一個Leader節(jié)點來處理客戶端請求。

6.1.3 數(shù)據(jù)模型

ZooKeeper主要用于管理協(xié)調(diào)數(shù)據(jù)(服務(wù)器的配置、狀態(tài)等信息),不能用于存儲大型數(shù)據(jù)集。

ZooKeeper有一個樹形層次的命名空間,該命名空間的組織方式類似于標(biāo)準(zhǔn)文件系統(tǒng)。ZooKeeper可以將該命名空間共享給分布式應(yīng)用程序,使它們可以利用該命名空間進(jìn)行相互協(xié)調(diào)。與為存儲而設(shè)計的典型文件系統(tǒng)不同,ZooKeeper數(shù)據(jù)保存在內(nèi)存中,這樣可以提高吞吐量和降低數(shù)據(jù)延遲。

在ZooKeeper的命名空間中,名稱是由斜線(/)分隔的路徑元素組成的。命名空間中的每個名稱(也叫節(jié)點)都由路徑標(biāo)識,如圖6-2所示。

圖6-2 ZooKeeper數(shù)據(jù)模型

ZooKeeper命名空間中的每個節(jié)點都可以有與之關(guān)聯(lián)的數(shù)據(jù)(也稱元數(shù)據(jù))以及子節(jié)點,就好比標(biāo)準(zhǔn)文件系統(tǒng)中的每個文件夾都可以存放文件并且每個文件夾都有子文件夾。

通常使用znode來表示ZooKeeper命名空間中的名稱節(jié)點,存儲在每個znode上的數(shù)據(jù)會被客戶端原子化地讀取和寫入。讀取操作可以獲取與znode關(guān)聯(lián)的所有數(shù)據(jù),而寫入操作可以替換所有數(shù)據(jù)。znode的主要特點如下:

  •  znode中僅存儲協(xié)調(diào)數(shù)據(jù),即與同步相關(guān)的數(shù)據(jù),例如狀態(tài)信息、配置內(nèi)容、位置信息等,因此數(shù)據(jù)量很小,大概B到KB量級。
  •  一個znode維護(hù)一個狀態(tài)結(jié)構(gòu),該結(jié)構(gòu)包括版本號、ACL(訪問控制列表)變更、時間戳。znode存儲的數(shù)據(jù)每次發(fā)生變化,版本號都會遞增,每當(dāng)客戶端檢索數(shù)據(jù)時,客戶端也會同時接收到數(shù)據(jù)的版本。客戶端也可以基于版本號檢索相關(guān)數(shù)據(jù)。
  •  每個znode都有一個ACL,用來限定該znode的客戶端訪問權(quán)限。
  •  客戶端可以在znode上設(shè)置一個觀察者(Watcher),如果該znode上的數(shù)據(jù)發(fā)生變更,ZooKeeper就會通知客戶端,從而觸發(fā)Watcher中實現(xiàn)的邏輯的執(zhí)行。

6.1.4 節(jié)點類型

ZooKeeper中的znode節(jié)點主要有以下4種類型。

1. 持久節(jié)點(PERSISTENT)

持久節(jié)點在創(chuàng)建后就一直存在,除非手動將其刪除。

2. 持久順序節(jié)點( PERSISTENT _SEQUENTIAL)

持久順序節(jié)點除了有持久節(jié)點的功能外,在創(chuàng)建時,ZooKeeper會在節(jié)點名稱末尾自動追加一個自增長的數(shù)字后綴作為新的節(jié)點名稱,以便記錄每一個節(jié)點創(chuàng)建的先后順序。數(shù)字后綴的長度是10位,且由0填充,例如0000000001。舉個例子,當(dāng)前有一個父節(jié)點/lock,我們需要在該節(jié)點下創(chuàng)建順序子節(jié)點/lock/node-,ZooKeeper在生成該子節(jié)點時會根據(jù)當(dāng)前子節(jié)點數(shù)量自動增加數(shù)字后綴,如果是第一個創(chuàng)建的子節(jié)點,則節(jié)點名稱為/lock/node-0000000000,下一個子節(jié)點則為/lock/node-0000000001,依次類推。

3. 臨時節(jié)點(EPHEMERAL)

只要創(chuàng)建節(jié)點的客戶端與ZooKeeper服務(wù)器的連接會話是活動的,這些節(jié)點就存在。當(dāng)客戶端與服務(wù)器的連接會話斷開時,節(jié)點將被刪除。基于此,臨時節(jié)點是不允許有子節(jié)點的。

4. 臨時順序節(jié)點( EPHEMERAL _SEQUENTIAL )

臨時順序節(jié)點除了有臨時節(jié)點的功能外,節(jié)點在創(chuàng)建時,會在節(jié)點末尾追加自增長的數(shù)字編號,這一點與持久順序節(jié)點的順序功能一致。

6.1.5 Watcher機(jī)制

ZooKeeper是一個基于Watcher(觀察者)模式設(shè)計的分布式服務(wù)管理框架,其允許客戶端向服務(wù)器的znode上注冊一個Watcher,一旦znode的狀態(tài)發(fā)生變化,ZooKeeper就會通知已經(jīng)在它上面注冊的Watcher做出相應(yīng)的反應(yīng)。當(dāng)前,ZooKeeper有四種狀態(tài)變化事件:節(jié)點創(chuàng)建、節(jié)點刪除、節(jié)點數(shù)據(jù)修改和子節(jié)點變更。

ZooKeeper中所有的讀取操作——getData()方法、getChildren()方法和exists()方法,都可以向服務(wù)器設(shè)置一個Watcher。Watcher事件相當(dāng)于一次性的觸發(fā)器,當(dāng)znode的數(shù)據(jù)發(fā)生改變時,會通知設(shè)置Watcher的客戶端。例如,如果客戶端執(zhí)行g(shù)etData(“/znode1”,true)方法,然后改變或刪除/znode1的數(shù)據(jù),客戶端將獲得/znode1的狀態(tài)改變事件通知。如果/znode1再次更改,則不會發(fā)送任何通知給客戶端,除非客戶端提前再次向/znode1設(shè)置Watcher。

ZooKeeper的Watcher有兩種類型:數(shù)據(jù)Watcher和子節(jié)點Watcher。數(shù)據(jù)Watcher只監(jiān)聽節(jié)點元數(shù)據(jù)的改變,子節(jié)點Watcher只監(jiān)聽節(jié)點的子節(jié)點的創(chuàng)建與刪除。getData()方法和exists()方法可以設(shè)置數(shù)據(jù)Watcher,這兩個方法返回znode節(jié)點的元數(shù)據(jù)信息。getChildren()方法可以設(shè)置子節(jié)點Watcher,該方法則返回一個子節(jié)點列表。因此,setData()方法會觸發(fā)數(shù)據(jù)Watcher,一個成功的create()方法將觸發(fā)正在創(chuàng)建的znode的數(shù)據(jù)Watcher以及父znode的子節(jié)點Watcher,一個成功的delete()方法將會為被刪除的znode觸發(fā)一個數(shù)據(jù)Watcher以及為被刪除節(jié)點的父節(jié)點觸發(fā)一個子節(jié)點Watcher。

1. Watcher機(jī)制執(zhí)行流程

Watcher機(jī)制主要包括客戶端線程、客戶端WatchManager和ZooKeeper服務(wù)器三部分。具體流程為:客戶端在向ZooKeeper服務(wù)器注冊Watcher的同時,會將Watcher對象存儲在客戶端的WatchManager 中。當(dāng)ZooKeeper服務(wù)器端觸發(fā)Watcher事件后,會向客戶端發(fā)送通知,客戶端線程從WatchManager中取出對應(yīng)的Watcher對象來執(zhí)行回調(diào)邏輯,如圖6-3所示。

圖6-3 ZooKeeper Watcher機(jī)制執(zhí)行流程

WatchManager類的部分源碼如下:

2. Watcher相關(guān)事件

我們可以調(diào)用exists()、getData()和getChildren()三個方法來設(shè)置Watcher,這些方法主要用于讀取ZooKeeper的狀態(tài)信息。下面列出了常用的設(shè)置Watcher事件的方法。

  •  節(jié)點創(chuàng)建事件:通過調(diào)用exists()方法設(shè)置。
  •  節(jié)點刪除事件:通過調(diào)用exists()、getData()和getChildren()方法設(shè)置。
  •  節(jié)點改變事件:通過調(diào)用exists()和getData()方法設(shè)置。
  •  子節(jié)點事件:通過調(diào)用getChildren()方法設(shè)置。

6.1.6 分布式鎖

在分布式環(huán)境中,為了保證在同一時刻只能有一個客戶端對指定的數(shù)據(jù)進(jìn)行訪問,需要使用分布式鎖技術(shù),只有獲得鎖的客戶端才能對數(shù)據(jù)進(jìn)行訪問,其余客戶端只能暫時等待。

利用ZooKeeper實現(xiàn)分布式鎖,常用的實現(xiàn)方法是,所有希望獲得鎖的客戶端都需要執(zhí)行以下操作:

(1)客戶端連接ZooKeeper,調(diào)用create()方法在指定的鎖節(jié)點(如/lock)下創(chuàng)建一個臨時順序節(jié)點。例如節(jié)點名為“node-”,則第一個客戶端創(chuàng)建的節(jié)點為“/lock/node-0000000000”,第二個客戶端創(chuàng)建的節(jié)點為“/lock/node-0000000001”。

(2)客戶端調(diào)用getChildren()方法查詢鎖節(jié)點/lock下的所有子節(jié)點列表,判斷子節(jié)點列表中序號最小的子節(jié)點是否是自己創(chuàng)建的。如果是,則客戶端獲得鎖,否則監(jiān)聽排在自己前一位的子節(jié)點的刪除事件,若監(jiān)聽的子節(jié)點被刪除,則重復(fù)執(zhí)行此步驟,直至獲得鎖。

(3)客戶端執(zhí)行業(yè)務(wù)代碼。

(4)客戶端業(yè)務(wù)完成后,刪除在ZooKeeper中對應(yīng)的子節(jié)點以釋放鎖。

針對上述流程中的兩個不容易理解的問題解析如下:

步驟(1)中為什么要創(chuàng)建臨時節(jié)點?

假如客戶端A獲得鎖之后,客戶端A所在的計算機(jī)宕機(jī)了,此時客戶端A沒有來得及主動刪除子節(jié)點。如果創(chuàng)建的是永久節(jié)點,鎖將永遠(yuǎn)不會被釋放,從而導(dǎo)致死鎖。臨時節(jié)點的好處是,盡管客戶端宕機(jī)了,但是ZooKeeper在一定時間內(nèi)沒有收到客戶端的心跳則會認(rèn)為會話失效,然后將臨時節(jié)點刪除以釋放鎖。

步驟(2)中未獲得鎖的客戶端為什么要監(jiān)聽排在自己前一位的子節(jié)點的刪除事件?

按照爭奪鎖的規(guī)則,每一輪鎖的爭奪取的都是序號最小節(jié)點,當(dāng)序號最小的節(jié)點刪除后,正常情況排在最小節(jié)點后一位的節(jié)點將獲得鎖,以此類推。因此,若客戶端沒有獲得鎖,只需要監(jiān)聽自己前一位的節(jié)點即可,這樣每當(dāng)鎖釋放時,ZooKeeper只需要通知一個客戶端,從而節(jié)省了網(wǎng)絡(luò)帶寬。若將監(jiān)聽事件設(shè)置在父節(jié)點/lock上,那么每次鎖的釋放將通知所有客戶端。假如客戶端數(shù)量龐大,會導(dǎo)致ZooKeeper服務(wù)器必須處理的操作數(shù)量激增,增加了ZooKeeper服務(wù)器的壓力,同時很容易產(chǎn)生網(wǎng)絡(luò)阻塞。

上述使用ZooKeeper實現(xiàn)分布式鎖的流程如圖6-4所示。

圖6-4 ZooKeeper 分布式鎖實現(xiàn)流程

主站蜘蛛池模板: 扶沟县| 正定县| 新源县| 毕节市| 高淳县| 宜宾市| 柘城县| 洛阳市| 嘉兴市| 黎城县| 准格尔旗| 兴安盟| 涪陵区| 大渡口区| 新乡县| 陆丰市| 福泉市| 临潭县| 霞浦县| 胶南市| 定日县| 潼关县| 柞水县| 四川省| 达日县| 安陆市| 常宁市| 屏山县| 句容市| 隆林| 犍为县| 白河县| 夏邑县| 团风县| 营山县| 宜昌市| 汉川市| 葵青区| 京山县| 马山县| 和硕县|