Zookeeper分布式協同工具詳解

引言

zookeeper(動物管理員)設計目標為分布式系統的任務執行提供協同支持,包括Hadoop,Storm,Hive等分布式系統,解決分布式系統常見問題

分布式系統常見問題

1、主節點崩潰:如果主節點發送崩潰,就無法給從節點分配任務或重新分配已失敗的任務;

2、從節點崩潰:如果從節點崩潰,就無法執行分配的任務;

3、通信故障:如果主節點和從節點之間無法通信,從節點就收不到主節點分配給它的任務;

主節點崩潰的解決方案:

主節點崩潰后需要有一個備份主節點,當主節點崩潰后,備份主節點接管主節點的角色,進行故障轉移。這樣會導致出現3個新的的問題:

1、備份節點需要恢復到主節點崩潰前的狀態,而主節點的原狀態信息不能從已崩潰的主節點上獲取。

fan2、假如主節點并未崩潰,但是備份節點卻認為主節點已崩潰,備份節點會成為主節點的角色,成為第二個主節點。主要的場景就是主節點負載很高,導致通訊延遲,備份節點會認為主節點已崩潰;

3、從節點無法與主節點通訊,由于網絡分區的原因,部分從節點會與主節點斷開聯系,而與第二個主節點建立通訊。這樣會導致系統中會有多個部分獨立工作,導致整體行為不一致,這種情況稱為腦裂。

從節點崩潰解決方案:

1、如果從節點崩潰了,所有分配給從節點未完成的任務需要重新分配給其他從節點,這樣的話,主節點需要能夠檢測從節點是否崩潰,哪些從節點有效可以派發未完成的任務;

2、從節點崩潰時,該節點執行的任務可能部分完成,也許全部執行完但沒有報告執行結果。如果整個運算過程產生了其他作用,我們還有必要執行某些恢復過程來清除之前的狀態;

通信故障解決方案:

1、網絡通信故障,有可能會導致主節點不知道子節點執行的已分配任務的狀態,可能會重新分配任務,這樣就會導致任務重新執行。若該任務冪等,則可重復執行;若不冪等,則需要考慮多個節點執行同個任務的情況;

2、采用分布式鎖控制的話,會出現從節點正常執行并釋放了鎖,但是主節點認為從節點已失效,重新分配了任務并且新的從節點也可以正常的獲取鎖。

解決這種情況,zk需要客戶端告訴某些數據的狀態是臨時的,同時,zk需要客戶端定時發送是否存活的通知,若一個客戶端未能及時發送通知,那么所有屬于這個客戶端的臨時狀態的數據都將全部被刪除。通過這兩個機制,在崩潰或發生通信故障時,我們可以預防客戶端獨立運行而發生的應用宕機

總結上面的問題,zk的主要任務:

1、主節點選舉:關鍵的一步,使得主節點可以給從節點分配任務

2、崩潰檢測:主節點必須具備檢測從節點崩潰或者失去連接的能力

3、組成員管理:主節點必須具備知道哪一個從節點可以執行任務的能力

4、元數據管理:主節點和從節點必須具有通過某種可靠的方式保存分配狀態和執行狀態的能力

一、基礎內容

1、數據模型

zookeeper維護一個樹形目錄結構,樹上的節點成為znode,znode可以用來存儲數據,由于zookeeper設計初衷是為了協調分布式服務,因此znode中通常用來存儲少量的元數據,通常大小不超過1MB。znode的樹形結構如下圖。


2、znode類型

持久節點:只能通過/delete命令刪除

臨時節點:客戶端崩潰或關閉了與zk的連接,這個節點就會被刪除。或者客戶端主動刪除。(臨時節點可以用于檢測節點狀態)

有序節點:一個有序znode節點被分配唯一一個單調遞增的整數,當創建有序節點時,一個序號會被追加到路徑之后。可以通過序號判斷節點創建的順序

znode一共有4種類型:持久型,臨時型,持久有序,臨時有序

3、常用命令

create /path data:創建一個名為/path的znode節點,并包含數據data;創建臨時節點:create -e /path data

delete /path:刪除名為/path的節點

exists /path:檢查是否存在名為/path的節點

setData /path data:設置名為/path的znode的數據為data

getData /path:返回名為/path的節點的數據

getChildren /path:返回所有/path節點下的所有子節點

stat /path:查詢節點狀態

stat查詢的數據屬性:

czxid:節點創建的zxid

mzxid:節點最新一次更新發生時的zxid

ctime:節點創建時的時間戳

mtime:節點最新一次更新發生時的時間戳

dataversion:節點數據的更新次數

cversion:子節點的更新次數

