Apache ZooKeeper是Apache軟件基金會的一個軟件項目,他爲大型分佈式計算提供開源的分佈式配置服務、同步服務和命名註冊。ZooKeeper曾經是Hadoop的一個子項目,但如今是一個獨立的頂級項目。java
ZooKeeper的架構經過冗餘服務實現高可用性。所以,若是第一次無應答,客戶端就能夠詢問另外一臺ZooKeeper主機。ZooKeeper節點將它們的數據存儲於一個分層的命名空間,很是相似於一個文件系統或一個前綴樹結構。客戶端能夠在節點讀寫,從而以這種方式擁有一個共享的配置服務。node
使用ZooKeeper的公司包括Rackspace、雅虎和eBay,以及相似於像Solr這樣的開源企業級搜索系統。算法
文件系統:zookeeper維護一個相似文件系統的數據結構,每一個子目錄項如 NameService 都被稱做爲 znode,和文件系統同樣,自由增長及刪除,惟一不一樣其可存儲數據。Znode分爲四種類型apache
Zookeeper角色分爲三類,領導者:負責進行投票的發起和決議,更新系統狀態。跟隨者:Follower用於接收客戶請求並向客戶端返回結果,在選中過程當中參與投票。觀察者:Observer能夠接收客戶端鏈接,將寫請求轉發給leader節點。但不參加投票過程,只同步leader狀態。Observer目的在於擴展系統,提升讀取速度。服務器
當leader崩潰或者leader失去大多數的follower,這時候zk進入恢復模式,恢復模式須要從新選舉出一個新的leader,讓全部的Server都恢復到一個正確的狀態。Zk的選舉算法有兩種:一種是基於basic paxos實現的,另一種是基於fast paxos算法實現的。系統默認的選舉算法爲fast paxos。先介紹basic paxos流程:網絡
選舉線程由當前Server發起選舉的線程擔任,其主要功能是對投票結果進行統計,並選出推薦的Server;
選舉線程首先向全部Server發起一次詢問(包括本身);
選舉線程收到回覆後,驗證是不是本身發起的詢問(驗證zxid是否一致),而後獲取對方的id(myid),並存儲到當前詢問對象列表中,最後獲取對方提議的leader相關信息(id,zxid),並將這些信息存儲到當次選舉的投票記錄表中;
收到全部Server回覆之後,就計算出zxid最大的那個Server,並將這個Server相關信息設置成下一次要投票的Server;
線程將當前zxid最大的Server設置爲當前Server要推薦的Leader,若是此時獲勝的Server得到n/2 + 1的Server票數, 設置當前推薦的leader爲獲勝的Server,將根據獲勝的Server相關信息設置本身的狀態,不然,繼續這個過程,直到leader被選舉出來。
經過流程分析咱們能夠得出:要使Leader得到多數Server的支持,則Server總數必須是奇數2n+1,且存活的Server的數目不得少於n+1.數據結構
每一個Server啓動後都會重複以上流程。在恢復模式下,若是是剛從崩潰狀態恢復的或者剛啓動的server還會從磁盤快照中恢復數據和會話信息,zk會記錄事務日誌並按期進行快照,方便在恢復時進行狀態恢復。架構
fast paxos流程是在選舉過程當中,某Server首先向全部Server提議本身要成爲leader,當其它Server收到提議之後,解決epoch和zxid的衝突,並接受對方的提議,而後向對方發送接受提議完成的消息,重複這個流程,最後必定能選舉出Leader。eclipse
wget http://mirrors.cnnic.cn/apache/zookeeper/zookeeper-3.4.8/zookeeper-3.4.8.tar.gz tar zxvf zookeeper-3.4.8.tar.gz -C /usr/local/ cd $ZOOKEEPER_HOME cp conf/zoo_sample.cfg conf/zoo.cfg # 集羣須要在zoo.cfg配置 server.1=192.168.1.148:2888:3888 server.2=192.168.1.149:2888:3888 server.3=192.168.1.150:2888:3888 # 在zookeeper的臨時目錄建立myid mkdir -p /tmp/zookeeper # 分別在不一樣節點建立myid文件裏面的數字對應節點的編號好比server.1就對應1,server.2就對應2 echo 1 > /tmp/zookeeper/myid # 最後分別啓動集羣上的節點 $ZOOKEEPER_HOME/bin/zkServer.sh start # 查看zookeeper的狀態 $ZOOKEEPER_HOME/bin/zkServer.sh status # 中止zookeeper服務 $ZOOKEEPER_HOME/bin/zkServer.sh stop
啓動zookeeper服務後到bin目錄啓動zookeeper的客戶端$ZOOKEEPER_HOME/bin/zkCli.sh分佈式
# 輸入help [zk: localhost:2181(CONNECTED) 0] help ZooKeeper -server host:port cmd args stat path [watch] set path data [version] ls path [watch] delquota [-n|-b] path ls2 path [watch] setAcl path acl setquota -n|-b val path history redo cmdno printwatches on|off delete path [version] sync path listquota path rmr path get path [watch] create [-s] [-e] path data acl addauth scheme auth quit getAcl path close connect host:port
建立節點
[zk: localhost:2181(CONNECTED) 14] create /test test-data Created /test
查看節點
[zk: localhost:2181(CONNECTED) 1] ls / [abc, zookeeper, eclipse]
獲取節點
[zk: localhost:2181(CONNECTED) 10] get /test test-update cZxid = 0x38 ctime = Sat Dec 22 10:11:46 CST 2018 mZxid = 0x39 mtime = Sat Dec 22 10:12:05 CST 2018 pZxid = 0x38 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 11 numChildren = 0
修改節點
[zk: localhost:2181(CONNECTED) 11] set /test test-update cZxid = 0x38 ctime = Sat Dec 22 10:11:46 CST 2018 mZxid = 0x3a mtime = Sat Dec 22 10:15:04 CST 2018 pZxid = 0x38 cversion = 0 dataVersion = 2 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 11 numChildren = 0
刪除節點
[zk: localhost:2181(CONNECTED) 13] delete /test
/** * @author leone * @since 2018-06-16 **/ public class ZkClient { private final static Logger logger = LoggerFactory.getLogger(ZkClient.class); private final static String ZK_URL = "xxx.xxx.xxx.xxx:2181"; private final static int TIME_OUT = 5000; private static ZooKeeper zkClient = null; @Before public void init() throws Exception { zkClient = new ZooKeeper(ZK_URL, TIME_OUT, (WatchedEvent event) -> { // 收到事件通知後的回調函數(應該是咱們本身的事件處理邏輯) logger.info(event.getType() + "---" + event.getPath()); try { zkClient.getChildren("/", true); } catch (Exception e) { e.printStackTrace(); } }); } /** * 設置值 * * @throws Exception */ @Test public void testSetData() throws Exception { zkClient.setData("/eclipse", "world".getBytes(), -1); byte[] data = zkClient.getData("/eclipse", false, null); System.out.println(new String(data)); } /** * 建立節點 * * @throws Exception */ @Test public void testCreate() throws Exception { // 參數1:要建立的節點的路徑 參數2:節點數據 參數3:節點的權限 參數4:節點的類型 zkClient.create("/eclipse/aaa", "aaaData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } /** * 測試某節點是否存在 * * @throws Exception */ @Test public void testExists() throws Exception { Stat stat = zkClient.exists("/eclipse", false); System.out.println(stat == null ? "not exist" : "exist"); } /** * 獲取子節點 * * @throws Exception */ @Test public void testGetChild() throws Exception { List<String> children = zkClient.getChildren("/", true); for (String child : children) { System.out.println(child); } } /** * 刪除節點 * * @throws Exception */ @Test public void testDelete() throws Exception { // 參數2:指定要刪除的版本,-1表示刪除全部版本 zkClient.delete("/abc", -1); } /** * 獲取節點的數據 * * @throws Exception */ @Test public void testGetDate() throws Exception { byte[] data = zkClient.getData("/eclipse", false, null); System.out.println(new String(data)); } }