1.1 Zookeeper介紹
Zookeeper是分佈式應用程序的協調服務框架,是Hadoop的重要組件。ZK要解決的問題:
1.分佈式環境下的數據一致性。
2.分佈式環境下的統一命名服務
3.分佈式環境下的配置管理
4.分佈式環境下的分佈式鎖
單臺機器使用的鎖:同步代碼塊、重入鎖。但是在分佈式環境這個鎖就發揮不出來作用。分佈鎖分爲共享鎖和排他鎖。
5.集羣管理問題
1.2 分佈式概念(集羣)
分佈式的思想就是人多幹活快,即用多臺機器同時處理一個任務。分佈式的編程和單機的編程 思想是不同的,隨之也帶來新的問題和挑戰。
1.3 分佈式編程容易出現的問題
1.死鎖2.活鎖。
活鎖定義:在程序裏,由於某些條件的發生碰撞,導致重新執行,再碰撞=》再執 行,如此循環往復,就形成了活鎖。活鎖的危害:多個線程爭用一個資源,但是沒有任何一個 線程能拿到這個資源。(死鎖是有一個線程拿到資源,但相互等待互不釋放造成死鎖),活鎖 是死鎖的變種。補充:活鎖更深層次的危害,很耗盡Cpu資源(在做無意義的調度)
擴展:zk是根據Google的一篇論文
《The Chubby lock service for loosely coupled distributed systems》
3.需要考慮集羣的管理問題,需要有一套機制來檢測到集羣裏節點的狀態變化。(可以用心跳 機制來做,但zk不是用心跳機制來做的)
4.如果用一臺機器做集羣管理,存在單點故障問題,所以針對集羣管理,也需要形成一個集羣
5.管理集羣裏Leader的選舉問題(要根據一定的算法和規則來選舉),包括要考慮Leader掛掉 之後,如何從剩餘的follower裏選出Leader
6.分佈式鎖的實現,用之前學的重入鎖,同步代碼塊是做不了的
1.4 Zookeeper的名字
動物園管理員
2 Zookeeper單機模式安裝
實現步驟:
1. 準備虛擬機
克隆一個純淨的虛擬機
連接克隆佔用的空間較少,但是母體壞掉的話,克隆出來的虛擬機將不能使用
2.安裝和配置jdk
3.上傳和安裝zk
A.上傳目錄:/usr/soft 上傳 rz 或者使用ssh工具
B.tar -zxvf zookeeper-3.4.7.tar.gz
4.配置zk的配置文件
目錄結構:
bin 指令
conf 配置文件
lib 運行jar包庫
進入conf目錄,執行:
cp zoo_sample.cfg zoo.cfg
5.啓動zk
進入bin目錄,執行:
sh zkServer.sh start 或者./zkServer.sh start
可以通過jps指令查看活動的java進程
zk的進程是:QuorumPeerMain
6.進入zk客戶端,操作zk
進入bin目錄,執行: ./zkCli.sh
關閉zk服務:
sh zkServer.sh stop 或者
jps查看zk的進程id
kill -15 zk進程id
3 Zookeeper指令與數據結構
Zk數據結構
1. ZK有一個最開始的節點
2. ZK的節點叫做znode節點
3. 每個znode節點都可存儲數據
4. 每個znode節點都可創建自己的子節點
5. 多個znode節點共同形成了znode樹
6. Znode樹的維繫實在內存中,目的是供用戶快速的查詢
7. 每個znode節點都是一個路徑(通過路徑來定位這個節點)
8. 每個路徑名都是唯一的。
ZK指令
指令 |
示例 |
ls查看指令 |
ls / |
create創建節點指令,注意,在創建節點時,要分配初始數據。 |
create /zk01 '' create /zk02 'hello' |
get查看節點數據指令 hello 數據 cZxid = 0x2 ctime = Mon May 15 05:58:32 PDT 2017創建節點的時間戳 mZxid = 0x2 mtime = Mon May 15 05:58:32 PDT 2017修改此節點數據的最新時間戳 pZxid = 0x2 cversion = 0 dataVersion = 0數據版本號,每當數據發生編號,版本號遞增1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5數據大小 numChildren = 0子節點個數 |
get /zk02 |
set更新節點數據指令(執行後mtime、dataVersion可定會放生變化,dataLength可能會變化) |
set /zk01 hellozk |
delete刪除節點:如果存在子節點,不讓刪除。 只有刪除子節點後才能刪除。 |
delete /zk01 |
create指令補充: 1. 創建子節點 2. Zk節點分四種類型:分別是: 普通持久節點: 普通臨時節點:創建此臨時節點的客戶端失去和zk連接後,此節點消失.zk是通過臨時節點監控哪個服務器掛掉的。
順序持久節點:會根據用戶指定的節點路徑,自動分配一個遞增的順序號。(順序節點實現分佈式鎖的效果,服務器1搶到zk05分配zk050001,服務器2搶到zk05分配zk050002) 順序臨時節點: |
1. create /zk01/node01 hello 2. 2.1.create /zk01 hello 2.2. create –e /zk02 abc 2.4.create –s -e /zk05 abcd zk050000000003 再創建一個就是: zk050000000004 |
quit退出zk客戶端 |
|
4 Zk API
4.1 項目搭建
實現步驟:
1. 關閉虛擬機的防火牆 service iptables stop(臨時關閉)
永久關閉:chkconfigiptables off
2. 創建一個maven工程
修改maven工程的幾個參數
(1).修改默認的jdk,從1.5改爲1.7
(2).修改JavaCompiler版本,從1.5改爲1.7
點擊Apply->OK
(3).從提供的資料中將pom.xml文件拷貝過來替換項目中的同名文件。
zk需要的核心jar包
環境就搭建好了,接下來我們建測試類。(刪除不需要的App.java)
4.2 maven補充
學生機由於不能聯網,所以maven可能不能用,如果maven不能用:
1、 將apache-maven-3.3.1.zip解壓的D:\Tool
2、 將zebra_mvnrepository(私服)拷貝到D:\Tool
3、 修改D:\Tool\apache-maven-3.3.1\conf\settings.xml
<localRepository>D:\Tool\zebra_mvnrepository</localRepository>
修改該屬性後,表示只要使用該maven的所有項目下載時全部使用該私服。
4、 修改Eclipse使用本地的maven
5、 修改maven使用的配置文件,指向私服
擴展閱讀:maven超級pom(最頂層的父pom.xml)
maven-model-builder-3.3.1.jar->org\apache\maven\model\pom-4.0.0.xml
<directory>${project.basedir}/target</directory> <!—發佈完後的項目保存位置--> <outputDirectory>${project.build.directory}/classes</outputDirectory> <finalName>${project.artifactId}-${project.version}</finalName> <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory> <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory> <scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory> <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory> |
<repositories> <repository> <id>central</id> <name>Central Repository</name> <url>https://repo.maven.apache.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
<pluginRepositories> <pluginRepository> <id>central</id> <name>Central Repository</name> <url>https://repo.maven.apache.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <releases> <updatePolicy>never</updatePolicy> </releases> </pluginRepository> </pluginRepositories> |
將以上兩個節點指定默認的中央倉庫,可以將之拷貝不同項目的pom.xml文件,分別修改,可以實現不同項目引用不同私服或公服。
4.3 API演示
4.3.1 編寫測試類,測試連接
/**connectString連接zk服務器的ip(192.168.80.50)和port (2181) * sessionTimeout:回話的超時時間,單位爲毫秒 * watcher:監聽器對象 * @throws Exception */ @Test public void testConect() throws Exception{ ZooKeeper zk = new ZooKeeper("192.168.80.50:2181", 3000, new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("連接成功"); } }); while(true); } } |
爲何添加while(true)就可以,原因是:
zk連接是一個非阻塞連接方法,連接還沒有來的急建立,該線程已經結束。
在連接成功後,使用zk.create(…)或zk.get(),那麼如何保證執行到該行時保證連接成功,可以使用閉鎖。
public void testConect() throws Exception{ final CountDownLatch cdl = new CountDownLatch(1); ZooKeeper zk = new ZooKeeper("192.168.80.50:2181", 3000, new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("連接成功"); cdl.countDown(); } }); //while(true); cdl.await(); } |
4.3.2 編寫測試創建節點
@Test publicvoid testCreate() throws Exception{ final CountDownLatch cdl = new CountDownLatch(1); ZooKeeper zk = new ZooKeeper("192.168.80.50:2181", 3000, new Watcher() { @Override publicvoid process(WatchedEvent event) { System.out.println("連接成功"); cdl.countDown(); } }); cdl.await(); //path:節點路徑 //data:節點的內容 //acl:節點的權限 //createMode:節點的類型 //PERSISTENT(persistent):普通持久節點 //EPHEMERAL(ephemeral):普通臨時節點 //PERSISTENT_SEQUENTIAL:順序持久節點 //EPHEMERAL_SEQUENTIAL:順序臨時節點 zk.create("/zk02","hellozk".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL); while(true); } |
如果創建不成功,有可能是該節點已經存在了。
4.3.3 測試get方法
@Test public void testGet() throws Exception{ ……//省略zk的創建。 //path:獲取節點的地址 //watcher監聽器 //stat:查詢後,會將節點的其它信息封裝stat中 Stat stat = new Stat(); byte[] data = zk.getData("/zk01", new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("獲取到數據"); } }, stat); System.out.println(new String(data)); System.out.println(stat); } |
4.3.4 測試set
@Test public void testSet() throws Exception{ …… //path:修改節點的地址 //data:修改後節點的內容。 //version:修改節點對應的版本號 zk.setData("/zk01", "you can't sleep".getBytes(), -1); while(true); } |
4.3.5 獲取子節點
@Test publicvoid testGetChild() throws Exception{ …… //注意:stat是zk01的節點信息 Stat stat = new Stat(); List<String> paths = zk.getChildren("/zk01", null,stat); for (String path : paths) { System.out.println(path); } System.out.println(stat); } |
4.3.6 手動刪除節點
@Test publicvoid testDelete() throws Exception{ …… /**path:刪除的節點路徑 * version:節點的版本,-1支持各種版本號 * 刪除節點使用的場景不是太多,可以創建臨時節點,連接斷了之後, * 節點會自動被刪除。 * 注意:只用空節點才能刪除。 */ zk.delete("/zk01", -1); } |
4.3.7 觀察節點數據變化
@Test public void testGetDataWatcher() throws Exception{ …… zk.getData("/zk01", new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("數據放生變化"); } }, null); while(true); } |
運行程序後,在終端上執行以下命令:
set /zk01 newdata
控制檯輸出“數據發生變化”
目前只能監聽一次,如何實現永久監聽:
@Test publicvoid testGetDataWatcher() throws Exception{ ……
while(true){ final CountDownLatch cdl2 = new CountDownLatch(1); zk.getData("/zk01", new Watcher() { @Override publicvoid process(WatchedEvent event) { System.out.println("數據放生變化"); cdl2.countDown(); } }, null); cdl2.await(); } } |
4.3.8 監聽子節點變化(創建或刪除)
@Test publicvoid testGetChildWatcher() throws Exception{ ……
zk.getChildren("/zk01",new Watcher(){ @Override publicvoid process(WatchedEvent event) { if(event.getType()==EventType.NodeChildrenChanged){ System.out.println("子節點發生了變化"); } } }); while(true); } |
運行程序,終端上執行以下命令(在/zk01下創建子節點)
create /zk01/zk02 jxf
控制檯輸出“子節點變化”
4.3.9 監聽節點刪除
@Test publicvoid testDeleteWatcher() throws Exception{ …… zk.exists("/zk030000000004",new Watcher(){ @Override publicvoid process(WatchedEvent event) { if(event.getType()==EventType.NodeDeleted){ System.out.println("節點被刪除。。。"); } } }); while(true); } |
運行程序,在終端上執行刪除:delete /zk02
控制檯輸出:節點被刪除
4.3.10 監聽創建節點的相關信息
@Test publicvoid testCreateCallBack() throws Exception{ …… /**path:節點路徑 * data:節點內容 * acl:節點的相關權限 * createMode:節點的類型 * ctx:傳入的附件 */ zk.create("/zk04/node1", "Kill sleeper".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, new StringCallback(){ @Override publicvoid processResult(intrc, String path, Object ctx, String name) { //int rc:狀態碼 0代表創建成功,-110 代表創建失敗 //String path:用戶指定的路徑 //Object ctx:傳入的附件 //String name:實際創建節點的路徑名 System.out.println("rc:"+rc+",path:"+path+",ctx:"+ctx+",name:"+name); }
},"over" ); while(true); } |
集羣搭建和配置
1. 克隆三臺空虛擬機(含有lrzsz),修改網絡ip,並關閉虛擬機的防火牆
臨時關閉:service iptables stop
永久關閉:chkconfig iptables off
2. 安裝和配置jdk
3. 安裝和配置zookeeper
mkdir /use/soft
tar –zxvf /use/soft/zookeeper-3.4.7.tar.gz
[root@localhostconf]# cp zoo_sample.cfg zoo.cfg
4. 配置zoo.cfg
配置說明:
tickTime=2000 心跳間隔週期 毫秒。
initLimit=10初始連接超時閾值=10*tickTime。指的是follower初始連接leader的超時時間。 如果網絡環境不好,適當調大。
syncLimit=5連接超時閾值=syncLimit*tickTime。指的是follower和leader做數據交互的超時時間。如果網絡環境不好,適當調大。
dataDir=/usr/soft/zookeeper-3.4.7/tmp dataDir數據目錄指的是zookeeper znode樹的持久化目錄,
server.1=192.168.80.51:2888:3888
server.2=192.168.80.52:2888:3888
server.3=192.168.80.53:2888:3888
server後的數字是選舉id,在選舉過程中會用到。注意:數字一定要能比較出大小。
2888 端口原子廣播端口,可以自定義
3888 端口選舉端口,可以自定義
在zk安裝目錄下創建tmp文件,創建myid文件(名字固定),並編輯當前虛擬機的選舉id
Mkdir /usr/soft/zookeeper-3.4.7/tmp
tmp#vim myid (內容爲1)
遠程拷貝zk的安裝目錄到zk2、zk3上
[root@localhostsoftware]# scp -r zookeeper-3.4.7/ [email protected]:/home/software
[root@localhostsoftware]# scp -r zookeeper-3.4.7/ [email protected]:/home/software
分別修改myid,zk2->2,zk3->3
如果拷貝不過去或者拷貝太慢,可以先打包並壓縮:
[root@localhostsoftware]# tar -zcvf zk.tar.gz zookeeper-3.4.7/
然後在scp
然後在解壓:[root@localhost home]# tar -zxvf zk.tar.gz
5.啓動zk集羣測試
分別啓動zk
[root@localhost bin]# ls README.txt zkCli.cmd zkEnv.cmd zkServer.cmd zkCleanup.sh zkCli.sh zkEnv.sh zkServer.sh [root@localhost bin]# ./zkServer.sh start ZooKeeper JMX enabled by default Using config: /usr/soft/zookeeper-3.4.7/bin/../conf/zoo.cfg Starting zookeeper ... STARTED [root@localhost bin]# jps 3490 Jps 3464 QuorumPeerMain |
[root@localhostbin]# ./zkServer.sh status
ZooKeeper JMX enabled by default Using config: /usr/soft/zookeeper-3.4.7/bin/../conf/zoo.cfg Mode: follower |
如果啓動順序爲zk1->zk2->zk3,通常zk2爲leader。
Leader是如何選舉出來的?
6 選舉機制
6.1 Zookeeper事務概念
1.每一個寫操作都是一個事務,每一個事務都用一個事務id來代表,叫:zxid
2.zxid 是全局唯一,並且全局遞增的。作用就是可以根據最大事務id,找到最新的事務
6.2 Zookeeper選舉機制
6.2.1 選舉分兩個階段
1. 數據恢復階段:
當zk服務器啓動時,會先從本地磁盤找到本機的最大事務id。
2. 選舉階段:
zk服務器會提交選舉協議 1.Zxid(最大事務id) 2.本機的選舉id(myid文件裏的數字) 3.邏輯時鐘值 記錄當前的選舉輪數,確保每個zk在同一輪選舉中 4.當前zk服務器狀態,分4種: Looking=>選舉階段 Following=>當小弟階段 Leading=>當領導階段 Observering=>觀察者階段
6.2.2 選舉的pk原則
1.首先比較最大事務id,Zxid,誰大誰當領導
2.如果Zxid比較不出來,比較myid(選舉id),誰大誰當領導
3.選舉的前提滿足過半同意
6.2.3 Leader選舉出來之後
Leader身上肯定是有最新數據的(有最大事務id的),所以首先做的就是原子廣播(通過原子 廣播端口)。 原子廣播的目的就是爲了確保數據一致性(即客戶端無論通過哪個zk服務器查看數據,數據都是一樣的)
目的二就是爲了防止leader掛掉之後,數據的丟失問題
對於事務更新,Leader會通過原子廣播徵詢其他follower,只要滿足過半同意機制,事務才能被更新
針對下圖,Leader掛掉之後,第二個機器成爲Leader
針對下圖,不滿足過半機制,集羣就工作不了了
修改testCreateCallBack()方法
ZooKeeper zk=new ZooKeeper("192.168.80.51:2181, 192.168.80.52:2181,192.168.80.53:2181",3000,new Watcher(){… |
測試發現,三臺zk上都創建了該節點。
Zxid 最大事務id 是全局的, cZxid、mZxid、pZxid是針對某個節點路徑而言的。
擴展Zookeeper的選舉機制根據Paxos 算法來實現。 Paxos算法解決的問題:在分佈式環境下就某一個決議達成一致性問題。 Paxos算法存在活鎖問題,Zk用的是FastPaxos算法,解決了活鎖問題。
- czxid. 節點創建時的zxid.
- mzxid. 節點最新一次更新發生時的zxid.
- cversion. 其子節點的更新次數.
- aclVersion. 節點ACL(授權信息)的更新次數.
- ephemeralOwner. 如果該節點爲ephemeral節點, ephemeralOwner值表示與該節點綁定的session id. 如果該節點不是ephemeral節點, ephemeralOwner值爲0. 至於什麼是ephemeral節點, 請看後面的講述.
- dataLength. 節點數據的字節數.
- numChildren. 子節點個數.
6.2.4 zxid
znode節點的狀態信息中包含czxid和mzxid, 那麼什麼是zxid呢?
ZooKeeper狀態的每一次改變, 都對應着一個遞增的Transaction id
, 該id稱爲zxid. 由於zxid的遞增性質, 如果zxid1小於zxid2, 那麼zxid1肯定先於zxid2發生. 創建任意節點, 或者更新任意節點的數據, 或者刪除任意節點, 都會導致Zookeeper狀態發生改變, 從而導致zxid的值增加
6.2.5 session
在client和server通信之前, 首先需要建立連接, 該連接稱爲session.連接建立後, 如果發生連接超時, 授權失敗, 或者顯式關閉連接, 連接便處於CLOSED狀態, 此時session結束.
6.2.6 節點類型
講述節點狀態的ephemeralOwner字段時, 提到過有的節點是ephemeral節點, 而有的並不是. 那麼節點都具有哪些類型呢? 每種類型的節點又具有哪些特點呢?persistent
.persistent節點不和特定的session綁定, 不會隨着創建該節點的session的結束而消失, 而是一直存在, 除非該節點被顯式刪除.ephemeral
.ephemeral節點是臨時性的, 如果創建該節點的session結束了, 該節點就會被自動刪除. ephemeral節點不能擁有子節點. 雖然ephemeral節點與創建它的session綁定, 但只要該該節點沒有被刪除, 其他session就可以讀寫該節點中關聯的數據. 使用-e參數指定創建ephemeral節點.