1. 程式人生 > 實用技巧 >Pytest框架入門03 初始化清除

Pytest框架入門03 初始化清除

特點

一個leader多個follower組成的叢集,只要半數伺服器有效,則叢集有效。

  • 順序一致性: 從同一客戶端發起的事務請求,最終將會嚴格地按照順序被應用到ZooKeeper中去。
  • 原子性: 所有事務請求的處理結果在整個叢集中所有機器上的應用情況是一致的,也就是說,要麼整個叢集中所有的機器都成功應用了某一個事務,要麼都沒有應用。
  • 全域性資料一致: 無論客戶端連到哪一個ZooKeeper伺服器上,其看到的服務端資料模型都是一致的。
  • 可靠性: 一旦一次更改請求被應用,更改的結果就會被持久化,直到被下一次更改覆蓋。
  • 實時性:在一段時間內,客戶端可以讀取到最新的資料(伺服器間同步很快)

體系結構

事實上ZooKeeper底層只提供了兩個功能:

  • 管理(儲存,讀取)使用者程式提交的資料
  • 為使用者程式提交資料節點監聽服務

由若干伺服器構成,每臺伺服器記憶體中維護相同的類似於檔案系統的樹形結構。其中一臺通過ZAB原子廣播選舉主控伺服器,其他的作為從屬伺服器。

客戶端可以通過TCP協議連線任意一臺伺服器,使用這個TCP連線來發送請求、獲取結果、獲取監聽事件以及傳送心跳包。如果這個連線異常斷開了,客戶端可以連線到另外的機器上。但是:

  • 每一臺從伺服器都可以直接響應讀請求
  • 如果是更新或者寫資料操作,則必須由主控伺服器進行協調更新(客戶端如果連線的是從伺服器,則請求會被轉發至主控伺服器上,由其完成)

因此,ZooKeeper任意一臺伺服器都可以響應讀操作,這是吞吐量高的主要原因(資料儲存在記憶體中,因此它也是低延遲的)。並且,在讀多於寫的應用程式中效能更高。

但潛在的問題是,即使主控伺服器已經更新了記憶體資料,但是ZAB協議還未能將其廣播到從屬伺服器時,客戶端可能會讀到過期的資料。所以在API中提供了sync操作:接收到sync命令的從屬伺服器從主控伺服器同步狀態資訊,保證兩者完全一致。因此只要在讀操作前呼叫sync,可以保證客戶端讀到最新狀態的資料。

伺服器在響應請求時,會給客戶端分配一個漸增的zxid格式的時間戳(包括了節點建立時間cZxid、節點修改時間mZxid、子節點修改或者建立時間pZxid

),這個時間戳全域性有序,實際上是64位的數字,高32位是epoch,用來標識leader是否發生改變,低32位用來遞增計數。客戶端會在請求中攜帶該資訊,如果發生了伺服器切換,當伺服器發現自身的zxid比客戶端發來的zxid要低,表明伺服器資料過期,需要同步。

ZooKeeper的容錯是通過重放日誌(Replay log)和模糊快照(Fuzzy Snapshot)實現的:

  • 重放日誌:將更新操作體現在記憶體資料之前先寫入外存日誌中避免資料丟失
  • 模糊快照:週期性對記憶體資料進行資料快照,並不對記憶體資料加鎖,而是用深度遍歷的方式將記憶體中的屬性結構轉入外存快照資料中。因而快照資料可能與記憶體中並不一致,是模糊的。

ZooKeeper可以保證資料更新操作是冪等的,只要保證操作順序一致,即使多次執行統一操作對最終結果沒有影響。載入模糊快照,利用重放日誌重新執行一遍,系統會恢復到最新狀態。

資料結構

ACL

ZooKeeper採用ACL(Access Controll List,訪問控制)策略進行許可權控制,共有五種許可權:

  • create:建立子節點
  • read:獲取節點資料和子節點列表
  • write:更新節點資料
  • delete:刪除子節點
  • admin:設定節點ACL許可權

Znode

類似於傳統的檔案系統模式,由樹形的層級目錄結構組成,節點稱之為Znode。每個ZNode預設可以儲存1MB的資料。節點既可以是檔案,也可以是目錄。它有兩種型別:持久節點和臨時節點。

對應於每個ZnodeZookeeper都會為其維護一個叫作Stat的資料結構,Stat中記錄了這個Znode的三個資料版本,分別是dataVersion(每次對節點進行set操作,資料版本值加)、cVersion(當子節點有變化時,值加1)和aclVersion(當前Znode的訪問控制版本號)。

dataVersion實際上是樂觀鎖。

持久節點不論客戶端會話情況,一直存在,只有當客戶端顯式呼叫刪除操作才會消失。臨時節點會在會話結束後,自動由ZooKeeper刪除。

ZooKeeper中,一個客戶端連線是指客戶端和伺服器之間的一個TCP長連線。通過這個連線,客戶端能夠通過心跳檢測與伺服器保持有效的會話,也能夠向Zookeeper伺服器傳送請求並接受響應,同時還能夠通過該連線接收來自伺服器的Watch事件通知。 會話的sessionTimeout值用來設定一個客戶端會話的超時時間。當由於伺服器壓力太大、網路故障或是客戶端主動斷開連線等各種原因導致客戶端連線斷開時,只要在sessionTimeout規定的時間內能夠重新連線上叢集中任意一臺伺服器,那麼之前建立的會話仍然有效。

在為客戶端建立會話之前,服務端首先會為每個客戶端都分配一個sessionID。由於sessionIDZookeeper會話的一個重要標識,許多與會話相關的執行機制都是基於這個sessionID的,因此,無論是哪臺伺服器為客戶端分配的sessionID,都務必保證全域性唯一。

API

getDataexistsgetChildren可以設定觀察標識,如果觀察標識watchtrue,則當節點內容發生變化時,ZooKeeper會主動通知客戶端程序,但是並不會將變化內容資料推送過來。

應用場景

領導者選舉

當主從結構中主節點發生故障,需要由某臺備機成為新的主控機,這個過程被稱為領導者選舉。

ZooKeeperl節點設定為領導專用節點,儲存領導者的地址資訊以及其他輔助資訊。當程序p嘗試讀取l,並設定觀察標識。

  1. 如果讀取成功,說明已有領導者,並且從讀取結果中獲得領導者相關資訊。
  2. 如果讀取失敗,說明無領導者。p嘗試建立l節點,如果建立成功,其他節點因為設定了觀察標識,ZooKeeper會通知其他節點領導者發生了故障。
  3. 如果l節點失效了,ZooKeeper會通知其他非領導節點,其他節點再按此步驟進行選舉競爭。

配置管理

配置檔案儲存在ZooKeeper的某個節點c中,分散式系統中的客戶端程序啟動時從該節點讀取配置資訊並設定觀察標記。若配置檔案內容被改變,客戶端會接收到變化通知。

組成員管理

利用臨時節點進行組成員管理,建立一個g節點代表某個群組,某個組成員加入在g下建立臨時子節點。負責組成員管理的監控程序可以使用getChildren獲得組下所有成員資訊並設定觀察標識,當後續有新節點加入或者退出時會獲得通知訊息。

任務分配

監控程序建立任務佇列管理節點tasks,所有新進入系統的任務都可以在tasks節點下建立子節點。當有新任務task-j時,ZooKeeper通知監控程序,監控程序將其分配給機器i,然後在machines目錄下對應的m-i節點建立子節點task-jm-i節點對應的伺服器會監聽節點的變化,發現有新增節點時說明有新分配的任務,讀出並執行後,將其刪除,同時刪除tasks節點下的task-j節點。

鎖管理(實現分散式鎖)

首先設立節點l作為鎖標記,每個程序在節點下創立一個臨時自增節點,這樣各個程序按照建立子節點的先後順序在名字上進行編號,如果當前程序發現自己編號是l節點下最小的編號節點,那麼它就獲得了鎖。

由於建立的是臨時節點,客戶端如果發生宕機,過了一定時間ZooKeeper沒有收到客戶端的心跳包會判斷會話失效,將臨時節點刪除從而釋放鎖。

同時,ZooKeeper提供的API中,讀取子節點列表(判斷自己是否是序號最小的節點)與設定監聽器的操作是原子執行的,從而可以保證不會丟失事件。

寫鎖(排他鎖):判斷l下的節點是否存在編號比自己小1的子節點,並設定觀察標識。如果不存在,可以直接獲得鎖,如果存在則進行等待。

讀鎖(併發鎖):判斷l下的節點是否存在編號比自己小的子節點持有寫鎖,如果沒有寫鎖可以直接獲得鎖;否則監聽離自己最近的寫鎖並進行等待。

雙向路障同步

路障同步是指多個併發程序都要到達某個同步點後再繼續向後推進。

利用某個節點b代表路障,每個節點通過在b下建立子節點表明自己已經到達了同步點,離開時通過刪除自己節點表明自己準備離開同步點。因此,在開始時,可以判斷b下的節點是否到達一定數量,從而判斷同步條件是否滿足,可以開始執行。如果需要對結束時進行同步,那麼判斷b下的節點個數是否已經到達0

其他還包括命名服務,負載均衡等。

叢集角色

ZooKeeper中沒有選擇傳統的Master/Slave概念,而是引入了LeaderFollowerObserver三種角色。

ZooKeeper叢集中的所有機器通過一個Leader選舉過程來選定一臺稱為“Leader” 的機器,Leader 既可以為客戶端提供寫服務又能提供讀服務。除了 Leader 外,FollowerObserver都只能提供讀服務。FollowerObserver唯一的區別在於Observer機器不參與Leader的選舉過程,也不參與寫操作的“過半寫成功”策略,因此Observer機器可以在不影響寫效能的情況下提升叢集的讀效能。

半數機制

我們知道在ZookeeperLeader選舉演算法採用了Zab協議。Zab核心思想是當多數Server寫成功,則任務資料寫成功。

  • 如果有3個Server,則最多允許1個Server 掛掉。
  • 如果有4個Server,則同樣最多允許1個Server掛掉

因此選奇數個Server即可。

ZAB協議

ZooKeeper基於paxos演算法,但是也擁有自己的核心演算法ZAB(原子訊息廣播協議),包括訊息廣播和崩潰恢復兩個方面。

訊息廣播:

  1. 在廣播事務之前Leader伺服器會先給這個事務分配一個全域性單調遞增的唯一ID,也就是zxid,每一個事務必須按照zxid的先後順序進行處理。
  2. Leader 通過先進先出佇列將帶有 zxid 的訊息作為一個提案(proposal)分發給所有follower
  3. Follower伺服器在接收到proposal之後,都會將其以事務日誌的形式寫入到本地磁碟中,成功寫入後反饋給Leader一個ACK
  4. Leader收到半數ACK響應之後,就會廣播一個Commit訊息給所有Follower,通知它們進行提交,同時Leader也會完成自身的提交。
  5. follower收到COMMIT時,會執行該訊息

因此follower要麼回ACKLeader,要麼拋棄Leader,這樣可能會導致某一時刻leaderfollwer狀態不一致。因此ZAB協議還有恢復模式。

崩潰恢復:

  • 情況一: leader收到合法數量的ACK後,開始廣播COMMIT命令,但是在所有follower都收到之前出現宕機,於是剩下的伺服器沒有執行這條命令。此時:
    1. 選舉擁有zxid最大的節點作為leader(此節點必為儲存了所有COMMIT訊息的)
    2. 新的leader將自己事務日誌中未COMMIT的訊息處理
    3. 新的leader將自身有follwer沒有的proposal傳送給follower,並將COMMIT命令傳送給所有follower,保證所有follower都處理了訊息。最終實現已經處理的訊息不會丟
  • 情況二:leader還沒傳送COMMIT就已經宕機了,當leader重新上線成為follower之後,會丟棄被跳過的proposal用以保持與整個系統一致。

leader選舉

伺服器啟動時的選舉

  1. 每臺伺服器都會發出投票,投給自己
  2. 每臺伺服器也會受到其他伺服器的投票請求,首先比較zxid,如果對方zxid較大,則會認可把投票傳送出去。如果zxid相同,則比較myid
  3. 如果伺服器收到了超過半數機器相同的投票資訊,那麼它將成為leader
  4. 一旦確定了leader,每個伺服器就會更新自己的狀態,如果是follower,那麼就變更為FOLLOWING,如果是leader,就變更為LEADING

對於後面啟動的較晚的機器,此時叢集已經正常工作,因此只需要和Leader機器簡歷連線,進行狀態同步。

執行期間的選舉

  1. leader宕機後,follower會變更伺服器狀態為LOOKING,進入選舉
  2. 執行期間,每個伺服器上的zxid可能不同,每個伺服器在第一輪投票也同樣地會投給自己。然後與啟動時投票類似。

可見每次投票都是對(vote_sid, vote_zxid)(self_sid, self_zxid)對比的過程。

監聽機制

  • 僅觸發一次:當資料改變時,一個監聽事件被髮送到客戶端,並取消監聽,除非客戶端再次設定監聽,否則不再監聽,所以在回撥中應該新增監聽。
  • 服務端維護了兩個監聽佇列:資料監聽佇列和孩子監聽佇列。getData()exists()設定資料監聽,getChildren()設定孩子監聽。setData()將觸發資料監聽,create()delete()將觸發一個節點被建立的資料監聽和孩子變化的孩子監聽。
  • 當一個客戶端連線到一個新服務端時,監聽將被觸發。在客戶端與服務端斷開連線後,監聽將不能傳送到客戶端,當客戶端重連後,任何先前的註冊監聽將自動重新註冊並根據情況觸發,整個過程自動完成。
  • 存在一種情況將出現監聽事件丟失:當客戶端與服務端斷開連線期間,一個被監聽的節點建立並且被刪除,客戶端將收不到任何監聽事件。
  • ZooKeeper服務端的監聽列表僅儲存在記憶體中,不做持久化。當一個客戶端與服務端斷開連線後,它所有的watch都會從記憶體中移除,客戶端會在重連後自動重新註冊它的所有watch

References:

《大資料日知錄》p96-p104

Zookeeper詳解(一):分散式與Zookeeper

Zab:Zookeeper 中的分散式一致性協議介紹

【分散式】Zookeeper的Leader選舉]

ZooKeeper監聽機制