...
一、相關概念
- 中間件:為分布式系統提供協調服務的組件,如專門用于計算服務的機器就是一個計算型中間件,還有專門用于存儲的機器就是存儲中間件等等;
- 分布式系統:把一套系統按照業務進行橫向拆分,不同業務階段的處理放在不同的計算機上,由這些計算機組成了一套業務處理鏈,那么這個業務處理鏈就是一個分布式系統;
- 內部的每臺計算機都可以進行通信(rpc/rest);
- 可以這樣理解:加入我們的系統目前就在一個項目中,橫向拆分就是把MVC層拆出來作為一個模塊節點A,把Service層拆分出來當做節點B,把DAO層拆分出來當做節點C,那么節點A可以部署在服務器a上,節點B部署在服務器b上,節點C部署在節點c上;運行的時候客戶端的請求發送到服務器a上,a處理的時候使用rpc/rest調用b,b處理的時候調用c,這就是所謂的分布式系統;而且這個系統的特點很明顯,如果b的處理壓力過大,那么b節點可以單獨的擴展為集群,這樣這個系統就成為了一個分布式集群系統,大大增加了系統的可擴展性,以及系統運行時候的性能的保障;
- ZooKeeper:一個基于觀察者模式設計的,為用戶的分布式應用程序提供分布式的協調服務的中間件框架;簡稱ZK;主要用來解決分布式集群中應用系統的一致性問題;支持Java,并且提供java和c的相關API;
- ZK的作用
- Leader節點選舉:就是一個主備切換的過程,當主節點掛了以后,從節點就可以接手主節點的工作,保證系統的高可用。
- 統一配置管理:只需要配置部署一臺服務器,就可以把這個服務器上的配置資源更新到其他的服務器上。常用于云計算部署;
- 發布訂閱: 類似于MQ、Dubbo,發布者把數據發布到znode上,訂閱者會去獲取這個信息;
- 提供分布式鎖:在分布式環境中,各個服務器可能會搶奪同一個資源,分布式鎖就是解決這個搶奪過程的同步問題;
- 集群管理:集群中各個服務器之間保持數據的強一致性;
- ZK中的節點角色分兩種:Leader和Follower/Observer,只有集群中有半數以上節點存活,集群就能提供服務,一般情況下集群的節點數目不少于3個且為奇數個;一個Leader,多個Follower組成一個ZK集群;
二、ZooKeeper單機安裝配置
- 下載安裝
- 建議在官方歸檔頁面下載;
- 解壓下載到的安裝包,配置環境變量
ZOOKEEPER_HOME
和PATH
;
- ZooKeeper的配置
- 安裝完成后,在conf文件夾下面有一個文件:zoo_sample.cfg文件,拷貝這個文件為zoo.cfg;
- 在這個配置文件中有很多的常用的變量需要我們配置:
- tickTime:這個時間是作為 Zookeeper 服務器之間或客戶端與服務器之間維持心跳的時間間隔,也就是每個 tickTime 時間就會發送一個心跳,單位:毫秒,單機集群通用配置;
- initLimit:初始化時,Follower節點連接到Leader節點的最長能忍受的心跳間隔數,按照tickTime倍數形式表示,集群配置;
- syncLimit:Follower節點和Leader節點之間發送請求與應答最多能忍受的心跳間隔,集群配置;
- dataDir:常用數據所在目錄,必須配置,單機集群通用配置;
- dataLogDir:日志目錄,如果沒有配置,則使用dataDir,單機集群通用配置;
- clientPort:連接服務器的端口,默認2181,單機集群通用配置;
- ZooKeeper的使用
- 啟動ZooKeeper服務:
./zkServer.sh start ./zkServer.sh start-foreground
- 關閉ZooKeeper服務:
./zkServer.sh stop
- 重啟ZooKeeper服務:
./zkServer.sh restart
- 查看ZooKeeper服務狀態:
./zkServer.sh status
- 啟動ZooKeeper服務:
三、ZooKeeper的數據模型
- ZK的數據模型
- 是一個樹形模型;
- 每一個節點成為znode,每個節點都存儲自身的數據,可以有子節點;
- 每個節點都有兩種類型:臨時和永久;臨時節點在與客戶端斷開連接之后消失,子節點和自身數據都會丟失;
- 每個節點都有一個版本號,該版本號會隨著該節點的信息更新而更新(類似數據庫的樂觀鎖),這個版本信息可以通過命令來展示;
- 每個節點存儲的數據大小不宜過大(幾K);
- 每個節點都可以設置ACL權限;
- ZK的數據模型的相關操作
- 連接客戶端,必須先啟動Server,再去使用Client連接Server
./zkCli.sh
- zkCli相關命令
- 查看zkCli的所有命令
help
- 列舉當前節點的子節點
ls <node-path> [watch]
- 查看當前節點的狀態
stat <node-path>
- cZxid:節點id;
- cTime:create time;
- mZXid:修改節點后的節點id;
- mTime:modify time;
- pZxid:子節點的id???
- cversion:子節點的version;
- dataVersion:當前節點的數據版本;
- aclVersion:acl權限版本;
- ephemeralOwner:臨時Owner內存地址,如果值是
0x0
,那么這個節點就是持久性節點,否則這個節點就是臨時節點;臨時節點不是一斷開就立即刪除(心跳機制,斷開連接之后,沒有了心跳,就會去刪除臨時節點),而是有一個實效時間,在這個時間過后才會刪除; - dataLength:數據字節數;
- numChildren:子節點個數;
- 同事查看當前節點的子節點以及當前節點的狀態
ls2 <node-path> [watch] # 本質上就是ls和stat組合命令
- 查看當前節點的數據
get <node-path>
- 查看zkCli的所有命令
- 關閉客戶端連接
- Ctrl + C
- 連接客戶端,必須先啟動Server,再去使用Client連接Server
四、ZK的常用操作
- Session基本原理
- 服務端和客戶端之間的連接稱之為Session;
- 每個Session都可以設置一個超市時間;
- 心跳結束,Session過期,臨時節點就會被丟棄;
- 心跳機制:客戶端向服務端的ping包請求;
- 常用命令
- 創建節點
create [-s] [-e] <node-path> <node-data> <acl>
-
-s
:sequence,創建順序節點 -
-e
:ephemeral,創建臨時節點;臨時節點上不可以創建永久節點;
-
- 修改節點
set <node-path> <node-data> [<version>]
- 刪除節點
delete <node-path> [<version>]
- 創建節點
五、watch機制
- 機制簡介
- 我們對每個節點的操作都會有一個監聽者watch,可以理解為一個觸發器;當有人操作了節點,那么就會觸發這個節點的watch事件,來更新這個節點以及父節點的屬性值;
- watch是一次性的,觸發后事件完成后就立即銷毀;可以通過Apache實現永久性的watch;
- 不同的類型的watch觸發的事件也是不同的:
- 節點創建
- 節點刪除
- 節點數據更新
- 常見的watch事件
- 當前節點創建節點觸發事件:NodeCreated
- 如果當前節點不存在也可以設置,報錯可忽略
- 當前節點數據更新事件:NodeDataChanged
- 當前節點刪除事件:NodeDeleted
- 子節點創建、刪除事件:NodeChildrenChanged
- 修改子節點,不觸發父節點事件
- 當前節點創建節點觸發事件:NodeCreated
- watch相關的命令
- 通過查詢數據設置watch
get <node-path> [watch]
- 通過查詢節點狀態設置watch
stat <node-path> [watch]
- 通過列舉子節點設置watch
ls[2] <node-path> [watch]
- 通過查詢數據設置watch
- Watch使用場景
- 集群統一資源配置
六、權限控制列表
- ACL簡介
- 權限控制列表:ACL(Access Control List)
- 為了保障數據的安全性,針對節點設置相關的讀寫權限;這個權限可以設置不同的權限范圍和角色;
- ACL的結構單位:
[<scheme>:<id>:<permissions>]
:- scheme:采用的權限機制
- world:這個scheme下只有一個id,即anyone;組合
world:anyone:<permissions>
; - auth:認證登錄;組合:
auth:<username>:<password>:<permissions>
; - digest:加密訪問;組合
digest:<username>:BASE64(SHA1(<password>)):<persissions>
; - ip:限制ip訪問;組合
ip:<ip-addr>:<permissions>
; - super:超級管理員權限;
- 編輯zkServer.sh中,找到行nohup行的${ZOO_LOG4J_PROP}",在其后添加
"-Dzookeeper.DigestAuthenticationProvider.superDigest=<username>:<BASE64(SHA1(<password>))>"
- 重啟zkServer;
- 登陸認證:
addauth digest <username>:<BASE64(SHA1(<password>))>
,這樣就可以使用超級管理員賬號了;
- 編輯zkServer.sh中,找到行nohup行的${ZOO_LOG4J_PROP}",在其后添加
- world:這個scheme下只有一個id,即anyone;組合
- id:允許訪問的用戶id
- permissions:權限字符串:CRDWA,這5個字母可以任意組合
- Create:創建權限
- Read:讀當前節點和子節點的權限
- Delete:刪除權限
- Write:寫權限
- Admin:分配權限的權限
- scheme:采用的權限機制
- ACL使用
- addauth:添加認證授權信息:
addauth <scheme> <acl> # 添加一個用戶名為zhangsan,密碼為123456的用戶:認證過程 addauth digest zhangsan:123456
- setAcl:設置具體節點的權限信息:
setAcl <node-path> <acl> # 給一個用戶zhangsan設置權限,注意:只是第一次設置有效 setAcl /level1/level2 auth:zhangsan:123456:cdrwa
- getAcl:獲取具體節點的權限信息:
getAcl <node-path> # 默認權限: 'world,' anyone : cdrwa
- addauth:添加認證授權信息:
- ACL使用場景
- 分離開發測試環境:即不同的節點設置不同的訪問權限;
七、四字命令(The Four Letter Words)
- 簡介
- ZK可以通過四字命令與服務器進行交互;比較適合監控信息;
- 使用
- 安裝nc:
yum install nc
- nc命令使用規則:
echo [command] | nc [ip] [port]
- 安裝nc:
- 常用命令
- stat:查看ZK的狀態信息;
echo stat | nc localhost 2181 # 執行結果: Zookeeper version: 3.4.9-1757313, built on 08/23/2016 06:50 GMT Clients: /localhost:46026[0](queued=0,recved=1,sent=0) Latency min/avg/max: 0/1/8 Received: 10 Sent: 9 Connections: 1 Outstanding: 0 Zxid: 0xa Mode: standalone Node count: 4
- ruok:查看ZK的啟動狀態;
echo ruok | nc localhost 2181 # 執行結果: imok
- dump:查看ZK的啟動狀態;
echo dump | nc localhost 2181 # 執行結果: SessionTracker dump: Session Sets (0): ephemeral nodes dump: Sessions with Ephemerals (0):
- conf:查看ZK服務器的相關配置參數;
echo conf | nc localhost 2181 # 執行結果: clientPort=2181 dataDir=/usr/local/bin/zookeeper-3.4.9/data/version-2 dataLogDir=/usr/local/bin/zookeeper-3.4.9/log/version-2 tickTime=2000 maxClientCnxns=60 minSessionTimeout=4000 maxSessionTimeout=40000 serverId=0
- cons:查看連接到ZK服務器的信息;
echo cons | nc localhost 2181 # 執行結果: /0:0:0:0:0:0:0:1:49998[0](queued=0,recved=1,sent=0) /0:0:0:0:0:0:0:1:49996[1](queued=0,recved=1,sent=1,sid=0x1645e34b63f0001,lop=SESS,est=1530597540388,to=30000,lcxid=0x0,lzxid=0xb,lresp=1530597540403,llat=14,minlat=0,avglat=14,maxlat=14)
- envi:查看ZK服務器的環境變量;
echo envi | nc localhost 2181 # 執行結果: Environment: zookeeper.version=3.4.9-1757313, built on 08/23/2016 06:50 GMT host.name=localhost java.version=1.8.0_171 java.vendor=Oracle Corporation java.home=/usr/local/bin/jdk1.8.0_171/jre java.class.path=/usr/local/bin/zookeeper-3.4.9/bin/../build/classes:/usr/local/bin/zookeeper-3.4.9/bin/../build/lib/*.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/slf4j-log4j12-1.6.1.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/slf4j-api-1.6.1.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/netty-3.10.5.Final.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/log4j-1.2.16.jar:/usr/local/bin/zookeeper-3.4.9/bin/../lib/jline-0.9.94.jar:/usr/local/bin/zookeeper-3.4.9/bin/../zookeeper-3.4.9.jar:/usr/local/bin/zookeeper-3.4.9/bin/../src/java/lib/*.jar:/usr/local/bin/zookeeper-3.4.9/bin/../conf: java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib java.io.tmpdir=/tmp java.compiler=<NA> os.name=Linux os.arch=amd64 os.version=3.10.0-862.3.2.el7.x86_64 user.name=root user.home=/root user.dir=/usr/local/bin/zookeeper-3.4.9/bin
- mntr:查看ZK服務器的健康信息;
echo mntr | nc localhost 2181 # 執行結果: zk_version 3.4.9-1757313, built on 08/23/2016 06:50 GMT zk_avg_latency 1 zk_max_latency 14 zk_min_latency 0 zk_packets_received 59 zk_packets_sent 58 zk_num_alive_connections 2 zk_outstanding_requests 0 zk_server_state standalone zk_znode_count 4 zk_watch_count 0 zk_ephemerals_count 0 zk_approximate_data_size 27 zk_open_file_descriptor_count 27 zk_max_file_descriptor_count 4096
- wchs:查看ZK服務器的watch信息;
echo wchs | nc localhost 2181 # 執行結果: 0 connections watching 0 paths Total watches:0
- wchc:查看ZK服務器中session與帶有watch的節點的關系信息(3.4.10后需要開啟白名單才能執行);
echo wchc | nc localhost 2181
- wchp:查看ZK服務器中帶有watch的節點在哪個session中(3.4.10后需要開啟白名單才能執行);
echo wchp | nc localhost 2181
- 開啟白名單:在zoo.cfg文件中添加如下一行命令,重啟即可:
4lw.commands.whitelist=* 4lw.commands.whitelist=<cmd1>,<cmd2>...
- stat:查看ZK的狀態信息;
八、ZK搭建集群
-
偽分布式環境搭建(為學習)
- 下載ZooKeeper安裝包;
- 解壓安裝包;
- 復制conf文件夾下的zoo_sample.cfg為zoo.cfg;
- 配置各個節點的myid:向每個節點的ZK的數據文件夾中寫入一個文件
myid
,這個文件中寫入當前機器的id編號即可; - 編輯zoo.cfg:
- 配置
dataDir
和dataLogDir
,不要放置在tmp下,個人喜歡放在安裝目錄或者獨立的硬盤的一個目錄下,并且確保程序有訪問權限; - 在每個節點上配置所有節點的端口映射關系:①對外訪問端口:2181;②Leader和Follower之間的端口:2888;③組件之間投票通訊端口:3888;對應的配置如下:
<server-name-prefix>.<myid>=<host>:<leader-listener-port>:<vote-port>
- <leader-listener-port>:是該服務器一旦成為Leader之后需要監聽的端口,用于接收來自follower的請求;
- <vote-port>:集群中的每一個節點在最開始選舉Leader時監聽的端口,用于服務器互相之間通信選舉Leader;
- 配置
- 建議關閉防火墻,因為集群只在內部訪問,不會暴露到對外的環境中;
真分布式環境搭建(為應用)
九、ZK集群的特點
- 數據一致性:每個ZK節點保存一份相同的數據副本,client無論連接到哪個節點,數據都是一致的,即
單一視圖
; - 分布式讀寫,更新請求轉發,由leader實施;更新請求順序進行,來自同一個client的更新請求按其發送順序依次執行;
- 操作原子性:一損俱損,全榮為榮;
- 數據實時性:在一定時間范圍內,client能讀到最新數據,ZK節點越多實時性越差;
- 數據可靠性:每次對ZK的操作都記錄在服務端;
十、ZK選舉Leader
- 問題的提出:
- 在配置集群的時候我么是不配置哪個節點是Leader節點,哪些事Follower節點的,當我們在每個節點上配置了這個集群的所有節點的映射關系之后,當ZK節點一個接著一個的啟動,系統會自動的選舉出Leader,那這個Leader的節點選舉時怎樣進行的呢?
- Leader選舉機制
- Leader選舉機制的考慮因素:
- 節點id:id即權重,節點id越大選舉的權重越大;
- 數據id:數據越新,數據id越大,選舉的權重越大;
- 投票次數:得票者越大,越容易成為Leader;
- 選舉中節點的狀態:
- LOOKING:競選狀態;
- FOLLOWING:隨從狀態,同步leader狀態,參與投票;
- OBSERVING:觀察狀態,同步leader狀態,不參與投票;
- LEADING:領導者狀態;
- Leader選舉機制的考慮因素:
十一、ZK的JavaAPI的使用
- 添加ZooKeeper的pom:
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.5</version> </dependency>
-
org.apache.zookeeper.Zookeeper
是客戶端入口主類,負責建立與ZK服務器節點的會話;主要的API參考下面的測試類;
-
- ZKClientTest:
import lombok.extern.slf4j.Slf4j; import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import org.junit.Before; import org.junit.Test; import java.util.List; /** * @Author: ShrekerNil * @Date: 2018/7/5 10:28 * @Description: ZooKeeper的API的使用 */ @Slf4j public class ZKClientTest { private static final String connectString = "192.168.70.131:2181,192.168.70.132:2181,192.168.70.133:2181"; private static final int sessionTimeout = 2000; private ZooKeeper zkClient = null; @Before public void init() throws Exception { zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() { // Watcher接口是當zkClient監聽到watch事件觸發的時候就會去調用該接口process方法的一個接口 public void process(WatchedEvent event) { // 收到事件通知后的回調函數(應該是我們自己的事件處理邏輯) log.info("接收到事件,類型:{},事件發生節點:{}", event.getType(), event.getPath()); // 因為監聽器只會生效一次,所以在消費監聽器之后再次注冊監聽器 try { zkClient.getChildren("/", true); } catch (Exception e) { log.error("添加監聽器失敗", e); } } }); } // 創建數據節點到ZK中 @Test public void testCreate() throws Exception { // 參數1:要創建的節點的路徑 參數2:節點大數據 參數3:節點的權限 參數4:節點的類型 String nodePathCreated = zkClient.create("/node110", "hellozk".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //上傳的數據可以是任何類型,但都要轉成byte[] log.info(nodePathCreated); } //判斷znode是否存在 @Test public void testExist() throws Exception { Stat stat = zkClient.exists("/node110", false); log.info(stat == null ? "Not exist!" : "Exists"); } // 獲取子節點 @Test public void getChildren() throws Exception { List<String> children = zkClient.getChildren("/", true); for (String child : children) { log.info(child); } Thread.sleep(20000); } //獲取znode的數據 @Test public void getNodeData() throws Exception { byte[] data = zkClient.getData("/node110", false, null); log.info(new String(data)); } //設置znode數據 @Test public void setNodeData() throws Exception { zkClient.setData("/node110", "something in my heart is ...".getBytes(), -1); byte[] data = zkClient.getData("/node110", false, null); log.info(new String(data)); } //刪除znode @Test public void deleteZnode() throws Exception { //第二個參數:指定要刪除的版本,-1表示刪除所有版本 zkClient.delete("/node110", -1); } }
十二、ZK的使用場景(參考自這里)
- 統一命名服務(Name Service)
- 統一命名服務是ZK的內置功能,類似于JNDI,我們在調用其API的時候使用了這一功能,在這里不再贅述;
- 配置管理(Configuration Management)
- 問題:配置的管理在分布式應用環境中很常見,例如同一個應用系統需要多臺服務器配合運行,但是它們運行的應用系統的某些配置項是相同的,如果要修改這些相同的配置項,那么就必須同時修改每臺運行這個應用系統的服務器,這樣非常麻煩而且容易出錯。
- 解決:ZK就能很好的幫我們完成這項配置工作,可以把配置存儲在ZK的一個節點上,設置監聽,當我們修改了ZK中這個節點的數據的時候,監聽器就能偵測到這個事件,進而把配置應用到各自的服務器中。
- 集群管理(Group Membership)
- 問題:在做集群的時候,一般我們都使用Master、Slave模式,但是如果Master掛了呢?這個時候就會出現問題,整個服務就掛掉了,HA化為泡影;
- 解決:這個問題的解決方式有很多,很多的框架都提供了集群管理的功能,當然我們今天的主角ZK也不例外。首先我們應該注冊監聽一個節點的子節點,客戶端在服務器上注冊臨時(EPHEMERAL)的節點,當出現如上的問題的時候,就觸發ZK的事件,我們就知道哪個節點掛了,根據一定的算法選舉出新的Leader,讓集群接著向外界提供服務;
- 共享鎖(Locks)
- 共享鎖在同一個進程中很容易實現,但是在跨進程或者在不同 Server 之間就不好實現了。Zookeeper 卻很容易實現這個功能,實現方式也是需要獲得鎖的 Server 創建一個 EPHEMERAL_SEQUENTIAL 目錄節點,然后調用 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是不是就是自己創建的目錄節點,如果正是自己創建的,那么它就獲得了這個鎖,如果不是那么它就調用 exists(String path, boolean watch) 方法并監控 Zookeeper 上目錄節點列表的變化,一直到自己創建的節點是列表中最小編號的目錄節點,從而獲得鎖,釋放鎖很簡單,只要刪除前面它自己所創建的目錄節點就行了。
- 隊列管理
- ZK可以處理兩種類型的隊列:同步隊列和FIFO隊列(完整代碼參看這里);
- 同步隊列實現:當一個隊列的成員都聚齊時,這個隊列才可用,否則一直等待所有成員到達;
- 實現思路:創建一個父目錄
/synchronizing
,每個成員都監控標志(Set Watch)位目錄/synchronizing/start
是否存在,然后每個成員都加入這個隊列,加入隊列的方式就是創建/synchronizing/member_i
的臨時目錄節點,然后每個成員獲取/ synchronizing
目錄的所有目錄節點,也就是member_i
。判斷i
的值是否已經是成員的個數,如果小于成員個數等待/synchronizing/start
的出現,如果已經相等就創建/synchronizing/start
;
- 實現思路:創建一個父目錄
- FIFO隊列實現:隊列按照 FIFO 方式進行入隊和出隊操作,例如實現生產者和消費者模型;
- 實現思路:實現的思路也非常簡單,就是在特定的目錄下創建
SEQUENTIAL
類型的子目錄 /queue_i,這樣就能保證所有成員加入隊列時都是有編號的,出隊列時通過getChildren()
方法可以返回當前所有的隊列中的元素,然后消費其中最小的一個,這樣就能保證 FIFO;
- 實現思路:實現的思路也非常簡單,就是在特定的目錄下創建
十三、集群節點上下線感知系統實現
-
客戶端實現
import lombok.extern.slf4j.Slf4j; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import java.util.ArrayList; import java.util.List; /** * @Author: ShrekerNil * @Date: 2018/7/5 14:21 * @Description: DistributedClient用來描述ZK在客戶端的用法 */ @Slf4j public class DistributedClient { private static final String connectString = "192.168.70.131:2181,192.168.70.132:2181,192.168.70.133:2181"; private static final int sessionTimeout = 2000; private static final String baseNode = "/servers"; private ZooKeeper zkClient = null; private volatile List<String> servers; /** * 創建到zk的客戶端連接 */ private void connectZoooKepper() throws Exception { zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() { public void process(WatchedEvent event) { try { updateServersAndListenChildrenNodes(); } catch (Exception e) { log.error("更新服務器列表失敗", e); } } }); } /** * 更新服務器列表,并且注冊子節點監聽 */ private void updateServersAndListenChildrenNodes() throws Exception { // 獲取服務器子節點信息,并且對父節點進行監聽 List<String> nodes = zkClient.getChildren(baseNode, true); // 先創建一個局部的list來存服務器信息 List<String> temp = new ArrayList<String>(); for (String node : nodes) { // child只是子節點的節點名 byte[] data = zkClient.getData(baseNode + "/" + node, false, null); temp.add(new String(data)); } servers = temp; //打印服務器列表 log.debug("servers:{}", servers.toString()); } /** * 業務功能 */ private void handleBussiness() throws InterruptedException { log.debug("Client start working....."); log.debug("Using servers:{}", servers); Thread.sleep(Long.MAX_VALUE); } public static void main(String[] args) throws Exception { // 創建客戶端對象 DistributedClient client = new DistributedClient(); // 連接ZK client.connectZoooKepper(); // 更新servers的子節點信息(并監聽),從中獲取服務器信息列表 client.updateServersAndListenChildrenNodes(); // 業務線程啟動 client.handleBussiness(); } }
-
集群端實現:
import lombok.extern.slf4j.Slf4j; import org.apache.zookeeper.*; /** * @Author: ShrekerNil * @Date: 2018/7/5 14:19 * @Description: DistributedServer用來描述ZK在服務器端的用法 */ @Slf4j public class DistributedServer { private static final String connectString = "192.168.70.131:2181,192.168.70.132:2181,192.168.70.133:2181"; private static final int sessionTimeout = 2000; private static final String parentNode = "/servers"; private ZooKeeper zkClient = null; /** * 創建到zk的客戶端連接 */ private void connectZoooKepper() throws Exception { zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() { public void process(WatchedEvent event) { // 收到事件通知后的回調函數(應該是我們自己的事件處理邏輯) log.debug(event.getType() + "---" + event.getPath()); try { zkClient.getChildren("/", true); } catch (Exception e) { log.error("添加監聽器失敗", e); } } }); } /** * 向zk集群注冊服務器信息 */ private void registerServer(String hostname) throws Exception { String newNode = zkClient.create(parentNode + "/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); log.debug("Created new node {} in {}", newNode, hostname); } /** * 業務功能 */ private void handleBussiness(String hostname) throws InterruptedException { log.debug(hostname + " start working....."); Thread.sleep(Long.MAX_VALUE); } public static void main(String[] args) throws Exception { // 創建Server DistributedServer server = new DistributedServer(); // 連接ZK server.connectZoooKepper(); int length = args.length; if (length < 1) { log.error("請添加參數hostname"); return; } // 利用zk連接注冊服務器信息 server.registerServer(args[0]); // 啟動業務功能 server.handleBussiness(args[0]); } }
十四、寫在最后
- 在本篇文章中提到了很多知識點,但是很多知識點只是提了一下,由于篇幅有限,只能寫到這里了,如果需要再補充吧
- 補充一個原理講解比較深刻的博客,大家可以去看看;
- 文章中也引用了一些其他博主的內容,已標記出處,如果轉載請標明這些博主的出處,我的就無所謂了,謝謝