Zookeeper是一個分佈式服務協調組件,是Hadoop、Hbase、Kafka重要的依賴組件,它是一個爲分佈式應用提供一致性服務的組件。java
Zookeeper的目標就是封裝好複雜易出錯的服務,爲使用者提供高效、穩定的服務。node
Zookeeper的使用場景:算法
1.Hadoop、Hbase、Kafka的依賴組件。數據庫
2.做爲註冊中心,用於維護服務列表。apache
3.做爲項目的配置中心,將一些重要的能夠動態配置的信息放入zk中,利用zk的通知機制,當狀態發生改變時通知客戶端,將其變量放入靜態變量中。緩存
4.做爲本地緩存的更新策略,當信息更新時更新節點的值,利用zk的通知機制,各個應用獲取到節點的值,再從數據庫中查詢,而後更新到本地緩存。服務器
5.中間件的高可用性。 session
Zookeeper維護了一個相似文件系統的數據結構,有根目錄 (/) 和若干個子目錄 (樹形結構 , 與Linux相似 )數據結構
*每一個目錄都稱爲一個znode,每一個znode都包含自身節點的數據且每一個znode下能夠包含多個znode。框架
*在建立znode時必須指定znode的數據,能夠爲null。
*znode下的數據有版本號,當進行更新操做時版本號會+1。
*當使用JAVA進行更新和刪除操做時,須要傳遞版本號,其內部進行CAS判斷,當且僅當傳遞的版本號與當前節點的版本號相同時,才進行操做(並不是每一個znode下包含多個版本的數據,傳遞版本號只是用來作CAS,默認值爲-1,表示不進行CAS判斷)
*刪除znode時,若該znode包含子znode,那麼必須先刪除全部的子znode,不然沒法刪除。
持久化節點:不管客戶端的鏈接是否斷開,該節點依然存在。
持久化節點並順序編號:在持久化節點的基礎上,Zookeeper動態的爲znode進行自動編號,即建立/p節點,那麼Zookeeper將把其節點命名爲/p1,當再次建立/p節點,那麼Zookeeper將把該節點命名爲/p2。
臨時節點:當客戶端的鏈接斷開,臨時節點及其節點中的數據將會被刪除。
臨時節點並順序編號:在臨時節點的基礎上,Zookeeper動態的爲znode進行自動編號,與持久化節點並順序編號的區別是,臨時節點並順序編號會在客戶端斷開鏈接以後會自動刪除節點以及節點中的數據。
客戶端能夠監聽它關心的節點,當目標節點發生變化時 (數據版本號改變、被刪除、子目錄節點增長和刪除),Zookeeper會通知客戶端,客戶端再做出相應的處理。
*zk並非根據節點的值改變而觸發通知的,而是根據節點中數據的版本號。
*當節點中的值重複時,但因爲數據的版本號發生改變,所以仍然能夠經過通知機制通知客戶端。
Zookeeper通常是經過集羣的方式進行使用,即多臺Zookeeper服務構成一個有關係的組。
當搭建了一個Zookeeper集羣,Zookeeper會根據選舉算法,從多個Zookeeper節點中選取一個做爲Leader,剩餘的節點做爲Follower,Leader會與各個Follower創建一個有效的長鏈接,保證各個節點的通訊正常 (每臺服務器都有可能被選取爲Leader)
當Zookeeper集羣搭建完成後,就能夠啓動不少個客戶端與zk節點進行鏈接 (長鏈接方式,保證客戶端與服務器能有效持久的鏈接)
當某個節點收到修改的操做時,首先會把請求轉發給Leader,Leader內有處理機制,它會操做修改而且同步操做給全部的Follower節點。
*一旦選取的Leader節點宕機,則會從新組織Zookeeper集羣,選取新的Leader,從新與各個節點創建鏈接 (從新選取的時間很短,大概200ms)
*因爲Zookeeper是由java語言編寫的,所以在安裝Zookeeper前須要安裝好JDK,而且配置環境變量JAVA_HOME
從Zookeeper官網下載zk進行解壓安裝:
zkEnv.sh:用於配置zk服務啓動時的環境變量 (包括加載配置文件的路徑等)
zkServer.sh:用於啓動zk服務,默認監聽2181端口。
zkCli.sh:用於啓動zk客戶端。
zookeeper.out:用於存放zk運行時的日誌。
log4j.properties文件:zk運行時的日誌配置文件,默認日誌信息都將打印到bin目錄下的zookeeper.out文件 (當使用Zookeeper遇到異常時應該查看此文件下的內容)
zoo_sample.conf文件:zk服務的配置文件,由Zookeeper官方提供 (默認zk服務啓動時將加載conf目錄下的zoo.cfg配置文件)
Zookeeper啓動時默認加載conf目錄下的zoo.cfg配置文件,所以將conf目錄下的zoo_sample.conf配置文件改名爲zoo.cfg。
配置文件
#基礎配置
tickTime=2000 initLimit=10 syncLimit=5 dataDir=/usr/Zookeeper/Zookeeper-3.4.6/zkdata dataLogDir=/usr/Zookeeper/Zookeeper-3.4.6/zklog clientPort=2181 autopurge.purgeInterval=1
tickTime:initLimit、syncLimit屬性的時間單位,值是毫秒。
initLimit:Zookeeper集羣搭建前所容許的初始化時間。
syncLimit:Leader發送心跳給Follower,Follower向Leader回覆心跳這一過程所容許的最大時長 (rtt,往返時間),一旦超過了這個時間,則Leader認爲該Follower宕機。
dataDir:Zookeeper快照日誌的存放目錄。
dataLogDir:Zookeeper事務日誌的存放目錄。
clientPort:Zookeeper服務監聽的端口,默認爲2181。
*當其中一臺zk服務啓動後,剩餘的zk服務必須在initLimit規定的時間內全都啓動,不然zk進行集羣的搭建時會認爲未啓動的zk服務已經失效。
*若是不配置dataLogDir,那麼Zookeeper的事務日誌將寫入到dataDir目錄下 (會嚴重影響zk的性能)
使用zkServer.sh命令啓動Zookeeper服務。
使用jps命令查詢zk進程是否啓動成功,當出現QuorumPeerMain表示zk啓動成功。
#基礎配置
tickTime=2000 initLimit=10 syncLimit=5 dataDir=/usr/Zookeeper/Zookeeper-3.4.6/zkdata dataLogDir=/usr/Zookeeper/Zookeeper-3.4.6/zklog clientPort=2181 autopurge.purgeInterval=1
#集羣配置 server.1=192.168.1.119:2888:3888 server.2=192.168.1.122:2888:3888 server.3=192.168.1.125:2888:3888
在conf文件下使用server.標識屬性配置zk集羣,使多個zk服務構成一個組 (標識必須爲整數)
server.本機zk標識 = zk服務地址:leader和follower之間的通訊端口:leader選舉端口
server.其餘zk標識 = zk服務地址:leader和follower之間的通訊端口:leader選舉端口
*標識與zk服務進行綁定,所以同一個集羣下的zk服務的標識不能相同。
*leader和follower之間的通訊端口默認是2888,leader選舉端口默認是3888。
#將1輸入到myid文件中
echo "1" > myid
*須要修改要構成集羣的其餘zk節點的配置文件以及設置其myid文件。
*在 initLimit * tickTime的時間內啓動集羣中的全部zk節點。
*搭建Zookeeper集羣時務必遵循2n+1個節點,由於根據Zookeeper的工做原理,只要有大於一半的節點存活,則Zookeeper集羣就可以對外提供服務。
*搭建zk集羣時需關閉每臺zk服務器上的防火牆或者開放對應的端口,不然集羣中的zk間沒法進行通信。
*zk集羣在高負荷的工做時會產生大量的事務日誌,若是日誌長期不進行清理容易將分區中的空間佔滿最終致使zk服務沒法運行,所以須要按期清理zk產生的事務日誌 (能夠配合Linux的crontab命令設置天天定時去執行清除日誌文件的腳本)
能夠經過Apache提供的Zookeeper API對zk進行操做 (提供基本功能),也可使用Apache提供的Curator框架操做zk (提供更全面的功能)
*Curator框架除了基本的節點添加、刪除、修改、查詢,監聽節點功能外,還提供了session超時重連、主從選舉、分佈式計算器、分佈式鎖等等適用於各類複雜的zk場景的API封裝。
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.13</version> <type>pom</type> </dependency>
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher) throws IOException
connectString:zookeeper server列表,多個以逗號隔開。
sessionTimeout:指定鏈接Zookeeper的超時時間。
watcher:事件回調接口。
*ZooKeeper實例將從服務列表中選擇一個server創建鏈接,若鏈接失敗則選擇另一個server從新進行鏈接。
*ZooKeeper實例是經過異步的方式創建鏈接,當鏈接創建完畢後回調指定Watcher的process方法,所以程序爲了保證同步創建鏈接,可使用JAVA提供的CountDownLatch同步輔助類進行控制。
//建立節點,指定節點路徑、數據、節點的類型 public String create(final String path, byte data[], List<ACL> acl,CreateMode createMode) //獲取子節點 public List<String> getChildren(final String path, boolean watch) //判斷節點是否存在 public Stat exists(String path, boolean watch) //獲取節點中的數據 public byte[] getData(String path, boolean watch, Stat stat) //設置或更新節點中的數據 public Stat setData(final String path, byte data[], int version) //刪除節點 public void delete(final String path, int version)
*建立節點時須要指定節點的類型,Apache Zookeeper API中提供了CreateMode枚舉類,用於指定節點的類型。
CreateMode.PERSISTENT:持久化節點 CreateMode.PERSISTENT_SEQUENTIAL:持久化節點並順序編號 CreateMode.EPHEMERAL:臨時節點 CreateMode.EPHEMERAL_SEQUENTIAL:臨時節點並順序編號
*當進行更新和刪除操做時,須要傳遞版本號,其內部進行CAS判斷,當且僅當傳遞的版本號與當前節點的版本號相同時,才進行操做(並不是每一個znode下包含多個版本的數據,傳遞版本號只是用來作CAS,默認值-1,表示不進行CAS判斷)
*Apache Zookeeper API中有不少方法都支持Watcher類型參數,Watcher可用於監聽事件的發生以及鏈接狀態的改變,包括節點的建立、刪除、節點中數據的改變、節點的子節點發生改變等事件,失去鏈接、異步鏈接、認證失敗、只讀鏈接、鏈接過時等鏈接狀態。
/** * @Auther: ZHUANGHAOTANG * @Date: 2018/11/12 14:55 * @Description: */ public class ZKUtils { /** * 日誌輸出 */ private static Logger logger = LoggerFactory.getLogger(ZKUtils.class); /** * ZK服務列表 */ private static final String URLS = "192.168.1.80:2181,192.168.1.81:2181,192.168.1.83:2181"; /** * 鏈接Zookeeper的超時時長(單位:毫秒) */ private static final int SESSION_TIMEOUT = 3000; /** * Zookeeper鏈接對象 */ private static ZooKeeper zk = null; static { try { CountDownLatch countDownLatch = new CountDownLatch(1);//鎖存器(同步輔助類) zk = new ZooKeeper(URLS, SESSION_TIMEOUT, new Watcher() { @Override public void process(WatchedEvent event) { if (Event.KeeperState.SyncConnected == event.getState()) { countDownLatch.countDown();//倒數器-1 } } }); countDownLatch.await(); } catch (Exception e) { logger.info("Zookeeper獲取鏈接失敗,{}", e); } } /** * 建立節點 * * @param path * @param data * @param createMode * @throws Exception */ public static void createPath(String path, String data, CreateMode createMode) throws Exception { if (StringUtils.isBlank(path)) { throw new Exception("path is null"); } if (!exists(path)) { zk.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, createMode); } } /** * 獲取子節點 * * @param path * @return * @throws Exception */ public static List<String> getSubNode(String path) throws Exception { if (StringUtils.isBlank(path)) { throw new Exception("path is null"); } return zk.getChildren(path, false); } /** * 判斷節點是否存在 * * @param path * @return * @throws Exception */ public static boolean exists(String path) throws Exception { if (StringUtils.isBlank(path)) { throw new Exception("path is null"); } if (zk.exists(path, false) != null) { return true; } return false; } /** * 獲取節點中的數據 * * @param path * @return * @throws Exception */ public static String getData(String path) throws Exception { if (StringUtils.isBlank(path)) { throw new Exception("path is null"); } return new String(zk.getData(path, false, null)); } /** * 更新節點中的數據 * * @param path * @param data * @throws Exception */ public static void setData(String path, String data) throws Exception { if (StringUtils.isBlank(path)) { throw new Exception("path is null"); } zk.setData(path, data.getBytes(), -1); } /** * 刪除節點 * * @param path * @throws Exception */ public static void deletePath(String path) throws Exception { if (StringUtils.isBlank(path)) { throw new Exception("path is null"); } zk.delete(path, -1); } }