aclVersion:節點ACL授權信息的更新次數

ephemeralOwner:如果該節點為臨時節點,該值表示與該節點綁定的sessionid;如果不是臨時節點,該值為0

dataLength:節點數據字節數

numChildren:子節點個數

4、znode監聽

客戶端通過向zk注冊需要接受通知的znode,通過對znode設置監視點(watch)來接受通知。監視點是一個單次觸發的操作,意即監視點會觸發一個通知。為了接受多個通知,客戶端必須在每次通知后設置一個新的監視點。

通知機制的一個重要保障:對于同一個znode操作,先向客戶端傳送通知,然后在對該節點進行更新。如果客戶端對一個znode設置了監視點,而該znode發生了兩個連續的更新。第一次更新后,客戶端在觀察第二次變化前就收到了通知,然后讀取znode中的數據。

zk監視點對應的通知類型:znode的數據變化;znode子節點的變化;znode的創建或刪除。

使用ls /path true表示監聽該節點,當該節點發生變化時,會收到事件通知

5、znode的版本號:

每個znode都有一個版本號,它隨著每次數據變化而自增。當使用setData和delete對節點進行更新操作時,會以版本號作為轉入參數,只有當轉入參數的版本號與服務器上的版本號一致時調用才會成功。當版本號為-1時,表示匹配全部版本號,默認操作指令的版本號為-1

6、znode權限控制

znode的訪問權限的檢查是基于每個znode的,如果一個客戶端可以訪問一個znode,即使該客戶端無權訪問該節點的父節點,仍然可以訪問該節點。

zk通過訪問控制表來控制訪問權限,一個ACK包含以下形式的記錄:

scheme:auth-info,其中scheme對應了一組內置的鑒權模式,auth-info為對于特定模式所對應的方式進行編碼的鑒權信息。

通過調研addAuthInfo來增加鑒權信息:

void addAuthInfo(String scheme, byte[] auth);

zk內置的acl鑒權模式:

1、OPEN_ACL_UNSAFE:使用anyone作為auth-info,表示任何人都可以訪問

2、super:可以訪問所有節點,不用鑒權

3、digest:命令格式:digest:name:secret, READ|WRITE|CREATE|DELETE|ADMIN

4、ip鑒權:ip:127.0.0.1/8080, READ,通過ip鑒權模式,不需要使用addAuthInfo

二、高級特性

1、zookeeper分布式鎖的實現

并發訪問時,可以先在zk上創建一個/lock的臨時節點,若創建成功,表示獲取鎖成功,否則未獲取成功,需要監聽該節點,當該節點被刪除后重新嘗試創建;當使用完成后,主動釋放或者斷開與zk的連接,該鎖就會被zk回收,這樣可以保證該鎖一定會被釋放。其他等待線程監聽該/lock節點,一旦該節點被釋放,則可發起創建請求。

為防止大量的線程都在監聽一個節點,當該節點發生變化時產生大量的通知,可能造成一定的影響,可以使用臨時有序節點,以創建最小序列號的znode為鎖的持有者,監聽時,每個客戶端監聽前一個znode,如創建/lock-003的客戶端監聽/lock-002的znode,若/lock-002不存在,則/lock-003獲得了鎖

2、同步方法

為解決兩個zk客戶端在隱藏通道進行通訊,zk1客戶端進行更新后,通知zk2,而zk2連接的zk服務器與zk1不同,且還未更新。此時,zk2就拿到不最新的更新數據。在getData調用sync方法就是為了解決這個問題。

zk.sync(path, voidCb, ctx);

服務端處理sync調用時,服務端會刷新群首與調用sync操作的客戶端c所連接的服務端之間的通道,刷新的意思就是說在調用getData的返回數據的時候,服務端確保返回所有客戶端c調用sync方法時所有可能的變化情況。上面的場景中,變化的情況通訊會先于sync操作的調用而發生,因此當zk2收到getData調用的響應,響應中必定包含zk1所通知的變化情況。注意,在此時,該節點也可能發生了其他變化,因此在調用getData時,zk服務器只保證所有變化情況能夠返回。

sync還有個問題,就是在群首變更時,snyc操作不會進入執行管道中(zk的create,setData,delete會放入執行管道中,即使發生群首變更,也會被順序執行),因此snyc操作有可能不會被傳遞執行。

三、zookeeper核心內容


1、zk架構:

zk服務器運行于兩種模式下:獨立模式(單點模式)和仲裁模式(zk集群)。其中集群模式下的zk服務器之間會進行狀態復制。

zk集群模式下運行崩潰的服務器不得多于集群服務器的一半。

