1. 程式人生 > >Zookeeper什麼,它可以做什麼?看了這篇就懂了

Zookeeper什麼,它可以做什麼?看了這篇就懂了

## 前言 什麼是ZooKeeper,你真的瞭解它嗎。我們一起來看看吧~ ## 什麼是 ZooKeeper ZooKeeper 是 Apache 的一個頂級專案,為分散式應用提供高效、高可用的分散式協調服務,提供了諸如資料釋出/訂閱、負載均衡、命名服務、分散式協調/通知和分散式鎖等分散式基礎服務。由於 ZooKeeper 便捷的使用方式、卓越的效能和良好的穩定性,被廣泛地應用於諸如 Hadoop、HBase、Kafka 和 Dubbo 等大型分散式系統中。 Zookeeper 有三種執行模式:單機模式、偽叢集模式和叢集模式。 **單機模式:** 這種模式一般適用於開發測試環境,一方面我們沒有那麼多機器資源,另外就是平時的開發除錯並不需要極好的穩定性。 **叢集模式:** 一個 ZooKeeper 叢集通常由一組機器組成,一般 3 臺以上就可以組成一個可用的 ZooKeeper 叢集了。組成 ZooKeeper 叢集的每臺機器都會在記憶體中維護當前的伺服器狀態,並且每臺機器之間都會互相保持通訊。 **偽叢集模式:** 這是一種特殊的叢集模式,即叢集的所有伺服器都部署在一臺機器上。當你手頭上有一臺比較好的機器,如果作為單機模式進行部署,就會浪費資源,這種情況下,ZooKeeper 允許你在一臺機器上通過啟動不同的埠來啟動多個 ZooKeeper 服務例項,以此來以叢集的特性來對外服務。 ## ZooKeeper 的相關知識 Zookeeper 中的角色: **領導者(leader):** 負責進行投票的發起和決議,更新系統狀態。 **跟隨者(follower):** 用於接收客戶端請求並給客戶端返回結果,在選主過程中進行投票。 **觀察者(observer):** 可以接受客戶端連線,將寫請求轉發給 leader,但是observer 不參加投票的過程,只是為了擴充套件系統,提高讀取的速度。 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1eae0aaf0ab344fb84509652d3f49d87~tplv-k3u1fbpfcp-watermark.image) ## Zookeeper 的資料模型 1.層次化的目錄結構,命名符合常規檔案系統規範,類似於 Linux。 2.每個節點在 Zookeeper 中叫做 Znode,並且其有一個唯一的路徑標識。 3.節點 Znode 可以包含資料和子節點,但是 EPHEMERAL 型別的節點不能有子節點。 4.Znode 中的資料可以有多個版本,比如某一個路徑下存有多個數據版本,那麼查詢這個路徑下的資料就需要帶上版本。 5.客戶端應用可以在節點上設定監視器。 6.節點不支援部分讀寫,而是一次性完整讀寫。 ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c4a10ac67d1d4d31a7bf6bf3fb78751e~tplv-k3u1fbpfcp-watermark.image) ## ZooKeeper 的節點特性 ZooKeeper 節點是生命週期的,這取決於節點的型別。在 ZooKeeper 中,節點根據持續時間可以分為持久節點(PERSISTENT)、臨時節點(EPHEMERAL),根據是否有序可以分為順序節點(SEQUENTIAL)、和無序節點(預設是無序的)。 持久節點一旦被建立,除非主動移除,不然一直會儲存在 Zookeeper 中(不會因為建立該節點的客戶端的會話失效而消失)。 ## Zookeeper 的應用場景 ZooKeeper 是一個高可用的分散式資料管理與系統協調框架。基於對 Paxos 演算法的實現,使該框架保證了分散式環境中資料的強一致性,也正是基於這樣的特性,使得 ZooKeeper 解決很多分散式問題。 值得注意的是,ZooKeeper 並非天生就是為這些應用場景設計的,都是後來眾多開發者根據其框架的特性,利用其提供的一系列 API 介面(或者稱為原語集),摸索出來的典型使用方法。 ## 資料釋出與訂閱(配置中心) 釋出與訂閱模型,即所謂的配置中心,顧名思義就是釋出者將資料釋出到 ZooKeeper 節點上,供訂閱者動態獲取資料,實現配置資訊的集中式管理和動態更新。例如全域性的配置資訊,服務式服務框架的服務地址列表等就非常適合使用。 應用中用到的一些配置資訊放到 ZooKeeper 上進行集中管理。這類場景通常是這樣:應用在啟動的時候會主動來獲取一次配置,同時在節點上註冊一個 Watcher。這樣一來,以後每次配置有更新的時候,都會實時通知到訂閱的客戶端,從來達到獲取最新配置資訊的目的。 分散式搜尋服務中,索引的元資訊和伺服器叢集機器的節點狀態存放在 ZooKeeper 的一些指定節點,供各個客戶端訂閱使用。 ## 分散式日誌收集系統 這個系統的核心工作是收集分佈在不同機器的日誌。收集器通常是按照應用來分配收集任務單元,因此需要在 ZooKeeper 上建立一個以應用名作為 path 的節點 P,並將這個應用的所有機器 IP,以子節點的形式註冊到節點 P 上。這樣一來就能夠實現機器變動的時候,能夠實時通知到收集器調整任務分配。 系統中有些資訊需要動態獲取,並且還會存在人工手動去修改這個資訊的發問。通常是暴露出介面,例如 JMX 介面,來獲取一些執行時的資訊。引入 ZooKeeper 之後,就不用自己實現一套方案了,只要將這些資訊存放到指定的 ZooKeeper 節點上即可。 **注意:** 在上面提到的應用場景中,有個預設前提——資料量很小,但是資料更新可能會比較快的場景。 ## 負載均衡 這裡說的負載均衡是指軟負載均衡。在分散式環境中,為了保證高可用性,通常同一個應用或同一個服務的提供方都會部署多份,達到對等服務。而消費者就須要在這些對等的伺服器中選擇一個來執行相關的業務邏輯,其中比較典型的是訊息中介軟體中的生產者,消費者負載均衡。 ## 命名服務(Naming Service) 命名服務也是分散式系統中比較常見的一類場景。在分散式系統中,通過使用命名服務,客戶端應用能夠根據指定名字來獲取資源或服務的地址,提供者等資訊。被命名的實體通常可以是叢集中的機器,提供的服務地址,遠端物件等等——這些我們都可以統稱它們為名字(Name)。其中較為常見的就是一些分散式服務框架中的服務地址列表。通過呼叫 ZooKeeper 提供的建立節點的 API,能夠很容易建立一個全域性唯一的path,這個 path 就可以作為一個名字。 阿里巴巴集團開源的分散式服務框架 Dubbo 中使用 ZooKeeper 來作為其命名服務,維護全域性的服務地址列表。在 Dubbo 的實現中: 1.服務提供者在啟動的時候,向 ZooKeeper 上的指定節點 /dubbo/${serviceName}/providers 目錄下寫入自己的 URL 地址,這個操作就完成了服務的釋出。 2.服務消費者啟動的時候,訂閱 /dubbo/${serviceName}/providers 目錄下的提供者 URL 地址, 並向 /dubbo/${serviceName} /consumers 目錄下寫入自己的 URL 地址。 **注意:** 所有向 ZooKeeper 上註冊的地址都是臨時節點,這樣就能夠保證服務提供者和消費者能夠自動感應資源的變化。 另外,Dubbo 還有針對服務粒度的監控。方法是訂閱 /dubbo/${serviceName} 目錄下所有提供者和消費者的資訊。 ## 分散式通知/協調 ZooKeeper 中特有 Watcher 註冊與非同步通知機制,能夠很好的實現分散式環境下不同系統之間的通知與協調,實現對資料變更的實時處理。使用方法通常是不同系統都對 ZooKeeper 上同一個 Znode 進行註冊,監聽 Znode 的變化(包括 Znode 本身內容及子節點的),其中一個系統 Update 了 Znode,那麼另一個系統能夠收到通知,並作出相應處理。 另一種心跳檢測機制:檢測系統和被檢測系統之間並不直接關聯起來,而是通過 ZooKeeper 上某個節點關聯,大大減少系統耦合。 另一種系統排程模式:某系統有控制檯和推送系統兩部分組成,控制檯的職責是控制推送系統進行相應的推送工作。管理人員在控制檯作的一些操作,實際上是修改了 ZooKeeper 上某些節點的狀態,而 ZooKeeper 就把這些變化通知給它們註冊 Watcher 的客戶端,即推送系統。於是,作出相應的推送任務。 另一種工作彙報模式:一些類似於任務分發系統。子任務啟動後,到 ZooKeeper 來註冊一個臨時節點,並且定時將自己的進度進行彙報(將進度寫回這個臨時節點)。這樣任務管理者就能夠實時知道任務進度。 ## 分散式鎖 分散式鎖主要得益於 ZooKeeper 為我們保證了資料的強一致性。鎖服務可以分為兩類:一類是保持獨佔,另一類是控制時序。 所謂保持獨佔,就是所有試圖來獲取這個鎖的客戶端,最終只有一個可以成功獲得這把鎖。通常的做法是把 ZooKeeper 上的一個 Znode 看作是一把鎖,通過 create znode的方式來實現。所有客戶端都去建立 /distribute_lock 節點,最終成功建立的那個客戶端也即擁有了這把鎖。 控制時序,就是所有檢視來獲取這個鎖的客戶端,最終都是會被安排執行,只是有個全域性時序了。做法和上面基本類似,只是這裡 /distribute_lock 已經預先存在,客戶端在它下面建立臨時有序節點(這個可以通過節點的屬性控制:CreateMode.EPHEMERAL_SEQUENTIAL 來指定)。ZooKeeper 的父節點(/distribute_lock)維持一份 sequence,保證子節點建立的時序性,從而也形成了每個客戶端的全域性時序。 1.由於同一節點下子節點名稱不能相同,所以只要在某個節點下建立 Znode,建立成功即表明加鎖成功。註冊監聽器監聽此 Znode,只要刪除此 Znode 就通知其他客戶端來加鎖。 2.建立臨時順序節點:在某個節點下建立節點,來一個請求則建立一個節點,由於是順序的,所以序號最小的獲得鎖,當釋放鎖時,通知下一序號獲得鎖。 ## 分散式佇列 佇列方面,簡單來說有兩種:一種是常規的先進先出佇列,另一種是等佇列的隊員聚齊以後才按照順序執行。對於第一種的佇列和上面講的分散式鎖服務中控制時序的場景基本原理一致,這裡就不贅述了。 第二種佇列其實是在 FIFO 佇列的基礎上作了一個增強。通常可以在 /queue 這個 Znode 下預先建立一個 /queue/num 節點,並且賦值為 n(或者直接給 /queue 賦值 n)表示佇列大小。之後每次有佇列成員加入後,就判斷下是否已經到達佇列大小,決定是否可以開始執行了。 這種用法的典型場景是:分散式環境中,一個大任務 Task A,需要在很多子任務完成(或條件就緒)情況下才能進行。這個時候,凡是其中一個子任務完成(就緒),那麼就去 /taskList 下建立自己的臨時時序節點(CreateMode.EPHEMERAL_SEQUENTIAL)。當 /taskList 發現自己下面的子節點滿足指定個數,就可以進行下一步按序進行處理了。 ## 使用 dokcer-compose 搭建叢集 上面我們介紹了關於 ZooKeeper 有這麼多的應用場景,那麼接下來就先學習如何搭建 ZooKeeper 叢集然後再進行實戰上面的應用場景。 檔案的目錄結構如下: ``` ├── docker-compose.yml ``` ## 編寫 docker-compose.yml 檔案 docker-compose.yml 檔案內容如下: ``` version: '3.4' services: zoo1: image: zookeeper restart: always hostname: zoo1 ports: - 2181:2181 environment: ZOO_MY_ID: 1 ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181 zoo2: image: zookeeper restart: always hostname: zoo2 ports: - 2182:2181 environment: ZOO_MY_ID: 2 ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=0.0.0.0:2888:3888;2181 server.3=zoo3:2888:3888;2181 zoo3: image: zookeeper restart: always hostname: zoo3 ports: - 2183:2181 environment: ZOO_MY_ID: 3 ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=0.0.0.0:2888:3888;2181 ``` 在這個配置檔案中,Docker 運行了 3 個 Zookeeper 映象,通過 ports 欄位分別將本地的 2181, 2182, 2183 埠繫結到對應容器的 2181 埠上。 ZOO_MY_ID 和 ZOO_SERVERS 是搭建 Zookeeper 叢集需要的兩個環境變數。ZOO_MY_ID 標識服務的 id,為 1-255 之間的整數,必須在叢集中唯一。ZOO_SERVERS 是叢集中的主機列表。 在 docker-compose.yml 所在目錄下執行 docker-compose up,可以看到啟動的日誌。 ## 連線 ZooKeeper 將叢集啟動起來以後我們可以連線 ZooKeeper 對其進行節點的相關操作。 1.首先需要下載 ZooKeeper。 2.將其解壓。 3.進入其 conf/ 目錄,將 zoo_sample .cfg 改成 zoo.cfg。 ## 配置檔案說明 ``` # The number of milliseconds of each tick # tickTime:CS通訊心跳數 # Zookeeper 伺服器之間或客戶端與伺服器之間維持心跳的時間間隔,也就是每個 tickTime 時間就會發送一個心跳。tickTime以毫秒為單位。 tickTime=2000 # The number of ticks that the initial # synchronization phase can take # initLimit:LF初始通訊時限 # 叢集中的follower伺服器(F)與leader伺服器(L)之間初始連線時能容忍的最多心跳數(tickTime的數量)。 initLimit=5 # The number of ticks that can pass between # sending a request and getting an acknowledgement # syncLimit:LF同步通訊時限 # 叢集中的follower伺服器與leader伺服器之間請求和應答之間能容忍的最多心跳數(tickTime的數量)。 syncLimit=2 # the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes. # dataDir:資料檔案目錄 # Zookeeper儲存資料的目錄,預設情況下,Zookeeper將寫資料的日誌檔案也儲存在這個目錄裡。 dataDir=/data/soft/zookeeper-3.4.12/data # dataLogDir:日誌檔案目錄 # Zookeeper儲存日誌檔案的目錄。 dataLogDir=/data/soft/zookeeper-3.4.12/logs # the port at which the clients will connect # clientPort:客戶端連線埠 # 客戶端連線 Zookeeper 伺服器的埠,Zookeeper 會監聽這個埠,接受客戶端的訪問請求。 clientPort=2181 # the maximum number of client connections. # increase this if you need to handle more clients #maxClientCnxns=60 # # Be sure to read the maintenance section of the # administrator guide before turning on autopurge. # # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance # # The number of snapshots to retain in dataDir #autopurge.snapRetainCount=3 # Purge task interval in hours # Set to "0" to disable auto purge feature #autopurge.purgeInterval=1 # 伺服器名稱與地址:叢集資訊(伺服器編號,伺服器地址,LF通訊埠,選舉埠) # 這個配置項的書寫格式比較特殊,規則如下: # server.N=YYY:A:B # 其中N表示伺服器編號,YYY表示伺服器的IP地址,A為LF通訊埠,表示該伺服器與叢集中的leader交換的資訊的埠。B為選舉埠,表示選舉新leader時伺服器間相互通訊的埠(當leader掛掉時,其餘伺服器會相互通訊,選擇出新的leader)。一般來說,叢集中每個伺服器的A埠都是一樣,每個伺服器的B埠也是一樣。但是當所採用的為偽叢集時,IP地址都一樣,只能時A埠和B埠不一樣。 ``` 可以不修改 zoo.cfg,使用預設配置。接下來在解壓後的 bin/ 目錄中執行命令 ./zkCli.sh -server 127.0.0.1:2181 就能進行連線了。 ``` Welcome to ZooKeeper! 2020-06-01 15:03:52,512 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@1025] - Opening socket connection to server localhost/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error) JLine support is enabled 2020-06-01 15:03:52,576 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@879] - Socket connection established to localhost/127.0.0.1:2181, initiating session 2020-06-01 15:03:52,599 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@1299] - Session establishment complete on server localhost/127.0.0.1:2181, sessionid = 0x100001140080000, negotiated timeout = 30000 WATCHER:: WatchedEvent state:SyncConnected type:None path:null [zk: 127.0.0.1:2181(CONNECTED) 0] ``` 接下來可以使用命令檢視節點: 使用 ls 命令檢視當前 ZooKeeper 中所包含的內容。命令:ls / ``` [zk: 127.0.0.1:2181(CONNECTED) 10] ls / [zookeeper] ``` 建立了一個新的 znode 節點 zk 以及與它關聯的字串。命令:create /zk myData ``` [zk: 127.0.0.1:2181(CONNECTED) 11] create /zk myData Created /zk [zk: 127.0.0.1:2181(CONNECTED) 12] ls / [zk, zookeeper] [zk: 127.0.0.1:2181(CONNECTED) 13] ``` 獲取 znode 節點 zk。命令:get /zk ``` [zk: 127.0.0.1:2181(CONNECTED) 13] get /zk myData cZxid = 0x400000008 ctime = Mon Jun 01 15:07:50 CST 2020 mZxid = 0x400000008 mtime = Mon Jun 01 15:07:50 CST 2020 pZxid = 0x400000008 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 6 numChildren = 0 ``` 刪除 znode 節點 zk。命令:delete /zk ``` [zk: 127.0.0.1:2181(CONNECTED) 14] delete /zk [zk: 127.0.0.1:2181(CONNECTED) 15] ls / [zookeeper] ``` 由於篇幅有限,在接下來的文章中會根據上面提到的 ZooKeeper 應用場景逐一進行用程式碼進行實現。 **大家可以直接從 GitHub 拉取專案,啟動只需要兩步:** 1.從 GitHub 上面拉取專案。 2.在 ZooKeeper 資料夾中執行 docker-compose up 命令。 ## 最後 我這邊整理了一份:Zookeeper相關資料,Java核心知識點(包括Spring全家桶系列、面試專題和20年最新的網際網路真題、電子書等)有需要的朋友可以關注公眾號【程式媛小琬】即可