ZooKeeper學習第一期---Zookeeper簡單介紹
ZooKeeper學習第二期--ZooKeeper安裝配置
ZooKeeper學習第三期---Zookeeper命令操作
ZooKeeper學習第四期---構建ZooKeeper應用
ZooKeeper學習第五期--ZooKeeper管理分布式環境中的數據
ZooKeeper學習第六期---ZooKeeper機制架構
ZooKeeper學習第七期--ZooKeeper一致性原理
ZooKeeper學習第八期——ZooKeeper伸縮性
一、Zookeeper的四字命令
Zookeeper支持某些特定的四字命令字母與其的交互。他們大多數是查詢命令,用來獲取Zookeeper服務的當前狀態及相關信息。用戶在客戶端可以通過telnet或nc向Zookeeper提交相應的命令。Zookeeper常用的四字命令見下圖所示。
通過下列命令來獲取這些監控信息 echo commands | nc ip port
如:
echo conf | nc 192.168.144.110 2181
上圖,是Zookeeper四字命令的一個簡單用例。
[root@hadoop ~]# echo ruok|nc localhost 2181 [root@hadoop ~]# zkServer.sh start zoo1.cfg JMX enabled by default Using config: /usr/local/zk/bin/../conf/zoo1.cfg Starting zookeeper ... STARTED [root@hadoop ~]# zkServer.sh start zoo2.cfg JMX enabled by default Using config: /usr/local/zk/bin/../conf/zoo2.cfg Starting zookeeper ... STARTED [root@hadoop ~]# zkServer.sh start zoo3.cfg JMX enabled by default Using config: /usr/local/zk/bin/../conf/zoo3.cfg Starting zookeeper ... STARTED [root@hadoop ~]# echo ruok|nc localhost 2181 imok[root@hadoop ~]# echo ruok|nc localhost 2182 imok[root@hadoop ~]# echo ruok|nc localhost 2183 imok[root@hadoop ~]# echo conf|nc localhost 2181 clientPort=2181 dataDir=/usr/local/zk/data_1/version-2 dataLogDir=/usr/local/zk/logs_1/version-2 tickTime=2000 maxClientCnxns=60 minSessionTimeout=4000 maxSessionTimeout=40000 serverId=0 initLimit=10 syncLimit=5 electionAlg=3 electionPort=3387 quorumPort=2287 peerType=0 [root@hadoop ~]#
額:
yum install nc
二、Zookeeper的簡單操作
2.1 Zookeeper的shell操作
2.1.1 Zookeeper命令工具
再啟動Zookeeper服務之后,輸入以下命令,連接到Zookeeper服務:
zkCli.sh -server localhost:2181
連接成功之后,系統會輸出Zookeeper的相關環境及配置信息,並在屏幕輸出“welcome to Zookeeper!”等信息。輸入help之后,屏幕會輸出可用的Zookeeper命令,如下圖所示
2.1.2 使用Zookeeper命令的簡單操作步驟
(1) 使用ls命令查看當前Zookeeper中所包含的內容:ls /
[zk: localhost:2181(CONNECTED) 1] ls / [zookeeper] [zk: localhost:2181(CONNECTED) 2]
(2) 創建一個新的Znode節點"zk",以及和它相關字符,執行命令:create /zk myData
[zk: localhost:2181(CONNECTED) 2] create /zk myData Created /zk
(3) 再次使用ls命令來查看現在Zookeeper的中所包含的內容:ls /
[zk: localhost:2181(CONNECTED) 3] ls / [zk, zookeeper]
此時看到,zk節點已經被創建。
(4) 使用get命令來確認第二步中所創建的Znode是否包含我們創建的字符串,執行命令:get /zk
[zk: localhost:2181(CONNECTED) 4] get /zk myData cZxid = 0x500000006 ctime = Fri Oct 17 03:54:20 PDT 2014 mZxid = 0x500000006 mtime = Fri Oct 17 03:54:20 PDT 2014 pZxid = 0x500000006 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 6 numChildren = 0
(5) 接下來通過set命令來對zk所關聯的字符串進行設置,執行命令:set /zk jiang1234
[zk: localhost:2181(CONNECTED) 5] set /zk jiang2014 cZxid = 0x500000006 ctime = Fri Oct 17 03:54:20 PDT 2014 mZxid = 0x500000007 mtime = Fri Oct 17 03:55:50 PDT 2014 pZxid = 0x500000006 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 9 numChildren = 0
(6) 再次使用get命令來查看,上次修改的內容,執行命令:get /zk
[zk: localhost:2181(CONNECTED) 6] get /zk jiang2014 cZxid = 0x500000006 ctime = Fri Oct 17 03:54:20 PDT 2014 mZxid = 0x500000007 mtime = Fri Oct 17 03:55:50 PDT 2014 pZxid = 0x500000006 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 9 numChildren = 0
(7) 下面我們將剛才創建的Znode刪除,執行命令:delete /zk
[zk: localhost:2181(CONNECTED) 7] delete /zk
(8) 最后再次使用ls命令查看Zookeeper中的內容,執行命令:ls /
[zk: localhost:2181(CONNECTED) 8] ls / [zookeeper]
經過驗證,zk節點已經刪除。
2.2 Zookeeper的api的簡單使用
2.2.1 ZookeeperAPI簡介
Zookeeper API共包含五個包,分別為:
(1)org.apache.zookeeper
(2)org.apache.zookeeper.data
(3)org.apache.zookeeper.server
(4)org.apache.zookeeper.server.quorum
(5)org.apache.zookeeper.server.upgrade
其中org.apache.zookeeper,包含Zookeeper類,他是我們編程時 最常用的類文件。這個類是Zookeeper客戶端的主要類文件。如果要使用Zookeeper服務,應用程序首先必須創建一個Zookeeper實例, 這時就需要使用此類。一旦客戶端和Zookeeper服務建立起了連接,Zookeeper系統將會給次連接會話分配一個ID值,並且客戶端將會周期性的 向服務器端發送心跳來維持會話連接。只要連接有效,客戶端就可以使用Zookeeper API來做相應處理了。
Zookeeper類提供了如下圖所示的幾類主要方法
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.8</version> </dependency>
三、ZooKeeper示例
假設一組服務器,用於為客戶端提供一些服務。我們希望每個客戶端都能夠能夠找到其中一台服務器,使其能夠使用這些服務,挑戰之一就是維護這組服務器 列表。這組服務器的成員列表明顯不能存在網絡中的單個節點上,因為如果那個節點發生故障,就意味着是整個系統的故障(我們希望這個列表有很高的可用性)。 假設我們有了一個可靠的方法解決了這個成員列表的存儲問題。如果其中一台服務器出現故障,我們仍然需要解決如何從服務器成員列表中將它刪除的問題。某個進 程需要負責刪除故障服務器,但注意不能由故障服務器自己來完成,因為故障服務器已經不再運行。
我們所描述的不是一個被動的分布式數據結構,而是一個主動的、能夠在某個外部事件發生時修改數據項狀態的數據結構。ZooKeeper提供這種服務,所以讓我們看看如何使用它來實現這種眾所周知的組成員管理應用。
ZooKeeper中的組成員關系
理解ZooKeeper的一種方法就是將其看作一個具有高可用性的文件系統。但這個文件系統中沒有文件和目錄,而是統一使用“節點”(node)的概念,稱為znode。znode既可以作為保存數據的容器(如同文件),也可以作為保存其他znode的容器(如同目錄)。所有的znode構成一個層次化的命名空間。一種自然的建立組成員列表的方式就是利用這種層次結構,創建一個以組名為節點名的znode作為父節點,然后以組成員名(服務器名)為節點名來創建作為子節點的znode。如下圖給出了一組具有層次結構的znode。
在這個示例中,我們沒有在任何znode中存儲數據,但在一個真實的應用中,你可以將“關於成員的數據”存儲在它們的znode中,例如主機名。
3.1 創建組
3.1.1 代碼示例
讓我們通過編寫一段程序的方式來再次詳細介紹ZooKeeper的Java API,這段示例程序用於為組名為/zoo的組創建一個znode。代碼參見如下
代碼 該程序在ZooKeeper中新建表示組的Znode
package zk;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
public class CreateGroup implements Watcher{
private static final int SESSION_TIMEOUT=5000;
private static final String HOST1="192.168.56.90:2181";
private static final String HOST2="192.168.56.90:2182,192.168.56.90:2183,192.168.56.90:2184";
private ZooKeeper zk;
private CountDownLatch connectedSignal=new CountDownLatch(1);
public void process(WatchedEvent event) {
if(event.getState()==KeeperState.SyncConnected){
connectedSignal.countDown();
}
}
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
CreateGroup createGroup = new CreateGroup();
createGroup.connect(HOST2);
createGroup.create("c");
createGroup.close();
}
private void close() throws InterruptedException {
zk.close();
}
private void create(String groupName) throws KeeperException, InterruptedException {
String path="/"+groupName;
if(zk.exists(path, false)== null){
zk.create(path, null/*data*/, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
System.out.println("Created:"+path);
}
private void connect(String hosts) throws IOException, InterruptedException {
zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
connectedSignal.await();
}
}
運行結果
斷點時候發現
State:CONNECTED,假如連接不成功會是State:CONNECTING
3.1.2 代碼分析
在上面代碼中,main()方法執行時,創建了一個CreateGroup的實例並且調用了這個實例的connect()方法。connect方法實例化了一個新的ZooKeeper類的對象,這個類是客戶端API中的主要類,並且負責維護客戶端和ZooKeeper服務之間的連接。ZooKeeper類的構造函數有三個參數:
第一個是:ZooKeeper服務的主機地址,可指定端口,默認端口是2181。
第二個是:以毫秒為單位的會話超時參數,這里我們設成5秒。
第三個是:參數是一個Watcher對象的實例。
Watcher對象接收來自於ZooKeeper的回調,以獲得各種事件的通知。在這個例子中,CreateGroup是一個Watcher對象,因此我們將它傳遞給ZooKeeper的構造函數。
當一個ZooKeeper的實例被創建時,會啟動一個線程連接到ZooKeeper服務。由於對構造函數的調用是立即返回的,因此在使用新建的ZooKeeper對象之前一定要等待其與ZooKeeper服務之間的連接建立成功。我們使用Java的CountDownLatch類來阻止使用新建的ZooKeeper對象,直到這個ZooKeeper對象已經准備就緒。這就是Watcher類的
用途,在它的接口中只有一個方法:
public void process(WatcherEvent event);
客 戶端已經與ZooKeeper建立連接后,Watcher的process()方法會被調用,參數是一個表示該連接的事件。在接收到一個連接事件(由 Watcher.Event.KeeperState的枚舉型值SyncConnected來表示)時,我們通過調用CountDownLatch的countDown()方法來遞減它的計數器。鎖存器(latch)被創建時帶有一個值為1的計數器,用於表示在它釋放所有等待線程之前需要發生的事件數。在調用一歡countDown()方法之后,計數器的值變為0,則await()方法返回。
現在connect()方法已經返回,下一個執行的是CreateGroup的create()方法。在這個方法中,我們使用ZooKeeper實例中的create()方法來創建一個新的ZooKeeper的znode。所需的參數包括:
路徑:用字符串表示。
znode的內容:字節數組,本例中使用空值。
訪問控制列表:簡稱ACL,本例中使用了完全開放的ACL,允許任何客戶端對znode進行讀寫。
創建znode的類型:有兩種類型的znode:短暫的和持久的。
創建znode的客戶端斷開連接時,無論客戶端是明確斷開還是因為任何原因而終止,短暫znode都會被ZooKeeper服務刪除。與之相反,當 客戶端斷開連接時,持久znode不會被刪除。我們希望代表一個組的znode存活的時間應當比創建程序的生命周期要長,因此在本例中我們創建了一個持久 的znode。
create()方法的返回值是ZooKeeper所創建的路徑,我們用這個返回值來打印一條表示路徑成功創建的消息。當我們查看“順序znode”(sequential znode)時.會發現create()方法返回的路徑與傳遞給該方法的路徑不同。
!把HOST1改成HOST2
public static void main(String[] args) throws IOException, InterruptedException, KeeperException { CreateGroup createGroup = new CreateGroup(); createGroup.connect(HOST2); createGroup.create("c"); createGroup.close(); }
192.168.56.90:2182,
192.168.56.90:2183,
192.168.56.90:2184
的節點上都有 /c 節點,數據一致性
斷點發現
實例是leader
3.2 加入組
下面的這一段程序用於注冊組的成員。每個組成員將作為一個程序運行,並且加入到組中。當程序退出時,這個組成員應當從組中被刪除。為了實現這一點,我們在ZooKeeper的命名空間中使用短暫znode來代表一個組成員。
在基類ConnectionWatcher中,對創建和連接ZooKeeper實例的程序邏輯進行了重構,參見代碼如下
代碼 用於將成員加入組的程序
package zk; import java.io.IOException; import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooKeeper; public class ConnectionWatcher implements Watcher{ private static final int SESSION_TIMEOUT=5000; protected ZooKeeper zk; CountDownLatch connectedSignal=new CountDownLatch(1); public void connect(String host) throws IOException, InterruptedException{ zk=new ZooKeeper(host, SESSION_TIMEOUT, this); connectedSignal.await(); } public void process(WatchedEvent event) { if(event.getState()==KeeperState.SyncConnected){ connectedSignal.countDown(); } } public void close() throws InterruptedException{ zk.close(); } }
package zk; import java.io.IOException; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs.Ids; public class JoinGroup extends ConnectionWatcher{ private static final String HOST1="192.168.56.90:2181"; private static final String HOST2="192.168.56.90:2182,192.168.56.90:2183,192.168.56.90:2184"; public void join(String groupName,String memberName) throws KeeperException, InterruptedException{ String path="/"+groupName+"/"+memberName; String createdPath=zk.create(path, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); System.out.println("Created:"+createdPath); } public static void main(String[] args) throws InterruptedException, IOException, KeeperException { JoinGroup joinGroup = new JoinGroup(); joinGroup.connect(HOST1); joinGroup.join("a", "aa"); Thread.sleep(Long.MAX_VALUE); } }
JoinGroup的代碼與CreateGroup非常相似,在它的join()方法中,創建短暫znode,作為組znode的子節點,然后通過 休眠來模擬正在做某種工作,直到該進程被強行終止。接着,你會看到隨着進程終止,這個短暫znode被ZooKeeper刪除。
3.3 列出組成員
現在,我們需要一段程序來查看組成員,參見代碼如下:
代碼 用於列出組成員的程序
package zk; import java.io.IOException; import java.util.Iterator; import java.util.List; import org.apache.zookeeper.KeeperException; public class ListGroup extends ConnectionWatcher { private static final String HOST1="192.168.56.90:2181"; private static final String HOST2="192.168.56.90:2182,192.168.56.90:2183,192.168.56.90:2184"; public void list(String groupNmae) throws KeeperException, InterruptedException{ String path ="/"+groupNmae; try { List children = zk.getChildren(path, false); if(children.isEmpty()){ System.out.println("Group "+groupNmae+" does not exist \n"); System.exit(1); } Iterator it=children.iterator(); while(it.hasNext()){ String child=(String)it.next(); System.err.println(child); } } catch (KeeperException.NoNodeException e) { System.out.println("Group "+groupNmae+" does not exist \n"); System.exit(1); } } public static void main(String[] args) throws IOException, InterruptedException, KeeperException { ListGroup listGroup = new ListGroup(); listGroup.connect(HOST2); listGroup.list(""); listGroup.close(); } }
在list()方法中,我們調用了getChildren()方法來檢索並打印輸出一個znode的子節點列表,調用參數為:該znode的路徑和 設為false的觀察標志。如果在一znode上設置了觀察標志,那么一旦該znode的狀態改變,關聯的觀察(Watcher)會被觸發。雖然在這里我 們可以不使用觀察,但在查看一個znode的子節點時,也可以設置觀察,讓應用程序接收到組成員加入、退出和組被刪除的有關通知。
3.4 刪除組
下面來看如何刪除一個組。ZooKeeper類提供了一個delete()方法,該方法有兩個參數:
1. 路徑
2. 版本號
如果所提供的版本號與znode的版本號一致,ZooKeeper會刪除這個znode。這是一種樂觀的加鎖機制,使客戶端能夠檢測出對znode的修改沖突。通過將版本號設置為-1,可以繞過這個版本檢測機制,不管znode的版本號是什么而直接將其刪除。ZooKeeper不支持遞歸的刪除操作,因此在刪除父節點之前必須先刪除子節點。
在代碼3.5中,DeleteGroup類用於刪除一個組及其所有成員。
代碼3.5用於刪除一個組及其所有成員的程序
package zk; import java.io.IOException; import java.util.Iterator; import java.util.List; import org.apache.zookeeper.KeeperException; public class DeleteGroup extends ConnectionWatcher{ private static final String HOST1="192.168.56.90:2181"; private static final String HOST2="192.168.56.90:2182,192.168.56.90:2183,192.168.56.90:2184"; public void delete(String groupName) throws InterruptedException, KeeperException{ String path="/"+groupName; List children; try { children = zk.getChildren(path, false); Iterator it=children.iterator(); while(it.hasNext()){ zk.delete(path+"/"+(String)it.next(), -1); } zk.delete(path, -1); } catch (KeeperException.NoNodeException e) { System.out.println("Group "+groupName+" does not exist \n"); System.exit(1); } } public static void main(String[] args) throws InterruptedException, IOException, KeeperException { DeleteGroup deleteGroup = new DeleteGroup(); deleteGroup.connect(HOST1); deleteGroup.delete("a"); deleteGroup.close(); } }
最后,我們可以刪除之前所創建的組: