這篇文章我試圖簡單易懂的做入門指導,而不是也不想過多描述定義和原理。
Zookeeper Apache 官網
Zookeeper Apache wiki
Zookeeper bird’s eye view
github-Zookeeper
一、是什麼
Apache Zookeeper 原來是Hadoop 的一個子項目,它爲大型分佈式計算提供開源的分佈式配置服務 、同步服務 和命名註冊 。現在是Apache 的一個獨立頂級項目。它是一個Java程序 。
Zookeeper的架構通過冗餘服務實現高可用性。因此,如果第一次請求無應答,客戶端就可以詢問另一臺Zookeeper主機。Zookeeper數據結構是一個分層的命名空間,就像一個文件系統那樣。客戶端可以有不同的權限對節點讀寫,從而以這種方式擁有一個共享的配置服務。
引用一下官網的介紹:
Zookeeper 是給分佈式應用使用的一個高性能的協調服務(coordination service)。它暴漏公共服務 - 比如命名、配置管理、同步and group services - in a simple interface ,所以你可以用Zookeeper 現成的功能去實現一致性、組管理、集羣領導節點選舉和presence protocols 。
典型用例
Naming Service(命名服務/目錄服務)
- 在分佈式架構中,有很多應用都對外提供了Service 和一個與之相關的Service路徑 ,且每個應用通常還可能部署了多份在不同的機器上;
- 也就是說一個Service 有多個提供者;
- 此時,調用方只要知道Service 的協議 + IP地址(域名) + 路徑即可成功調用;
- 這裏要說的是關於IP地址(域名)部分:
顯而易見的是,如果調用方使用硬編碼域名來連接Service 的話,需要將每一個提供者的地址都寫上,然後採用某種策略在調用時選擇其中一個提供者。這會帶來如下問題:
- 如果服務提供者部署的機器要增加一臺、刪除一臺、或僅修改域名,那麼每一個調用方竟然都不得不去修改其代碼才能實現切換。通常,提供者肯定會變動、調用方也肯定有很多,所以,這是災難……
- 所以,最好的方式就是有一個協調者/中介者:
- 協調者:記錄所有提供者的地址、告訴所有調用者應該使用的提供者的地址;
- 提供者:將自己的地址註冊到協調者上;
- 調用者:無須再硬編碼提供者地址,而只連接協調者,又協調者告訴它相關提供者的地址;自身只關心Service 路徑就好了;
所以你看,此時Zookeeper 就是命名服務 :它協調了提供者 和調用者 ,提供者 在Zookeeper 上註冊其名字 ,而調用者 從Zookeeper 獲取提供者 的名字 ;—> 協調服務 - Coordination Service 。
要點
- 爲什麼叫Zookeeper?
Zookeeper wiki:協調分佈式系統這個工作看起來就像動物園做的事,而動物園需要一個管理員。
- 一類動物就是一類服務器,一類動物一羣就是一類服務器的多節點。
- 多類動物就是多類服務器,動物園有多類多羣動物,它需要一個管理員。
二、Zookeeper實現算法
僅從上面介紹來說,其實單個Zookeeper 提供的那麼簡單的功能,相信你不會覺得它複雜,甚至大部分人都能輕易寫出一個這樣的程序來。所以:
使用Zookeeper 如果使用單點就沒有意義了,如果這個Zookeeper崩潰了,難道就讓所有提供者和調用者懵逼,從而使整個系統崩潰了嗎?所以一定是多個Zookeeper 實例,也就是分佈式Zookeeper 。
它的難點在於多個Zookeeper 的通訊,其實現的關鍵部分就在於如何處理分佈式情況下的數據一致性;
那Zookeeper 是怎麼做的呢?
Zookeeper 是以Fast Paxos 算法爲基礎的,我們可以瞭解下這個算法。(這裏我先簡單引用下百度百科,這個博文僅僅是要記錄如何快速使用它)
引用百度百科:
Paxos 算法解決的問題是一個分佈式系統如何就某個值(決議)達成一致。一個典型的場景是,在一個分佈式數據庫系統中,如果各節點的初始狀態一致,每個節點執行相同的操作序列,那麼他們最後能得到一個一致的狀態。爲保證每個節點執行相同的命令序列,需要在每一條指令上執行一個“一致性算法”以保證每個節點看到的指令一致。一個通用的一致性算法可以應用在許多場景中,是分佈式計算中的重要問題。因此從20世紀80年代起對於一致性算法的研究就沒有停止過。節點通信存在兩種模型:共享內存(Shared memory)和消息傳遞(Messages passing)。Paxos 算法就是一種基於消息傳遞模型的一致性算法。
三、安裝與配置
完整的安裝嚮導以及參數含義在官網上有,這裏只記錄下我的操作過程。
下載、解壓
下載地址:zookeeper-3.4.8.tar.gz# 可以直接使用wget下載,也可以在其它地方下載完拷貝過來。這裏解壓到了`/usr/local`下 wget http://apache.fayea.com/zookeeper/zookeeper-3.4.8/zookeeper-3.4.8.tar.gz tar -zxf zookeeper-3.4.8.tar.gz
修改配置文件(集羣配置)
cd zookeeper-3.4.8 # 將原有的zoo_sample.cfg拷貝一份,取名叫`zoo.cfg` cp conf/zoo_sample.cfg conf/zoo.cfg # vim修改配置文件 vim conf/zoo.cfg
這裏是集羣配置:
- 修改了
dataDir
; - 集羣:增加了最下邊的集羣配置
server.N
(如果不需要集羣不配置此項,不執行下面的步驟3.
) - 集羣:需要在集羣中每臺機器上都這樣配置。我這裏是兩臺機器
192.168.192.128
和192.168.192.129
。
// the directory where the snapshot is stored. // do not use /tmp for storage, /tmp here is just example sakes. dataDir=/usr/local/zookeeper-3.4.8/data // There are two port numbers nnnnn. // The first followers use to connect to the leader, // and the second is for leader election. server.1=192.168.192.128:2555:3555 server.2=192.168.192.129:2555:3555
- 修改了
在data目錄下(
zoo.cfg
中的dataDir
目錄)新建一個叫myid
的文件,文件內容對應zoo.cfg
中的server.x
的x
:vim myid
,操作的是第一臺機器,server.x
的x
是1,所以這裏文件內容是:1
記得將其它機器也配置好相應的這個文件。
可選:配置JVM參數
首先,查看
<Zookeeper_Home>/bin/zkServer.sh
的start
方法可以看到:定義了變量$JVMFLAGS
讓我們設置JVM參數。其次,可以在
<Zookeeper_Home>/bin/zkEnv.sh
中看到:if [ -f "$ZOOCFGDIR/java.env" ] then . "$ZOOCFGDIR/java.env" fi
就是說JVM參數最好配置在
<Zookeeper_Home>/conf/java.env
中。所以,我們改這裏。不過這個文件不存在,先創建一個:
vim java.env
,代碼如下(這裏我是需要改小一點):JVMFLAGS="-Xmx300M -Xms300M"
可選:配置zookeeper日誌輸出格式、目錄
首先,在
<Zookeeper_Home>/conf/log4j.properties
中可以看到:log4j.rootLogger=${zookeeper.root.logger} # Add ROLLINGFILE to rootLogger to get log file output log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender log4j.appender.ROLLINGFILE.Threshold=${zookeeper.log.threshold} log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/${zookeeper.log.file} log4j.appender.ROLLINGFILE.MaxFileSize=${zookeeper.log.maxfilesize} log4j.appender.ROLLINGFILE.MaxBackupIndex=${zookeeper.log.maxbackupindex} log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
還有其它配置沒有全部貼出來,這裏貼出來的是可以滾動存儲的日誌配置,可以看到zookeeper已經給我們準備好了,我們只需要設置相關變量就可以了:
${zookeeper.root.logger}
:級別 + 日誌記錄器${zookeeper.log.dir}
/${zookeeper.log.file}
:路徑和日誌文件名
其次,可以在
<Zookeeper_Home>/bin/zkServer.sh
中看到:if [ ! -w "$ZOO_LOG_DIR" ] ; then mkdir -p "$ZOO_LOG_DIR" fi ZOO_LOG_FILE=zookeeper-$USER-server-$HOSTNAME.log _ZOO_DAEMON_OUT="$ZOO_LOG_DIR/zookeeper-$USER-server-$HOSTNAME.out" case $1 in start) echo -n "Starting zookeeper ... " if [ -f "$ZOOPIDFILE" ]; then if kill -0 `cat "$ZOOPIDFILE"` > /dev/null 2>&1; then echo $command already running as process `cat "$ZOOPIDFILE"`. exit 1 fi fi nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \ "-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError='kill -9 %p' \ -cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null & …
-Dzookeeper.log.dir=${ZOO_LOG_DIR}
-Dzookeeper.log.file=${ZOO_LOG_FILE}
這個上面那個值就行,不改了-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}
所以,我們需要給上面這2個
${***}
賦值即可。還是在<Zookeeper_Home>/bin/zkEnv.sh
,修改如下:if [ "x${ZOO_LOG_DIR}" = "x" ] then ZOO_LOG_DIR="$ZOOKEEPER_PREFIX/logs" // 改爲自己需要的目錄 ZOO_LOG_DIR="/data/logs/zookeeper" fi if [ "x${ZOO_LOG4J_PROP}" = "x" ] then ZOO_LOG4J_PROP="INFO,CONSOLE" // 改爲自己需要的 ZOO_LOG4J_PROP="INFO,ROLLINGFILE" fi
分別在所有機器上啓動,命令如下
[root@localhost zookeeper-3.4.8]# bin/zkServer.sh start ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper-3.4.8/bin/../conf/zoo.cfg Starting zookeeper ... STARTED # 以上,啓動成功!
當然,我們最好還是要驗證一下有沒有真的啓動成功,使用
status
成功狀態如下:[root@localhost zookeeper-3.4.8]# ./bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper-3.4.8/bin/../conf/zoo.cfg Mode: leader [root@localhost zookeeper-3.4.8]# ./bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper-3.4.8/bin/../conf/zoo.cfg Mode: follower
如果有問題,會大概顯示如下:
[root@localhost zookeeper-3.4.8]# ./bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper-3.4.8/bin/../conf/zoo.cfg Error contacting service. It is probably not running.
此時我們可以打開上面自己配置的日誌文件,查看具體信息,並按照信息修改錯誤即可。
四、維護
手動清理
Zookeeper文檔中說到:
These are the snapshot and transactional log files.
…
A ZooKeeper server will not remove old snapshots and log files when using the default configuration (see autopurge below), this is the responsibility of the operator.就是說在我們配置的
dataDir=/usr/local/zookeeper-3.4.8/data
目錄下會生成快照文件和事務日誌文件,這些文件需要我們自己負責它的清理工作;不過我們當然不是說每次需要手動的去清理這些文件……只要在上面的文檔地址中,將官方提供的腳本代碼按需做成
crontab
定時任務即可。zkCli.sh
啓動腳本
./zkCli.sh -server localhost:2181
後,可以直接輸入help
來查看支持的操作:[zk: localhost:2181(CONNECTING) 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
zkCli.sh
其實就是啓動了org.apache.zookeeper.ZooKeeperMain$MyWatcher
,可以自己進來看具體的實現:static { commandMap.put("connect", "host:port"); commandMap.put("close",""); commandMap.put("create", "[-s] [-e] path data acl"); commandMap.put("delete","path [version]"); commandMap.put("rmr","path"); commandMap.put("set","path data [version]"); commandMap.put("get","path [watch]"); commandMap.put("ls","path [watch]"); commandMap.put("ls2","path [watch]"); commandMap.put("getAcl","path"); commandMap.put("setAcl","path acl"); commandMap.put("stat","path [watch]"); commandMap.put("sync","path"); commandMap.put("setquota","-n|-b val path"); commandMap.put("listquota","path"); commandMap.put("delquota","[-n|-b] path"); commandMap.put("history",""); commandMap.put("redo","cmdno"); commandMap.put("printwatches", "on|off"); commandMap.put("quit",""); commandMap.put("addauth", "scheme auth"); }
官方的
Getting Started
中有一些具體使用zookeeperStarted;
記錄一下介紹的不錯的博客:
ZooKeeper原理及使用 :http://blog.csdn.net/xinguan1267/article/details/38422149
zkCli.sh使用指南:http://blog.csdn.net/ganglia/article/details/11606807?utm_source=tuicool&utm_medium=referral
這個其實是使用Dubbo的前置篇,接下來可以繼續看RPC框架與Dubbo完整使用