zk客戶端通過TCP與zk服務器創建一個會話(長連接),zk服務端對一個會話中請求遵循FIFO的處理方式,當會話結束后,會話創建的臨時節點就會失效。當一個客戶端創建了多個會話,并同時發生請求時,就會破壞FIFO的模式。會話事件有:NOT_CONNECTED,CONNECTING,CONNECTED,CLOSED


2、zk會話:

客戶端會話的生命周期:當未進行連接時,會話狀態為NOT_CONNECTED,當zk客戶端初始化后,轉到CONNECTING狀態。成功與zk服務器建立連接后,會話轉到CONNECTED狀態。當客戶端與zk失去連接或者無法接受響應時,會轉換回CONNECTING狀態并嘗試發現其他zk服務器。如果重連或發現其他服務器成功,會話會轉回CONNECTED狀態,否則,它會聲明會話過期,然后轉換到CLOSED狀態。

在創建一個會話時,需要設置會話超時時間。當經過T時間,服務器收不到這個會話的任何消息,服務端就會聲明會話過期。而在客戶端,如果經過T/3的時間未收到任何消息,客戶端將向服務端發送心跳消息。再經過2T/3時間后,zk客戶端開始尋找其他服務器,而此時,它還有T/3的時間進行尋找。

當連接到一個新的服務器上時,該服務器的狀態要與最后一次連接的zk服務器狀態保持最新。zk通過在服務中排序更新操作來決定狀態是否最新。zk確保每一個變化相對于其他已執行的更新是完全有序。如果一個客戶端在位置i觀察到一個更新,它就不能連接到只觀察到j < i的服務器上。在zk實現中,系統根據每一個更新建立的順序來分配給事物標識符(zkid)。

3、zk讀寫操作順序:

寫操作的順序:zk集群中,zk的狀態會在集群中進行復制。服務端對于狀態變化的順序達成一致,并會使用相同的順序執行狀態的更新。但是這個更新時間不是同時的。

讀操作的順序:若不采用隱藏通道,(即應用系統之間通過其他通訊方式進行通訊,不通過zk),讀的順序取決于通知的順序

通知的順序:在對znode進行更新操作前,zk會先將通知發送給客戶端,這可以確保客戶端不會讀到任何無效配置。當進行批量更新時,為防止客戶端讀取部分更新數據,有兩種解決辦法:

(1)、下發更新通知后,客戶端先統一讀取一個znode,如果該znode存在,則說明更新未完成,不能進行讀取操作;否則進行讀取

(2)、使用Op操作進行更新,保證原子性。

4、請求、事物和標識符:

zk服務器在處理只讀請求時,只會在本地運行,因此性能會很高;

對于更新操作,都會被轉發給群首,由群首執行相應的操作,生成事物。對于一個setData操作,zk會將znode的數據信息和版本號包裝成一個事物。當處理該事物時,服務端將會用事物中的數據信息來替換znode中的原數據信息,并會用事物中的版本號更新該節點,而不是增加版本號的值。

一個事物為一個單位,所有的變更處理需要以原子方式執行。zk集群以事物的方式運行,并確保所有的變更操作已原子方式被執行,同時不會被其他事物所干擾。在zk中并不存在回滾機制,而是確保事物的每一步操作都不互相干擾。每個zk服務器都會啟動一個單獨線程來處理事物,通過單線程來保障事物之間的順序執行互不干擾。

同時,事物具有冪等性,多次執行同一個事物的是一樣的。但是多個事物執行要考慮到順序問題,否則無法實現一致性。

zk群首產生的事物標識符為zxid,通過zxid對事物進行標識,就可以按照群首所指定的順序在各個服務器中按序執行。服務器之間進行新的群首選舉時也會交換zxid信息,這樣就可以知道哪個無障服務器接受了更多的事物,并可以同步他們之間的狀態信息。

zxid一個long型整數(64位),分為兩部分:時間戳和計數器,每部分32位。zk通過廣播各個服務器之間的狀態變更信息。

5、Zab協議:zookeeper atomic broadcast protocol原子廣播協議

在接收到一個請求后,追隨者會把該請求轉發給群首,群首將探索性的執行該請求,并將執行結果以事物的方式對狀態更新進行廣播。當事物進行提交時,服務器就會將這些變更反饋到數據樹上。

服務器如何確認一個事物已經提交(兩階段提交):

(1)、群首向所有追隨者發送一個提案消息

(2)、當一個追隨者接收到消息后,追隨者會檢查所發送的提案消息是否屬于其所追隨的群首,并確認群首所廣播的提案消息和提交事物的順序是否正確,然后響應群首一個ACK消息,通知群首已接受該提案。

(3)、當收到仲裁數量的服務器發送的確認消息后,群首就會發送消息通知追隨者進行提交操作。

Zab協議保障了以下幾個重要屬性:

如果群首按順序廣播了事物T和事物T+,那么每個服務器在提交T+事物前保證事物T已經提交完成。(保證事物傳遞順序的一致性)

如果某個服務器按照事物T、事物T+的順序提交事物,所有其他服務器也必然會在提交事物T+前提交事物T。(保證服務器不會跳過任何事物)

一個被選舉群首確保在提交完所有之前的時間戳內需要提交的事物,之后才開始廣播新的事物。(保證及時存在多個群首,也不會導致事物被隨意廣播,導致服務器提交事物的順序亂掉,群首不會立即處于活動狀態,需要確保仲裁數量的服務器認可這個群首新的時間戳值,并且保證自時間戳開始值到時間戳e-1內的所有提案被提交)

在任何時間點,都不會出現兩個被仲裁支持的群首。

群首是會變化的,一個群首活動期間發送的事物,以zxid來表示,zxid由時間戳和計數器組成,當時間戳一致,表示是某個群首,因為時間戳只會在選舉新的群首時產生,計數器表示執行的第幾個事物。基于此,若一個服務器在當任群首時,擁有不同的時間戳,那么就認為是不同的群首。若群首s持有時間戳為4,當前的群首l持有時間戳為6,那么追隨者會追隨l,執行它的提案,但是在此之前也會接受4-6之間的提案。記錄已接受的提案消息非常關鍵,這樣可以確保所有的服務器最終提交了被某個或多個服務已經提交完成的事物。

對于時間戳轉換造成的追隨者滯后,zk采用兩種解決辦法:

(1)、若滯后不多,則群首只需發送缺失的事物點,因為追隨者按照嚴格的順序接收事物點,這些缺失事物點永遠是最近的。

(2)、若滯后很多,zk將發送名為SNAP的完整快照。發送完整的快照會增大系統恢復的延時。

6、觀察者:

觀察者不參與選舉,但是接收群首的inform消息。群首發送給追隨者有兩種消息,一種是提議,包含了事物信息,一種是inform,只包含了zxid,不包含具體的數據信息。因此inform消息本質上是事物的提交消息。因此觀察者不能實施提議。

引入觀察者的一個主要原因是提高讀請求的可擴展性。通過加入多個觀察者,我們可以在不犧牲寫操作的吞吐率的前提下服務更多的讀操作。觀察者可以觀察到事物是否已經被提交,若被提交則是最新的版本,可以讀取,否則需要等待寫完成。以此來提高讀操作。

7、群首選舉:

zk采用仲裁模式進行群首選舉,其中仲裁模式要求支持一個群首服務器數量必須至少存在一個服務器進程的交叉,仲裁服務器之間兩兩相交。

選舉流程:

(1)、每個服務器啟動后進入LOOKING狀態,開始選舉一個新的群首或查找已存在的群首。如果群首存在,其他服務器就會告訴新加入的服務器哪個是群首,同時,新服務器也會與群首聯系,以確保自己的狀態與群首一致。

(2)、如果集群中所有服務器均處于LOOKING狀態,這些服務器就會進行通信來選舉一個群首,通過信息交換對群首選舉達成共識的選擇。在選舉過程中勝出的服務器將進入LEADING狀態,而集群中的其他服務器將會進入FOLLOWING狀態

(3)、當一個服務器進入LOOKING狀態,就會向集群中的每個服務器發送一個通知消息,該消息包括該服務器的投票信息,投票中包含服務器標識id(sid)和最近執行的事物的zxid,如:一個服務器的投票信息為(1,5),則表示該服務器的id為1,最近執行的事物zxid為5。

(4)、當一個服務器接受到一個投票信息,它將會根據以下規則修改自己的投票信息:

將接收的voteid和votezxid作為一個標識符,并獲取自己當前投票中的myxid和myzxid。

如果(votezxid > myzxid)或(votezxid = myzxid && voteid > myid),保留當前的投票信息,即下一次的自己的投票信息將會是接收到得投票信息

否則下次投票還是用自己的投票信息

總之,只有最新的服務器會贏得選舉,因為其擁有最新一次的zxid。

(5)、當一個服務器收到的仲裁數量的服務器發來的投票都一樣時,就表示選舉成功。

注意:若在選舉過程中,某一臺服務器選舉出錯誤的群首,最終導致出現兩個群首,在這種情況下,兩個群首又會組成一個新的仲裁服務器進行群首選舉。而出錯的服務器因無法接受到選舉出來的群首信息而超時,因為它選的群首并沒有認為自己就是群首。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。