一、zookeeper基礎
1.1、什麼是zookeeper?
先來看下zookeeper的圖標:
很帥有沒有。。。
zookeeper這個單詞本身是動物管理員的意思,也就是負責管理動物的崗位,學大數據的應該都熟悉幾個動物—Hadoop(會飛的大象)、Hive(小蜜蜂)、Flink(超人松鼠)以及可愛的pig(佩奇)等等等。那這個管理員是怎麼管理這些小動物的呢,管理他們的哪些方面呢?
在大數據環境下或者更準確的說是在分佈式環境下,zookeeper可以說無處不在。它是Google的Chubby一個開源的實現,是一個分佈式的、開源的程序協調服務,是 hadoop 項目下的一個子項目。它主要提供了配置管理、統一命名服務、分佈式鎖、集羣管理等服務。
從架構設計上來看,zookeeper主要分爲兩部分,文件系統和監聽通知機制。
1.2、文件系統
文件系統顧名思義就是一個多層級的類似咱們電腦上的文件夾之類的數據結構,如下圖所示:
每個子目錄項如 NameService 都被稱作爲 znode(這裏NameService屬於目錄節點),和平常使用的文件夾一樣,我們能夠自由的增加、刪除znode,在一個znode下增加、刪除子znode。當然znode有自己獨特的地方,也就是它的這些znode可以用來設置關聯的數據,具體就是隻有文件節點可以存放數據而目錄節點不可以。同時爲了保證高吞吐低延遲,zookeeper需要在內存中維護這個樹狀的目錄結構,因此zookeeper不能存放大量的數據,每個節點的存放數據上限是1M。
zookeeper一共有四個類型的znode節點:
-
PERSISTENT 持久化節點
持久化節點是指在節點創建後,就一直存在,直到有刪除操作來主動清除這個節點。否則不會因爲創建該節點的客戶端會話失效而消失。
-
PERSISTENT_SEQUENTIAL 持久順序節點
持久順序節點和持久化節點的基本特徵是一致的。只是它額外的特性是:在zookeeper中,每個父節點會爲他的第一級子節點維護一份時序,會記錄每個子節點創建的先後順序。基於這個特性,我們在創建子節點的時候,可以設置這個屬性,這樣在創建節點過程中,zookeeper會自動爲這個節點名加上一個數字後綴作爲新的節點名。 這個數字後綴的範圍是整型的最大值。 比如在創建節點的時候只需要傳入節點 “csdn_”,然後zookeeper自動會給”csdn_”後面補充數字。
-
EPHEMERAL 臨時節點
和持久節點不同的是,臨時節點的生命週期和客戶端會話綁定。也就是說,如果客戶端會話失效,那麼這個節點就會自動被清除掉。(注意:這裏提到的是會話失效,而非連接斷開)另外,在臨時節點下面不能創建子節點。 這裏還要注意一件事,就是當你客戶端會話失效後,所產生的節點並不是瞬間就消失了,而是需要一段時間,大概是 10 秒以內。例如,本機操作生成節點,在服務器端用命令來查看當前的節點數目,你會發現客戶端已經 stop,但是產生的節點還在。
-
EPHEMERAL_SEQUENTIAL 臨時自動編號節點
屬於帶有順序的臨時節點,在客戶端會話結束後節點就消失。
1.2、監聽通知機制
簡單來說就是客戶端會對某個znode建立一個watcher事件,當這個znode發生了變化,比如數據改變、刪除、子目錄節點新增刪除等情況時,客戶端會收到zookeeper的通知,然後好處就是客戶端就可以根據znode的變化來調整對應的業務情況。
二、實際生產中的作用
2.1、配置管理
通過前面介紹zookeeper的數據結構,相信很多人首先想到的就是它可以解決各種需要同步的麻煩的配置。比如數據庫連接等。一般我們都是使用配置文件的方式,在代碼中引入這些配置文件。當我們只有一種配置,只有一臺服務器,並且不經常修改的時候,使用配置文件是一個很好的做法,但是如果我們配置非常多, 有很多服務器都需要這個配置,這時使用配置文件就不是個好主意了。這個時候往往需要尋找一種集中管理配置的方法,就是希望我們在這個集中的地方修改了配置,然後所有使用這個配置的服務器上的服務都可以獲得變更。
Zookeeper就是這種服務,它使用Zab這種一致性協議來提供一致性。
其實有很多開源項目都是使用 Zookeeper 來維護配置,比如在HBase中,本來內置的就有zookeeper,也可以使用外部的zookeeper。它的客戶端就是通過連接一個Zookeeper,獲得必要的 HBase 集羣的配置信息,然後纔可以進一步操作。
再比如實際生產中必不可少的開源的消息隊列Kafka,也是使用 Zookeeper來維護broker的信息。還有Alibaba開源的SOA框架Dubbo中也是通過使用Zookeeper管理一些配置來實現服務治理的。
2.2、統一命名服務
統一命名服務比較容易理解,就像DNS一樣,由於我們訪問網絡時直接輸入IP地址非常不友好(記不住),因此誕生了域名,但是域名也不是每臺計算機都保存的,我們不可能在每臺計算機上保存全世界所有的域名和IP的映射。因此DNS誕生了,我們只需要固定的訪問一個大家都熟知的服務器(根服務器),它就會告訴你這個域名對應 的 IP 是什麼(根服務器一般會根據域名的構成來指向一級、二級服務器層層去尋找這個域名對應的IP)。
在我們的應用中也會存在很多這類問題,特別是在我們的服務特別多的時候,如果我們在本地保存服務的地址的時候將非常不方便,但是如果我們只需要訪問一個大家都熟知的訪問點,然後從這裏提供統一的入口,那麼維護起來將方便得多了。
2.3、分佈式鎖
zookeeper本質上是一個分佈式協調服務。因此我們可以利用zookeeper來協調多個分佈式進程之間的活動。比如在一個分佈式環境中,爲了提高可靠性,我們的集羣的每臺服務器上都部署着同樣的服務。但是,一件事情如果集羣中的每個服 務器都進行的話,那相互之間就要協調,編程起來將非常複雜。而如果我們只讓一個服務進行操作,那又存在單點故障風險。
因此通常情況下我們的做法就是使用分佈式鎖,在某個時刻只讓一個服務去幹活,當這臺服務出故障的時候把鎖釋放掉,並立即fail over到別的服務。這在很多分佈式系統中都是這麼做,這種設計有一個更好聽的名字叫 Leader Election(leader 選舉)。比如 HBase 的 Master 就是採用這種機制。但要注意的是分佈式鎖跟同一個進程的鎖還是有區別的,所以使用的時候要比同一個進程裏的鎖更謹慎的使用。
2.4、集羣管理等服務
在現在大數據的場景及微服務的場景下,由於是分佈式的集羣,經常會由於各種原因,比如硬件故障,軟件故障,網絡問題等,導致部分節點進進出出。有新的節點加入進來,也有老的節點退出集羣。這個時候,集羣中其他機器需要感知到這種變化,然後根據這種變化做出對應的決策。
比如我們是一個分佈式存儲系統,有一箇中央控制節點負責存儲的分配,當有新的存儲進來的時候我們要根據現在集羣目前的狀態來分配存儲節點。這個時候我們就需要動態感知到集羣目前的狀態。
再比如一個分佈式的 SOA 架構中,服務是一個集羣提供的,當消費者訪問某個服務時,就需要採用某種機制發現現在有哪些節點可以提供該服務(這也稱之爲服務發現,比如Alibaba開源的SOA框架Dubbo就採用了Zookeeper作爲服務發現的底層機制)。還有常用的Kafka 隊列就採用了Zookeeper作爲Cosnumer的上下線管理。
三、紙上得來總覺淺,zookeeper實戰
3.1、使用docker安裝zookeeper最新版
爲啥使用docker安裝呢?因爲我個人覺得學習一門技術主要是要掌握它的原理、架構、以及具體的使用,而不是糾結於安裝配置。(其實是因爲懶。。。。)
好,docker不管在任何系統下安裝任何東西步驟都一樣:
3.1.1、下載zookeeper鏡像
docker pull zookeeper
3.1.2、啓動容器並暴露2181端口
docker run --privileged=true -d --name zookeeper --publish 2181:2181 -d zookeeper:latest
3.1.3、查看容器狀態
docker ps
可以看到,我這個zookeeper已經連續啓動19個小時了(這篇文章比較費時間)。
3.1.4、一起來玩一些基本命令操作
-
以bin/bash進入到容器中
docker exec -it 60d /bin/bash
-
以本地2181客戶端連接zk,進行命令行界面
./zkCli.sh -server 127.0.0.1:2181
-
以bin/bash進入到容器中
docker exec -it 60d /bin/bash
-
啓動和關閉zookeeper(默認容器開啓後是啓動的)
#啓動zookeeper服務 ./zkServer.sh start #關閉zookeeper服務 ./zkServer.sh stop #查看zookeeper運行狀態 ./zkServer.sh status
-
以本地2181客戶端連接zk,進行命令行界面
./zkCli.sh -server 127.0.0.1:2181
-
善於使用help查看所有命令的幫助信息
-
通過上面的help命令,我們已經知道了常用命令有哪些,下面讓我們實戰一下基本的常用操作
-
ls 查看,create 新增,get 獲取節點數據
上圖命令解析如下:# 查看根下有哪些znode節點(可以理解爲目錄) ls / # 在根下新增一個名爲csdn的節點,並賦予數據qq_26803795(我的csdn號) # 注意:create 後面可以跟參數 create [-s] [-e] path data acl # -s 代表sequence,也就是創建順序節點,可以自動累加 # -e 代表創建臨時節點 create /csdn qq_26803795 # 再次查看根下有哪些znode節點 ls / # 獲取csdn節點的數據信息 get /csdn
-
stat 獲取節點的更新信息,ls2 是ls和stat兩個命令的整合(但是請注意,ls2命令已經不建議使用,官方在新版zookeeper建議使用ls -s 來替代ls2)
-
set修改節點
首先上圖是新建了一個csdn2的節點並賦值test,隨後使用了set對這個節點的值進行了更改,當然這個操作也可以伴隨着dataVersion使用。 -
delete 刪除節點
如下圖,我們把csdn2節點刪除掉:
-
3.2、使用idea配置zookeeper plugin
強大的idea提供了一個zookeeper的插件,可以用來連接zookeeper及查看內容。
- windows:左上角File------>Settings------>Plugins
- Mac:左上角Intellij IDEA------>Preserences------>Plugins
在plugin裏輸入zoo,然後選擇第二個zoolytic並點擊install進行安裝(第一個zookeeper在新版idea裏已經不能用了,已踩坑。。。。),如下圖:
安裝後重啓idea
然後在點擊右上角zoolytic,打開後,點擊➕號,然後在彈框中輸入localhost:2181,最後點擊ok,如下圖:
在列表裏點擊剛纔的localhost:2181,展開後,點擊connect,如下圖:
然後就會出現咱們剛纔新增的csdn節點,並且這個節點上的數據qq_26803795(這是我的csdn ID)也會出現,如下圖:
感興趣的可以好好的把玩一番。
3.3、Talk is cheap ,Show me the code
只會這些命令行或者利用插件可視化操作顯然是不夠的,所以接下來就是掉頭髮的時間了。。。。
首先zookeeper提供了3種常用的客戶端api,分別是:原生zookeeper(官方,較底層,不推薦使用)、Apache Curator(最完美,推薦大家使用)以及開源的zkClient,這些客戶端的優缺點對比,待會看下代碼就清楚了。
3.3.1、首先把三種客戶端的maven依賴搞進來
下面是我的pom.xml文件內容:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>zookeeperDemoTest</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- 原生zookeeper,我用的是3.5.7版本 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.7</version>
</dependency>
<!--Apache Curator-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.9.0</version>
</dependency>
<!-- 開源的zkclient -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.9</version>
</dependency>
</dependencies>
</project>
3.3.2、 原生zookeeper基本操作demo
首先zookeeper官方提供的原生api比較偏底層,zookeeper類對象除了需要zookeeper服務端連接IP:端口,還必須要提供一個watcher對象(監聽器)。watcher是一個接口,當服務器znode節點花發生變化就會以事件的形式通知watcher對象。所以Watcher常用來監聽節點,當節點發生變化時客戶端就會監聽到。
zookeeper類提供的對節點進行增刪改查的主要方法介紹如下:
- create:用於創建節點,可以指定節點路徑、節點數據、節點的訪問權限、節點類型
- delete:刪除節點,每個節點都有一個版本,刪除時可指定刪除的版本,類似樂觀鎖。設置-1,則就直接刪除節點。
- exists:節點存不存在,若存在返回節點Stat信息,否則返回null
- getChildren:獲取子節點
- getData/setData:獲取節點數據
- getACL/setACL:獲取節點訪問權限列表,每個節點都可以設置訪問權限,指定只有特定的客戶端才能訪問和操作節點。
下面寫一個demo測試一下:
import org.apache.zookeeper.*;
import java.io.IOException;
public class ZookeeperDemo {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
//連接zookeeper並且註冊監聽器
ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 3000, new Watcher() {
public void process(WatchedEvent watchedEvent) {
System.out.println(watchedEvent.toString());
}
});
System.out.println("OK!");
// 創建一個目錄節點
/**
* CreateMode:
* PERSISTENT (持續的,相對於EPHEMERAL,不會隨着client的斷開而消失)
* PERSISTENT_SEQUENTIAL(持久的且帶順序的)
* EPHEMERAL (短暫的,生命週期依賴於client session)
* EPHEMERAL_SEQUENTIAL (短暫的,帶順序的)
* 節點訪問權限說明:
* 節點訪問權限由List確定,但是有幾個便捷的靜態屬性可以選擇:
* Ids.CREATOR_ALL_ACL:只有創建節點的客戶端纔有所有權限\
* Ids.OPEN_ACL_UNSAFE:這是一個完全開放的權限,所有客戶端都有權限
* Ids.READ_ACL_UNSAFE:所有客戶端只有讀取的
*/
zk.create("/zookeeperDemo", "RopleData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 創建一個子目錄節點
zk.create("/zookeeperDemo/type1", "RopleData/type1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(new String(zk.getData("/zookeeperDemo", false, null)));
// 取出子目錄節點列表
System.out.println(zk.getChildren("/zookeeperDemo", true));
// 創建另外一個子目錄節點
zk.create("/zookeeperDemo/type2", "RopleData/type2".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(zk.getChildren("/zookeeperDemo", true));
// 修改子目錄節點type1的數據
zk.setData("/zookeeperDemo/type1", "RopleData/type3".getBytes(), -1);
byte[] datas = zk.getData("/zookeeperDemo/type1", true, null);
String str = new String(datas, "utf-8");
System.out.println(str);
// 刪除整個子目錄 -1代表version版本號,-1是刪除所有版本
// zk.delete("/zookeeperDemo/type1", -1);
// zk.delete("/zookeeperDemo/type2", -1);
// zk.delete("/zookeeperDemo", -1);
// System.out.println(str);
Thread.sleep(10000);
zk.close();
System.out.println("OK");
}
}
運行結果爲:
然後我們通過zoolytic可視化查看節點數據信息:
3.3.3、開源的zkClinet基本操作demo
zkClient提供了許多封裝好的api方法,操作簡便直觀,下面我們舉個簡單的例子:
import org.I0Itec.zkclient.ZkClient;
import org.apache.zookeeper.CreateMode;
public class ZKclientDemo {
public static void main(String[] args) throws Exception {
ZkClient zkClient = new ZkClient("127.0.0.1:2181");//建立連接
zkClient.create("/zkClientDemo", "RopleData", CreateMode.PERSISTENT);//創建目錄並寫入數據
// 獲取節點數據並輸出
String data = zkClient.readData("/zkClientDemo");
System.out.println(data);
//zkClient.delete("/zkClientDemo");//刪除目錄
//zkClient.deleteRecursive("/zkClientDemo");//遞歸刪除節目錄
}
}
運行結果:
RopleData
然後查看zoolytic:
3.3.4、Apache Curator基本操作demo
最後介紹一下最方便的curator的基本操作,想深入掌握的同學可以去apache curator的官網深入研究:Apache Curator的官方文檔。
首先介紹curator的幾個組成部分:
- Client: 是ZooKeeper客戶端的一個替代品, 提供了一些底層處理和相關的工具方法
- Framework: 用來簡化ZooKeeper高級功能的使用, 並增加了一些新的功能, 比如管理到ZooKeeper集羣的連接, 重試處理
- Recipes: 實現了通用ZooKeeper的recipe, 該組件建立在Framework的基礎之上
- Utilities:各種ZooKeeper的工具類
- Errors: 異常處理, 連接, 恢復等
- Extensions: recipe擴展
瞭解了這些理論其實也沒什麼用(容易忘),還是自己多操作一下體會的更深:
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.ZooDefs;
import java.util.List;
public class CuratorDemo {
public static void main(String[] args) throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", new RetryNTimes(10, 2000));
client.start();// 連接zookeeper服務器
// 獲取子節點,並進行監控
List<String> children = client.getChildren().usingWatcher(new CuratorWatcher() {
public void process(WatchedEvent event) throws Exception {
System.out.println("監控: " + event);
}
}).forPath("/");
System.out.println(children);
// 創建節點
String result = client.create().withMode(CreateMode.PERSISTENT).withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath("/curatorDemo", "RopleData".getBytes());
System.out.println(result);
// 設置節點數據
client.setData().forPath("/curatorDemo", "NewRopleData".getBytes());
// 刪除節點
//System.out.println(client.checkExists().forPath("/curatorDemo"));
// client.delete().withVersion(-1).forPath("/curatorDemo");
// System.out.println(client.checkExists().forPath("/curatorDemo"));
client.close();
System.out.println("OK!");
}
}
運行結果:
我們再次使用zoolytic查看結果:
所有代碼已上傳到我的github,感興趣的可以的下載查看:RopleData
四、補充一點相關面試題
-
zookeeper底層算法是Fast Paxos算法,它的前身Paxos算法存在活鎖的問題,即當有多個proposer交錯提交時,有可能互相排斥導致沒有一個proposer能提交成功,後來Fast Paxos進行了一些優化,通過選舉產生一個leader (領導者),只有leader才能提交proposer;
-
zookeeper選舉機制採用半數機制,當整個集羣中有某臺服務器節點獲得了超過半數的投票,那它就會成爲leader:
半數機制:2n+1
在實際生產中根據集羣規模安裝zookeeper臺數經驗如下:(左爲集羣規模,右爲zookeeper規模)
10 臺服務器:3 臺zookeeper
20 臺服務器:5 臺zookeeper
100 臺服務器:11 臺zookeeper
-
zookeeper規模並不是越多越好,因爲當zookeeper規模太多時,較長的選舉時間反而會影響性能。