zookeeper中展示所有节点_分布式协调服务之Zookeeper
??理論篇
一、基礎概念
ZooKeeper是開源分布式協調服務,提供高可用、高性能、穩定的分布式數據一致性解決方案,通常被用于實現諸如數據發布/訂閱、負載均衡、命名服務、分布式協調/通知、集群管理、Master選舉、分布式鎖和分布式隊列等功能。
二、ZooKeeper數據模型
image.png2.1 znode(數據節點)
Zookeeper中所有存儲的數據由znode組成,節點也成為znode,并以key value鍵值對形式存儲數據。整體結構類似linux文件系統,根路徑以/開頭。
znode中數據的讀寫都是原子的,而且每一個znode都有一個Access Control List(ACL)用來限制誰可以做什么。每一個znode的數據大小不能超過1M
2.1.1 znode數據節點名稱規范
- null 字符(即\u0000)不能組成znode節點path的命名
- \ud800 - uF8FF, \uFFF0 - uFFFF, \u0001 - \u001F 以及 \u007F、\u009F 這些玩意無法很好地進行展示,看起來像亂碼
- "."可以構成命名的一部分,但是不能單獨作為path的命名
- "zookeeper"是保留字
2.1.2 znode數據節點組成:
- stat 狀態屬性組成:
| czxid | 創建節點的事務ID |
| mzxid | 節點最后一次修改的事務ID |
| pzxid | 子節點列表最后的一次修改(子節點列表的增加或刪除)的事務ID |
| ctime | 創建節點的時間,單位:毫秒 |
| mtime | 節點最后一次修改的時間,單位:毫秒 |
| version | 節點數據變更的次數 |
| cversion | 子節點變更的次數 |
| aversion | 節點ACL變更的次數 |
| ephemeralOwner | 如果節點是臨時節點, 則此值為創建該節點的session的id,否則為0 |
| dataLength | 節點數據長度 |
| numChildren | 子節點個數 |
- data
- children
2.1.3 znode數據節點類型:
持久節點(Persistent Nodes)
臨時節點(Ephemeral Nodes) 臨時節點的生命周期即為創建這些節點的會話生命周期,即會話結束,則這些節點就會被刪除。所以臨時節點不允許創建子節點。
順序節點(Sequence Nodes ) 順序節點可以是持久的,也可以是臨時的。在創建節點時,可為節點路徑添加一個單調遞增計數器,Zookeeper將通過將10位的序列號附加到原始節點名稱后來設置節點路徑。
容器節點(Container Nodes) 此類型節點是3.6.0版本之后添加,容器節點是一種有特殊用途的節點,可用于leader選舉和分布式鎖等,當容器中最后一個子節點被刪除,此容器節點將會在未來某個時刻被刪除。
超時過期節點(TTL Nodes) 此類型節點是3.6.0版本之后添加,當創建一個持久節點或者順序持久節點時,可以為其設置一個毫秒級的超時過期時間,如果在設置時間內,此節點沒又被修改過而且也沒有子節點,則此節點將作為候選項,在未來某個時刻被刪除。當然TTL Nodes默認是禁用狀態的。
三、Zookeeper Time
Zookeeper中時間有很多表示時間的方式。
Zxid 事務ID。Zookeeper狀態的每次變化都會收到這樣一個事務ID
Version numbers
Ticks
Real time
四、ZooKeeper Sessions (會話)
Zookeeper客戶端通過
五、ZooKeeper Watches (監聽)
5.1 watch基本概念
Zookeeper所有讀相關操作:getData()、getChildren()、 exists()等都有一個參數boolean watch用來設置watche。按照讀的內容不同,有兩種watch:Data Watch和Child Watch,像getData()和exists()這種屬于讀取znode數據,所以屬于Data Watch,所以當znode數據發生變更,將觸發znode的Data Watch;getChildren()對應的就是Child Watch。創建子節點將觸發父節點的Child Watch,而節點的刪除將同時觸發Data Watch和Child Watch。
Watch是一個一次性觸發器,比如當調用 getData("/znode1", true) 后--true代表設置監聽,該節點被刪除或者數據發生變更,將會觸發客戶端注冊的watch,觸發之后,將被刪除,也就是往后數據再變化就不會再觸發此watch了。
5.2 watch 事件類型
那么當觸發watch的時候有因為數據發生變化的,有因為節點創建或刪除的等等,客戶端如何判斷服務端觸發的watch是各種類型呢?zookeeper提供了EventType的枚舉,服務端觸發watch時,會告訴客戶端是何種類型。
一共有如下這么多事件類型:
- None
- NodeCreated
- NodeDeleted
- NodeDataChanged
- NodeChildrenChanged
- DataWatchRemoved
- ChildWatchRemoved
- PersistentWatchRemoved
其中最后三個事件類型:DataWatchRemoved、ChildWatchRemoved、PersistentWatchRemoved分別是刪除不同類型的watch的時候事件類型,從單詞字面意思也應該能夠理解。
5.3 永久遞歸watch
3.6.0之后(包括3.6.0) 客戶端可以通過addWatch()為znode設置永久、遞歸的watch,這意味著watch不再是一次性的了,可以多次觸發不會被刪除,并且還會遞歸觸發。當然也有移除永久watch的機制:removeWatches()
watch是維護在zookeeper服務端的,所有當客戶端與服務端斷鏈,將不會接受到watch的觸發,而當重連后都將恢復可以重新觸發。
5.3 關于watch的一些注意事項?
1.標準watch是一次性的,如果當客戶端接收到watch的回調通知,那么此watch將被刪除,如果客戶端還想接收到通知,則需要注冊另一個watch
2.正如第一條所講,在客戶端接收到watch回調通知時,可能會繼續設置一個watch以監聽znode的下次變更,但是假如在接收到watch和發送請求設置新watch的中間,znode發生了多次變化,這個可能客戶端會接收不到此變更通知。
3.如果為比如exist、getData注冊了同一個watch,那么當watch被刪除的時候,僅會觸發一次delete watch事件
六、ZooKeeper access control using ACLs(權限控制器)
ACL全稱Access Control List,即訪問控制列表。Zookeeper的ACL實現很類似與UNIX的文件系統權限控制。每一個ACL針對指定的znode,但是并不針對指定znode的子節點,也就是ACL并不遞歸生效。假如給/app設置了一個ACL,那么/app/childtest將不受此ACL控制。
權限的表達式為scheme:id, permissions
6.1 scheme-->授權策略
授權策略一共有4種
| world | 默認策略。任何人都可以訪問 |
| auth | 即已經認證通過的用戶 |
| digest | 通過使用MD5進行哈希 username:password格式生成的字符串來進行身份驗證,當進行身份驗證時,是使用usename:password明文形式字符串,當用做ACL驗證時,會先經過base64編碼,然后使用SHA1加密 |
| ip | 使用客戶端IP作為ACL身份標識。其格式為addr/bits,bits代表客戶端的IP地址要至少匹配ACL中IP地址的多少位 |
| x509 |
6.2 id-->授權對象
6.3 permissions-->權限點
| CREATE | 可以創建子節點 |
| READ | 可以獲取znode數據,以及znode的子節點列表 |
| WRITE | 可以為znode設置數據 |
| DELETE | 可以刪除znode的子節點 |
| ADMIN | 可以為znode設置ACL,ADMIN就像是znode的owner |
六、ZooKeeper ?Consistency Guarantees(一致性保障)
Zookeeper是高性能、可擴展的服務,它的讀操作和寫操作都非常的快
6.1 Sequential Consistency(順序一致性)
來自客戶端的更新將會按照順序進行處理
6.2 Atomicity (原子性)
6.3 Single System Image(單一系統鏡像)
無論客戶端連接到哪一個服務器上,其看到的服務端數據模型都是一致的
6.4Reliability(可靠性)
一旦更新請求被處理,更改的結果將會持久化
6.5 Timeliness (及時性)
??實戰篇
一、下載安裝
1.1 下載
下載地址:https://zookeeper.apache.org/releases.html#download [apache-zookeeper-3.6.2-bin.tar.gz](https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz)
1.2 單機模式
1.2.1 設置配置文件
在conf目錄下會有一個zoo_sample.cfg文件,這里提供了樣例配置信息,我們只需要將此文件改名為zoo.cfg,這樣才會被zookeeper識別。該配置文件中的配置項說明:
tickTime 單位:毫秒。是服務器之間或客戶端與服務器之間維持心跳的時間間隔;且最小會話超時時間將是tickTime的兩倍
dataDir zookeeper保存的數據的目錄地址,默認情況下,事務log也會記在這里(除非另外指定)
clientPort 客戶端連接服務端的端口 zoo.cfg示例
dataDir=/var/lib/zookeeper
clientPort=2181
1.2.2 啟動zookeeper服務端(前提是需要保證安裝機器上有JDK)
./zkServer.sh?start1.2.3 客戶端連接服務器
./zkCli.sh?-server?127.0.0.1:21811.3 集群模式
zookeeper集群部署可以獲得高可靠性,要想實現高可靠容錯好集群,至少需要3臺服務器,且集群數量最好為奇數。
1.3.1 設置集群配置文件zoo.conf
tickTime=2000dataDir=/var/lib/zookeeper/
clientPort=2181
initLimit=5
syncLimit=2
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888
除了單機模式中需要配置的那幾個參數外,需要配置和集群相關的參數
- initLimit 表示集群中的followers服務器 在連接leader服務器時,經過最大initLimit個tickTime后,若leader還未接收到followers的信息,則認為連接失敗
- syncLimit 表示集群中follower服務器與leader服務器在同步消息時最大syncLimit*tickTime時間間隔未收到響應,則此follower會被拋棄
- server.id=host:port:port 配置文件中每行的server.id=host:port:port共同組成一個集群。這里有兩個端口,前面的端口用于與集群leader連接的端口,后面的端口用于leader選舉。如果想在同一臺機器上搭建偽集群,則第一個端口不同即可
1.3.2 設置myid文件
myid文件中只有一行內容,且這行內容就是上述配置server.id=host:port:port 中的id,在集群模式下該id不可重復,范圍為1-255,將此文件放在dataDir參數表示的目錄下
1.3.3 查看服務器狀態
./zkServer.sh?statusfollowerleader
1.3.4 搭建集群過程遇到的問題
在搭建集群的時候,啟動三臺機器都顯示啟動成功,但是使用客戶端命令也連接失敗,通過./zkServer.sh status 顯示Error contacting service. It is probably not running.查看logs下的日志,發現有這么一行:Exception when following the leader java.io.EOFException。查資料發現是我將客戶端端口和follower服務器與leader服務器通信的端口混到一起了,如下圖配置的相同,所以出現了這種錯誤,所以這兩個端口不可以相同
錯誤配置二、zk客戶端 操作 Zookeeper及基礎命令
2.1 bin/zkServer.sh
./zkServer.sh [--config]startstart-foreground|stop|version|restart|status|print-cmd
用于操作zk服務器相關,不同的參數代表不同的操作
#啟動zk服務器bin/zkServer.sh?start
#重啟zk服務器
bin/zkServer.sh?restart
#停止zk服務器
bin/zkServer.sh?stop
#查看zk運行狀態
bin/zkServer.sh?status
#查看zk版本信息
bin/zkServer.sh?version
2.2 bin/zkCli.sh
- 啟動客戶端連接zk服務器
2.3 客戶端命令
- ls列出指定路徑下所有子節點 ls [-s] [-w] [-R] path
ls?/
#列出指定路徑節點下的子節點同時,輸出指定路徑節點狀態細膩些
ls?-s?/
- get查看指定路徑節點信息 get [-s] [-w] path
get?/node1
#?查看/node1節點信息及狀態信息
get?-s?/node1
- create創建節點 create [-s] [-e] [-c] [-t ttl] path [data] [acl] 可選參數 -s 代表創建順序節點 可選參數 -e 代表創建臨時節點 可選參數 -c 代表創建容器節點 可選參數 -t 設置節點過期時間
create?/node1?data
- set修改節點 set [-s] [-v version] path data 可選參數-s代表更新節點后,輸出節點狀態信息 可選參數-v用來表示根據版本號進行更新,低版本肯定是無法更新高版本節點的(樂觀鎖)
[zk:?localhost:2181(CONNECTED)?27]?create?/node2?data
Created?/node2
#查看/node2節點狀態信息
[zk:?localhost:2181(CONNECTED)?28]?get?-s?/node2
data
#...省略其他信息
dataVersion?=?0
#...省略其他信息
#更新節點,此時版本號為1
[zk:?localhost:2181(CONNECTED)?29]?set?-s?/node2?data2
#...省略其他信息
dataVersion?=?1
#...省略其他信息
[zk:?localhost:2181(CONNECTED)?30]?set?-v?1?/node2?data3
#更新失敗
[zk:?localhost:2181(CONNECTED)?31]?set?-v?1?/node2?data3
version?No?is?not?valid?:?/node2
[zk:?localhost:2181(CONNECTED)?32]?
- deletedelete [-v version] path 刪除節點 因為刪除本身也是更新的意思,-v參數同上set的-v參數
delete?/node2
三、Java 操作 Zookeeper
通過Java操作Zookeeper有兩種方式,一種就是通過Zookeeper提供的原生API(鏈接)進行操作,一種就是通過Apache Curator(官網) ---進行操作。
3.1 Apache Curator是什么
Apache Curator是比較完善的Zookeeper客戶端框架,針對Zookeeper原生API的封裝和擴展,降低了使用 Zookeeper的復雜性,使得使用Zookeeper更加可靠、更加簡單。Curator有很多不同的artifacts,可以根據我們的需要進行引入使用:
curator-recipes 該artifact包含了對Zookeeper的所有操作,大部分場景只需要使用該artifact即可滿足需求。
curator-framework 針對zookeeper的高級功能的簡化封裝,該artifact構建在整個客戶端之上,所有應該自動包含此模塊
curator-client 對于Zookeeper客戶端鏈接相關操作的封裝
3.2 使用apache-curator操作zookeeper
3.2.1 引入pom
org.apache.curatorcurator-recipes5.1.05.1.0版本對應的zookeeper客戶端版本為3.6.0
3.2.2 創建連接
創建鏈接需要zk host地址,需要重試策略,還可以設置一些諸如超時時間等等參數。創建客戶端連接的入口類是CuratorFrameworkFactory,該類中有一個內部靜態類Builder,用于設置連接的一些額外高級參數。重試策略則是RetryPolicy。
????public?static?CuratorFramework?createWithOptions(String?connectionString,?RetryPolicy?retryPolicy,?int?connectionTimeoutMs,?int?sessionTimeoutMs){????????return?CuratorFrameworkFactory.builder()
????????????????.connectString(connectionString)
????????????????.retryPolicy(retryPolicy)
????????????????.connectionTimeoutMs(connectionTimeoutMs)
????????????????.sessionTimeoutMs(sessionTimeoutMs)
????????????????.build();
????}
3.2.3 創建節點,更新節點內容
????/**?????*?創建持久性節點
?????*?@param?client?CuratorFramework
?????*?@param?path?節點路徑
?????*?@param?payload?節點內容
?????*?@throws?Exception
?????*/
????public?static?void?create(CuratorFramework?client,?String?path,?byte[]?payload)?throws?Exception?{
????????client.create().forPath(path,?payload);
????}
更多參考官方示例:https://github.com/apache/curator/tree/master/curator-examples/src/main/java/framework
??應用篇
一、應用場景
- 命名服務:按名稱標識集群中的節點
- 統一配置管理
- 數據發布/訂閱
- 分布式鎖
- Leader 選舉
二、通過Zookeeper實現統一配置管理
三、通過Zookeeper實現分布式鎖
Apache Curator針對分布式鎖提供了多種實現
- InterProcessMutex:分布式可重入排它鎖
- InterProcessSemaphoreMutex:分布式排它鎖
- InterProcessReadWriteLock:分布式可重入讀寫鎖
- InterProcessMultiLock:將多個鎖作為單個實體管理的容器
3.1 代碼實戰
3.1.1 分布式可重入排它鎖
/**?*?@author?miaomiao
?*?@date?2020/10/25?11:15
?*/
public?class?DistributReetrantLock?{
????private?final?InterProcessMutex?interProcessMutex;
????private?final?String?lockPath;
????public?DistributReetrantLock(CuratorFramework?client,?String?lockPath)?{
????????this.lockPath?=?lockPath;
????????//?此InterProcessMutex構造方法的maxLeases為1,表示為排他鎖
????????this.interProcessMutex?=?new?InterProcessMutex(client,?lockPath);
????}
????/**
?????*?阻塞式獲取
?????*/
????public?void?tryLock()?throws?Exception?{
????????this.interProcessMutex.acquire();
????}
????/**
?????*?超時未獲取到鎖則獲取鎖失敗
?????*?@param?time
?????*?@param?unit
?????*?@return?是否獲取到鎖
?????*?@throws?Exception
?????*/
????public?boolean?tryLock(long?time,?TimeUnit?unit)?throws?Exception?{
????????return?this.interProcessMutex.acquire(time,unit);
????}
????/**
?????*?釋放鎖
?????*?@throws?Exception
?????*/
????public?void?unLock()?throws?Exception?{
????????this.interProcessMutex.release();
????}
}
測試
?????public?static?void?main(String[]?args)?throws?InterruptedException?{????????ExecutorService?executorService?=?Executors.newFixedThreadPool(5);
????????final?String?lockPath?=?"/lock";
????????for?(int?i?=?0;?i?????????????final?int?clientIndex?=?i;
????????????Callable?callable?=?new?Callable()?{
????????????????public?Void?call()?throws?Exception?{
????????????????????CuratorFramework?simpleClient?=?MyZookeeperClient.createSimpleClient("192.168.0.104:2181");
????????????????????try?{
????????????????????????simpleClient.start();
????????????????????????DistributReetrantLock?distributReetrantLock?=?new?DistributReetrantLock(simpleClient,?lockPath);
????????????????????????//?阻塞式獲取
????????????????????????distributReetrantLock.tryLock();
????????????????????????System.out.println("Client:"?+?clientIndex?+?"?get?lock!");
????????????????????????//?驗證是否是可重入的
????????????????????????distributReetrantLock.tryLock();
????????????????????????System.out.println("Client:"?+?clientIndex?+?"?get?lock?again!");
????????????????????????Thread.sleep(1000);
????????????????????????//?持有鎖一秒后釋放,以便其他客戶端獲取到鎖
????????????????????????System.out.println("Client:"?+?clientIndex?+?"?release?lock!");
????????????????????????distributReetrantLock.unLock();
????????????????????}?finally?{
????????????????????????CloseableUtils.closeQuietly(simpleClient);
????????????????????}return?null;
????????????????}
????????????};
????????????executorService.submit(callable);
????????}
????????executorService.awaitTermination(10,?TimeUnit.MINUTES);
????}
輸出結果
Client:4?get?lock!Client:4?get?lock?again!
Client:4?release?lock!
Client:3?get?lock!
Client:3?get?lock?again!
Client:3?release?lock!
Client:0?get?lock!
Client:0?get?lock?again!
Client:0?release?lock!
Client:1?get?lock!
Client:1?get?lock?again!
Client:1?release?lock!
Client:2?get?lock!
Client:2?get?lock?again!
Client:2?release?lock!
3.1.2 分布式排它鎖
/**?*?分布式排它鎖
?*?@author?miaomiao
?*?@date?2020/10/25?12:54
?*/
public?class?DistributeLock?{
????private?final?String?lockPath;
????private?InterProcessSemaphoreMutex?interProcessSemaphoreMutex;
????public?DistributeLock(CuratorFramework?client,String?lockPath){
????????this.lockPath?=?lockPath;
????????this.interProcessSemaphoreMutex?=?new?InterProcessSemaphoreMutex(client,lockPath);
????}
????/**
?????*?阻塞式獲取
?????*/
????public?void?tryLock()?throws?Exception?{
????????this.interProcessSemaphoreMutex.acquire();
????}
????/**
?????*?超時未獲取到鎖則獲取鎖失敗
?????*?@param?time
?????*?@param?unit
?????*?@return?是否獲取到鎖
?????*?@throws?Exception
?????*/
????public?boolean?tryLock(long?time,?TimeUnit?unit)?throws?Exception?{
????????return?this.interProcessSemaphoreMutex.acquire(time,unit);
????}
????/**
?????*?釋放鎖
?????*?@throws?Exception
?????*/
????public?void?unLock()?throws?Exception?{
????????this.interProcessSemaphoreMutex.release();
????}
}
3.1.3 分布式可重入讀寫鎖
獲取到寫鎖的進程可以繼續獲取讀鎖,當釋放掉寫鎖后,降級為讀鎖。
/**?*?分布式可重入讀寫鎖
?*?@author?miaomiao
?*?@date?2020/10/25?13:07
?*/
public?class?DistributeReetrantReadWriteLock?{
????private?InterProcessReadWriteLock?interProcessReadWriteLock;
????private?String?lockPath;
????public?DistributeReetrantReadWriteLock(CuratorFramework?client,String?lockPath){
????????this.lockPath?=?lockPath;
????????this.interProcessReadWriteLock?=?new?InterProcessReadWriteLock(client,lockPath);
????}
????/**
?????*?阻塞式獲取讀鎖
?????*?@throws?Exception
?????*/
????public?void?tryReadLock()?throws?Exception?{
???????interProcessReadWriteLock.readLock().acquire();
????}
????/**
?????*?獲阻塞式獲取寫鎖
?????*?@throws?Exception
?????*/
????public?void?tryWriteLock()?throws?Exception?{
????????interProcessReadWriteLock.writeLock().acquire();
????}
????/**
?????*?釋放寫鎖
?????*?@throws?Exception
?????*/
????public?void?unlockWriteLock()?throws?Exception?{
????????interProcessReadWriteLock.writeLock().release();
????}
????/**
?????*?釋放讀鎖
?????*?@throws?Exception
?????*/
????public?void?unlockReadLock()?throws?Exception?{
????????interProcessReadWriteLock.readLock().release();
????}
}
3.2 分布式鎖原理
3.1 排它鎖原理
利用 zookeeper 的同級節點的唯一性特性,在需要獲取排他鎖時,所有的客戶端試圖通過調用 create() 接口,在 /指定 節點下創建相同臨時子節點 /exclusive_lock/lock,最終只有一個客戶端能創建成功,那么此客戶端就獲得了分布式鎖。同時,所有沒有獲取到鎖的客戶端可以在 /exclusive_lock 節點上注冊一個子節點變更的 watcher 監聽事件,以便重新爭取獲得鎖。
3.2 讀寫鎖原理
共享鎖需要實現共享讀,排他寫。實現原理:當多個客戶端請求共享鎖時,為指定節點創建臨時順序子節點,且子節點的path能夠區分是哪個客戶端以及當前該客戶端的操作(寫還是讀)就像這樣 [hostname]-請求類型W/R-序號。之后在判斷當前客戶端是否獲得鎖時,如果當前客戶端的操作為讀請求,則判斷如果存在小于自己節點序號的寫請求節點或者自己本身就是最小序列的節點,則獲取到鎖;如果當前客戶端的操作為寫請求時,則只有自己節點序號是最小的節點時,才可以獲取到鎖。如果沒有獲取到鎖,讀請求在比自己序號小的最后一個寫請求節點添加監聽器;寫請求在子節點列表比自己小的最后一個節點注冊watcher監聽。
四、通過Zookeeper實現Leader選舉
在分布式系統中,leader選舉是指指定一個進程(一個實例、一臺機器)作為分配給多臺服務器任務的組織者的過程。在任務開始之前,所有服務器節點都不知道哪個節點將作為任務的領導者或者說協調者,然后在leader選舉之后,每個節點都會識別出一個特定的、唯一的節點作為任務leader。
Apache Curator針對Leader 選舉提供了兩種方式:
4.1 利用順序臨時節點實現
最簡單的方式就是,當有一個"/election"節點,客戶端們為此節點創建一個順序、臨時節點,每個客戶端創建的子節點都會自動帶上一個序號后綴,并且最早創建的序號最小,只需要選舉序號最小的子節點對應的客戶端作為leader即可。
當然這些肯定是遠遠不夠的,還要有假如leader宕機出現故障,必須要重新推舉新的leader機制。一種解決辦法就是所有的應用客戶端都監聽序號最小的子節點來判斷自己是否可以成為leader,因為假如leader客戶端宕機,那么最小序號節點也會消失,所以會產生新的最小序號節點,也就是產生新的leader。但是這樣做會產生羊群效應(herd effect):所有的客戶端都接收到了最小序號子節點被刪除的通知,接下來所有客戶端都調用getChilrden()獲取"/election"的子節點列表,如果客戶端數量很大,將會給zookeeper服務器帶來一定的壓力。為了避免羊群效應,每個客戶端只需要監聽自己對應子節點的前一個節點就足夠了,這樣當leader客戶端宕機,最小子序列節點被刪除,那么最小序列子節點的下一個節點對應的客戶端就成為新的leader。
對應在Apache Curator中的相關實現類為
- org.apache.curator.framework.recipes.leader.LeaderLatch
核心類,主入口
- org.apache.curator.framework.recipes.leader.LeaderLatchListener leader latch監聽器,當leader狀態發生改變時回調,該接口有兩個方法
??public?void?isLeader();
??//當失去leader時調用
??public?void?notLeader();
示例
????????????????????????CuratorFramework?simpleClient?=?MyZookeeperClient.createSimpleClient("192.168.0.104:2181");????????????????????????simpleClient.start();
????????????????????????MyLeaderLatch?leaderLatch?=?new?MyLeaderLatch(simpleClient,"/leader_election"?,new?LeaderLatchListener(){
????????????????????????????public?void?isLeader()?{
????????????????????????????????System.out.println("Client:"+clientIndex+"?is?leader");
????????????????????????????}
????????????????????????????public?void?notLeader()?{
????????????????????????????????System.out.println("Client:"+clientIndex+"?lose?leader");
????????????????????????????}
????????????????????????});
????????????????????????leaderLatch.start();
4.2 利用分布式鎖實現
相關類:
- org.apache.curator.framework.recipes.leader.LeaderSelector
核心類,選舉主入口,構造LeaderSelector 必須傳入LeaderSelectorListener
- org.apache.curator.framework.recipes.leader.LeaderSelectorListener
leader selector監聽器,當被選為leader時回調。當某節點被選舉為leader時,調用takeLeadership,當takeLeadership方法執行完畢后,則此節點就會放棄leader,從而致使重新選舉,即leader得生命周期等于takeLeadership方法得周期。
- org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter
LeaderSelectorListenerAdapter是對LeaderSelectorListener的一個抽象實現,覆寫了stateChanged方法,并當選舉失敗時拋出CancelLeadershipException,官方推薦使用該監聽器
- org.apache.curator.framework.recipes.leader.CancelLeadershipException示例:
????????????????simpleClient.start();
????????????????MyLeaderSelector?leaderSelector?=?new?MyLeaderSelector(simpleClient,?"/leader_selector",?new?LeaderSelectorListenerAdapter()?{
????????????????????public?void?takeLeadership(CuratorFramework?client)?throws?Exception?{
????????????????????????System.out.println("Client:"?+?clientIndex?+?"?get?leader!");
????????????????????}
????????????????});
????????????????//開始選舉
????????????????leaderSelector.start();
??參考文章
https://github.com/Snailclimb/JavaGuide/blob/master/docs/system-design/framework/zookeeper/
https://zookeeper.apache.org/doc/r3.6.2/zookeeperProgrammers.html
https://www.cnblogs.com/qlqwjy/p/10517231.html
ZooKeeper 的應用場景
分布式服務框架 Zookeeper —— 管理分布式環境中的數據
https://www.runoob.com/w3cnote_genre/zookeeper/page/2
http://curator.apache.org/
??理論篇
一、基礎概念
ZooKeeper是開源分布式協調服務,提供高可用、高性能、穩定的分布式數據一致性解決方案,通常被用于實現諸如數據發布/訂閱、負載均衡、命名服務、分布式協調/通知、集群管理、Master選舉、分布式鎖和分布式隊列等功能。
二、ZooKeeper數據模型
image.png2.1 znode(數據節點)
Zookeeper中所有存儲的數據由znode組成,節點也成為znode,并以key value鍵值對形式存儲數據。整體結構類似linux文件系統,根路徑以/開頭。
znode中數據的讀寫都是原子的,而且每一個znode都有一個Access Control List(ACL)用來限制誰可以做什么。每一個znode的數據大小不能超過1M
2.1.1 znode數據節點名稱規范
- null 字符(即\u0000)不能組成znode節點path的命名
- \ud800 - uF8FF, \uFFF0 - uFFFF, \u0001 - \u001F 以及 \u007F、\u009F 這些玩意無法很好地進行展示,看起來像亂碼
- "."可以構成命名的一部分,但是不能單獨作為path的命名
- "zookeeper"是保留字
2.1.2 znode數據節點組成:
- stat 狀態屬性組成:
| czxid | 創建節點的事務ID |
| mzxid | 節點最后一次修改的事務ID |
| pzxid | 子節點列表最后的一次修改(子節點列表的增加或刪除)的事務ID |
| ctime | 創建節點的時間,單位:毫秒 |
| mtime | 節點最后一次修改的時間,單位:毫秒 |
| version | 節點數據變更的次數 |
| cversion | 子節點變更的次數 |
| aversion | 節點ACL變更的次數 |
| ephemeralOwner | 如果節點是臨時節點, 則此值為創建該節點的session的id,否則為0 |
| dataLength | 節點數據長度 |
| numChildren | 子節點個數 |
- data
- children
2.1.3 znode數據節點類型:
持久節點(Persistent Nodes)
臨時節點(Ephemeral Nodes) 臨時節點的生命周期即為創建這些節點的會話生命周期,即會話結束,則這些節點就會被刪除。所以臨時節點不允許創建子節點。
順序節點(Sequence Nodes ) 順序節點可以是持久的,也可以是臨時的。在創建節點時,可為節點路徑添加一個單調遞增計數器,Zookeeper將通過將10位的序列號附加到原始節點名稱后來設置節點路徑。
容器節點(Container Nodes) 此類型節點是3.6.0版本之后添加,容器節點是一種有特殊用途的節點,可用于leader選舉和分布式鎖等,當容器中最后一個子節點被刪除,此容器節點將會在未來某個時刻被刪除。
超時過期節點(TTL Nodes) 此類型節點是3.6.0版本之后添加,當創建一個持久節點或者順序持久節點時,可以為其設置一個毫秒級的超時過期時間,如果在設置時間內,此節點沒又被修改過而且也沒有子節點,則此節點將作為候選項,在未來某個時刻被刪除。當然TTL Nodes默認是禁用狀態的。
三、Zookeeper Time
Zookeeper中時間有很多表示時間的方式。
Zxid 事務ID。Zookeeper狀態的每次變化都會收到這樣一個事務ID
Version numbers
Ticks
Real time
四、ZooKeeper Sessions (會話)
Zookeeper客戶端通過
五、ZooKeeper Watches (監聽)
5.1 watch基本概念
Zookeeper所有讀相關操作:getData()、getChildren()、 exists()等都有一個參數boolean watch用來設置watche。按照讀的內容不同,有兩種watch:Data Watch和Child Watch,像getData()和exists()這種屬于讀取znode數據,所以屬于Data Watch,所以當znode數據發生變更,將觸發znode的Data Watch;getChildren()對應的就是Child Watch。創建子節點將觸發父節點的Child Watch,而節點的刪除將同時觸發Data Watch和Child Watch。
Watch是一個一次性觸發器,比如當調用 getData("/znode1", true) 后--true代表設置監聽,該節點被刪除或者數據發生變更,將會觸發客戶端注冊的watch,觸發之后,將被刪除,也就是往后數據再變化就不會再觸發此watch了。
5.2 watch 事件類型
那么當觸發watch的時候有因為數據發生變化的,有因為節點創建或刪除的等等,客戶端如何判斷服務端觸發的watch是各種類型呢?zookeeper提供了EventType的枚舉,服務端觸發watch時,會告訴客戶端是何種類型。
一共有如下這么多事件類型:
- None
- NodeCreated
- NodeDeleted
- NodeDataChanged
- NodeChildrenChanged
- DataWatchRemoved
- ChildWatchRemoved
- PersistentWatchRemoved
其中最后三個事件類型:DataWatchRemoved、ChildWatchRemoved、PersistentWatchRemoved分別是刪除不同類型的watch的時候事件類型,從單詞字面意思也應該能夠理解。
5.3 永久遞歸watch
3.6.0之后(包括3.6.0) 客戶端可以通過addWatch()為znode設置永久、遞歸的watch,這意味著watch不再是一次性的了,可以多次觸發不會被刪除,并且還會遞歸觸發。當然也有移除永久watch的機制:removeWatches()
watch是維護在zookeeper服務端的,所有當客戶端與服務端斷鏈,將不會接受到watch的觸發,而當重連后都將恢復可以重新觸發。
5.3 關于watch的一些注意事項?
1.標準watch是一次性的,如果當客戶端接收到watch的回調通知,那么此watch將被刪除,如果客戶端還想接收到通知,則需要注冊另一個watch
2.正如第一條所講,在客戶端接收到watch回調通知時,可能會繼續設置一個watch以監聽znode的下次變更,但是假如在接收到watch和發送請求設置新watch的中間,znode發生了多次變化,這個可能客戶端會接收不到此變更通知。
3.如果為比如exist、getData注冊了同一個watch,那么當watch被刪除的時候,僅會觸發一次delete watch事件
六、ZooKeeper access control using ACLs(權限控制器)
ACL全稱Access Control List,即訪問控制列表。Zookeeper的ACL實現很類似與UNIX的文件系統權限控制。每一個ACL針對指定的znode,但是并不針對指定znode的子節點,也就是ACL并不遞歸生效。假如給/app設置了一個ACL,那么/app/childtest將不受此ACL控制。
權限的表達式為scheme:id, permissions
6.1 scheme-->授權策略
授權策略一共有4種
| world | 默認策略。任何人都可以訪問 |
| auth | 即已經認證通過的用戶 |
| digest | 通過使用MD5進行哈希 username:password格式生成的字符串來進行身份驗證,當進行身份驗證時,是使用usename:password明文形式字符串,當用做ACL驗證時,會先經過base64編碼,然后使用SHA1加密 |
| ip | 使用客戶端IP作為ACL身份標識。其格式為addr/bits,bits代表客戶端的IP地址要至少匹配ACL中IP地址的多少位 |
| x509 |
6.2 id-->授權對象
6.3 permissions-->權限點
| CREATE | 可以創建子節點 |
| READ | 可以獲取znode數據,以及znode的子節點列表 |
| WRITE | 可以為znode設置數據 |
| DELETE | 可以刪除znode的子節點 |
| ADMIN | 可以為znode設置ACL,ADMIN就像是znode的owner |
六、ZooKeeper ?Consistency Guarantees(一致性保障)
Zookeeper是高性能、可擴展的服務,它的讀操作和寫操作都非常的快
6.1 Sequential Consistency(順序一致性)
來自客戶端的更新將會按照順序進行處理
6.2 Atomicity (原子性)
6.3 Single System Image(單一系統鏡像)
無論客戶端連接到哪一個服務器上,其看到的服務端數據模型都是一致的
6.4Reliability(可靠性)
一旦更新請求被處理,更改的結果將會持久化
6.5 Timeliness (及時性)
??實戰篇
一、下載安裝
1.1 下載
下載地址:https://zookeeper.apache.org/releases.html#download [apache-zookeeper-3.6.2-bin.tar.gz](https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz)
1.2 單機模式
1.2.1 設置配置文件
在conf目錄下會有一個zoo_sample.cfg文件,這里提供了樣例配置信息,我們只需要將此文件改名為zoo.cfg,這樣才會被zookeeper識別。該配置文件中的配置項說明:
tickTime 單位:毫秒。是服務器之間或客戶端與服務器之間維持心跳的時間間隔;且最小會話超時時間將是tickTime的兩倍
dataDir zookeeper保存的數據的目錄地址,默認情況下,事務log也會記在這里(除非另外指定)
clientPort 客戶端連接服務端的端口 zoo.cfg示例
dataDir=/var/lib/zookeeper
clientPort=2181
1.2.2 啟動zookeeper服務端(前提是需要保證安裝機器上有JDK)
./zkServer.sh?start1.2.3 客戶端連接服務器
./zkCli.sh?-server?127.0.0.1:21811.3 集群模式
zookeeper集群部署可以獲得高可靠性,要想實現高可靠容錯好集群,至少需要3臺服務器,且集群數量最好為奇數。
1.3.1 設置集群配置文件zoo.conf
tickTime=2000dataDir=/var/lib/zookeeper/
clientPort=2181
initLimit=5
syncLimit=2
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888
除了單機模式中需要配置的那幾個參數外,需要配置和集群相關的參數
- initLimit 表示集群中的followers服務器 在連接leader服務器時,經過最大initLimit個tickTime后,若leader還未接收到followers的信息,則認為連接失敗
- syncLimit 表示集群中follower服務器與leader服務器在同步消息時最大syncLimit*tickTime時間間隔未收到響應,則此follower會被拋棄
- server.id=host:port:port 配置文件中每行的server.id=host:port:port共同組成一個集群。這里有兩個端口,前面的端口用于與集群leader連接的端口,后面的端口用于leader選舉。如果想在同一臺機器上搭建偽集群,則第一個端口不同即可
1.3.2 設置myid文件
myid文件中只有一行內容,且這行內容就是上述配置server.id=host:port:port 中的id,在集群模式下該id不可重復,范圍為1-255,將此文件放在dataDir參數表示的目錄下
1.3.3 查看服務器狀態
./zkServer.sh?statusfollowerleader
1.3.4 搭建集群過程遇到的問題
在搭建集群的時候,啟動三臺機器都顯示啟動成功,但是使用客戶端命令也連接失敗,通過./zkServer.sh status 顯示Error contacting service. It is probably not running.查看logs下的日志,發現有這么一行:Exception when following the leader java.io.EOFException。查資料發現是我將客戶端端口和follower服務器與leader服務器通信的端口混到一起了,如下圖配置的相同,所以出現了這種錯誤,所以這兩個端口不可以相同
錯誤配置二、zk客戶端 操作 Zookeeper及基礎命令
2.1 bin/zkServer.sh
./zkServer.sh [--config]startstart-foreground|stop|version|restart|status|print-cmd
用于操作zk服務器相關,不同的參數代表不同的操作
#啟動zk服務器bin/zkServer.sh?start
#重啟zk服務器
bin/zkServer.sh?restart
#停止zk服務器
bin/zkServer.sh?stop
#查看zk運行狀態
bin/zkServer.sh?status
#查看zk版本信息
bin/zkServer.sh?version
2.2 bin/zkCli.sh
- 啟動客戶端連接zk服務器
2.3 客戶端命令
- ls列出指定路徑下所有子節點 ls [-s] [-w] [-R] path
ls?/
#列出指定路徑節點下的子節點同時,輸出指定路徑節點狀態細膩些
ls?-s?/
- get查看指定路徑節點信息 get [-s] [-w] path
get?/node1
#?查看/node1節點信息及狀態信息
get?-s?/node1
- create創建節點 create [-s] [-e] [-c] [-t ttl] path [data] [acl] 可選參數 -s 代表創建順序節點 可選參數 -e 代表創建臨時節點 可選參數 -c 代表創建容器節點 可選參數 -t 設置節點過期時間
create?/node1?data
- set修改節點 set [-s] [-v version] path data 可選參數-s代表更新節點后,輸出節點狀態信息 可選參數-v用來表示根據版本號進行更新,低版本肯定是無法更新高版本節點的(樂觀鎖)
[zk:?localhost:2181(CONNECTED)?27]?create?/node2?data
Created?/node2
#查看/node2節點狀態信息
[zk:?localhost:2181(CONNECTED)?28]?get?-s?/node2
data
#...省略其他信息
dataVersion?=?0
#...省略其他信息
#更新節點,此時版本號為1
[zk:?localhost:2181(CONNECTED)?29]?set?-s?/node2?data2
#...省略其他信息
dataVersion?=?1
#...省略其他信息
[zk:?localhost:2181(CONNECTED)?30]?set?-v?1?/node2?data3
#更新失敗
[zk:?localhost:2181(CONNECTED)?31]?set?-v?1?/node2?data3
version?No?is?not?valid?:?/node2
[zk:?localhost:2181(CONNECTED)?32]?
- deletedelete [-v version] path 刪除節點 因為刪除本身也是更新的意思,-v參數同上set的-v參數
delete?/node2
三、Java 操作 Zookeeper
通過Java操作Zookeeper有兩種方式,一種就是通過Zookeeper提供的原生API(鏈接)進行操作,一種就是通過Apache Curator(官網) ---進行操作。
3.1 Apache Curator是什么
Apache Curator是比較完善的Zookeeper客戶端框架,針對Zookeeper原生API的封裝和擴展,降低了使用 Zookeeper的復雜性,使得使用Zookeeper更加可靠、更加簡單。Curator有很多不同的artifacts,可以根據我們的需要進行引入使用:
curator-recipes 該artifact包含了對Zookeeper的所有操作,大部分場景只需要使用該artifact即可滿足需求。
curator-framework 針對zookeeper的高級功能的簡化封裝,該artifact構建在整個客戶端之上,所有應該自動包含此模塊
curator-client 對于Zookeeper客戶端鏈接相關操作的封裝
3.2 使用apache-curator操作zookeeper
3.2.1 引入pom
org.apache.curatorcurator-recipes5.1.05.1.0版本對應的zookeeper客戶端版本為3.6.0
3.2.2 創建連接
創建鏈接需要zk host地址,需要重試策略,還可以設置一些諸如超時時間等等參數。創建客戶端連接的入口類是CuratorFrameworkFactory,該類中有一個內部靜態類Builder,用于設置連接的一些額外高級參數。重試策略則是RetryPolicy。
????public?static?CuratorFramework?createWithOptions(String?connectionString,?RetryPolicy?retryPolicy,?int?connectionTimeoutMs,?int?sessionTimeoutMs){????????return?CuratorFrameworkFactory.builder()
????????????????.connectString(connectionString)
????????????????.retryPolicy(retryPolicy)
????????????????.connectionTimeoutMs(connectionTimeoutMs)
????????????????.sessionTimeoutMs(sessionTimeoutMs)
????????????????.build();
????}
3.2.3 創建節點,更新節點內容
????/**?????*?創建持久性節點
?????*?@param?client?CuratorFramework
?????*?@param?path?節點路徑
?????*?@param?payload?節點內容
?????*?@throws?Exception
?????*/
????public?static?void?create(CuratorFramework?client,?String?path,?byte[]?payload)?throws?Exception?{
????????client.create().forPath(path,?payload);
????}
更多參考官方示例:https://github.com/apache/curator/tree/master/curator-examples/src/main/java/framework
??應用篇
一、應用場景
- 命名服務:按名稱標識集群中的節點
- 統一配置管理
- 數據發布/訂閱
- 分布式鎖
- Leader 選舉
二、通過Zookeeper實現統一配置管理
三、通過Zookeeper實現分布式鎖
Apache Curator針對分布式鎖提供了多種實現
- InterProcessMutex:分布式可重入排它鎖
- InterProcessSemaphoreMutex:分布式排它鎖
- InterProcessReadWriteLock:分布式可重入讀寫鎖
- InterProcessMultiLock:將多個鎖作為單個實體管理的容器
3.1 代碼實戰
3.1.1 分布式可重入排它鎖
/**?*?@author?miaomiao
?*?@date?2020/10/25?11:15
?*/
public?class?DistributReetrantLock?{
????private?final?InterProcessMutex?interProcessMutex;
????private?final?String?lockPath;
????public?DistributReetrantLock(CuratorFramework?client,?String?lockPath)?{
????????this.lockPath?=?lockPath;
????????//?此InterProcessMutex構造方法的maxLeases為1,表示為排他鎖
????????this.interProcessMutex?=?new?InterProcessMutex(client,?lockPath);
????}
????/**
?????*?阻塞式獲取
?????*/
????public?void?tryLock()?throws?Exception?{
????????this.interProcessMutex.acquire();
????}
????/**
?????*?超時未獲取到鎖則獲取鎖失敗
?????*?@param?time
?????*?@param?unit
?????*?@return?是否獲取到鎖
?????*?@throws?Exception
?????*/
????public?boolean?tryLock(long?time,?TimeUnit?unit)?throws?Exception?{
????????return?this.interProcessMutex.acquire(time,unit);
????}
????/**
?????*?釋放鎖
?????*?@throws?Exception
?????*/
????public?void?unLock()?throws?Exception?{
????????this.interProcessMutex.release();
????}
}
測試
?????public?static?void?main(String[]?args)?throws?InterruptedException?{????????ExecutorService?executorService?=?Executors.newFixedThreadPool(5);
????????final?String?lockPath?=?"/lock";
????????for?(int?i?=?0;?i?????????????final?int?clientIndex?=?i;
????????????Callable?callable?=?new?Callable()?{
????????????????public?Void?call()?throws?Exception?{
????????????????????CuratorFramework?simpleClient?=?MyZookeeperClient.createSimpleClient("192.168.0.104:2181");
????????????????????try?{
????????????????????????simpleClient.start();
????????????????????????DistributReetrantLock?distributReetrantLock?=?new?DistributReetrantLock(simpleClient,?lockPath);
????????????????????????//?阻塞式獲取
????????????????????????distributReetrantLock.tryLock();
????????????????????????System.out.println("Client:"?+?clientIndex?+?"?get?lock!");
????????????????????????//?驗證是否是可重入的
????????????????????????distributReetrantLock.tryLock();
????????????????????????System.out.println("Client:"?+?clientIndex?+?"?get?lock?again!");
????????????????????????Thread.sleep(1000);
????????????????????????//?持有鎖一秒后釋放,以便其他客戶端獲取到鎖
????????????????????????System.out.println("Client:"?+?clientIndex?+?"?release?lock!");
????????????????????????distributReetrantLock.unLock();
????????????????????}?finally?{
????????????????????????CloseableUtils.closeQuietly(simpleClient);
????????????????????}return?null;
????????????????}
????????????};
????????????executorService.submit(callable);
????????}
????????executorService.awaitTermination(10,?TimeUnit.MINUTES);
????}
輸出結果
Client:4?get?lock!Client:4?get?lock?again!
Client:4?release?lock!
Client:3?get?lock!
Client:3?get?lock?again!
Client:3?release?lock!
Client:0?get?lock!
Client:0?get?lock?again!
Client:0?release?lock!
Client:1?get?lock!
Client:1?get?lock?again!
Client:1?release?lock!
Client:2?get?lock!
Client:2?get?lock?again!
Client:2?release?lock!
3.1.2 分布式排它鎖
/**?*?分布式排它鎖
?*?@author?miaomiao
?*?@date?2020/10/25?12:54
?*/
public?class?DistributeLock?{
????private?final?String?lockPath;
????private?InterProcessSemaphoreMutex?interProcessSemaphoreMutex;
????public?DistributeLock(CuratorFramework?client,String?lockPath){
????????this.lockPath?=?lockPath;
????????this.interProcessSemaphoreMutex?=?new?InterProcessSemaphoreMutex(client,lockPath);
????}
????/**
?????*?阻塞式獲取
?????*/
????public?void?tryLock()?throws?Exception?{
????????this.interProcessSemaphoreMutex.acquire();
????}
????/**
?????*?超時未獲取到鎖則獲取鎖失敗
?????*?@param?time
?????*?@param?unit
?????*?@return?是否獲取到鎖
?????*?@throws?Exception
?????*/
????public?boolean?tryLock(long?time,?TimeUnit?unit)?throws?Exception?{
????????return?this.interProcessSemaphoreMutex.acquire(time,unit);
????}
????/**
?????*?釋放鎖
?????*?@throws?Exception
?????*/
????public?void?unLock()?throws?Exception?{
????????this.interProcessSemaphoreMutex.release();
????}
}
3.1.3 分布式可重入讀寫鎖
獲取到寫鎖的進程可以繼續獲取讀鎖,當釋放掉寫鎖后,降級為讀鎖。
/**?*?分布式可重入讀寫鎖
?*?@author?miaomiao
?*?@date?2020/10/25?13:07
?*/
public?class?DistributeReetrantReadWriteLock?{
????private?InterProcessReadWriteLock?interProcessReadWriteLock;
????private?String?lockPath;
????public?DistributeReetrantReadWriteLock(CuratorFramework?client,String?lockPath){
????????this.lockPath?=?lockPath;
????????this.interProcessReadWriteLock?=?new?InterProcessReadWriteLock(client,lockPath);
????}
????/**
?????*?阻塞式獲取讀鎖
?????*?@throws?Exception
?????*/
????public?void?tryReadLock()?throws?Exception?{
???????interProcessReadWriteLock.readLock().acquire();
????}
????/**
?????*?獲阻塞式獲取寫鎖
?????*?@throws?Exception
?????*/
????public?void?tryWriteLock()?throws?Exception?{
????????interProcessReadWriteLock.writeLock().acquire();
????}
????/**
?????*?釋放寫鎖
?????*?@throws?Exception
?????*/
????public?void?unlockWriteLock()?throws?Exception?{
????????interProcessReadWriteLock.writeLock().release();
????}
????/**
?????*?釋放讀鎖
?????*?@throws?Exception
?????*/
????public?void?unlockReadLock()?throws?Exception?{
????????interProcessReadWriteLock.readLock().release();
????}
}
3.2 分布式鎖原理
3.1 排它鎖原理
利用 zookeeper 的同級節點的唯一性特性,在需要獲取排他鎖時,所有的客戶端試圖通過調用 create() 接口,在 /指定 節點下創建相同臨時子節點 /exclusive_lock/lock,最終只有一個客戶端能創建成功,那么此客戶端就獲得了分布式鎖。同時,所有沒有獲取到鎖的客戶端可以在 /exclusive_lock 節點上注冊一個子節點變更的 watcher 監聽事件,以便重新爭取獲得鎖。
3.2 讀寫鎖原理
共享鎖需要實現共享讀,排他寫。實現原理:當多個客戶端請求共享鎖時,為指定節點創建臨時順序子節點,且子節點的path能夠區分是哪個客戶端以及當前該客戶端的操作(寫還是讀)就像這樣 [hostname]-請求類型W/R-序號。之后在判斷當前客戶端是否獲得鎖時,如果當前客戶端的操作為讀請求,則判斷如果存在小于自己節點序號的寫請求節點或者自己本身就是最小序列的節點,則獲取到鎖;如果當前客戶端的操作為寫請求時,則只有自己節點序號是最小的節點時,才可以獲取到鎖。如果沒有獲取到鎖,讀請求在比自己序號小的最后一個寫請求節點添加監聽器;寫請求在子節點列表比自己小的最后一個節點注冊watcher監聽。
四、通過Zookeeper實現Leader選舉
在分布式系統中,leader選舉是指指定一個進程(一個實例、一臺機器)作為分配給多臺服務器任務的組織者的過程。在任務開始之前,所有服務器節點都不知道哪個節點將作為任務的領導者或者說協調者,然后在leader選舉之后,每個節點都會識別出一個特定的、唯一的節點作為任務leader。
Apache Curator針對Leader 選舉提供了兩種方式:
4.1 利用順序臨時節點實現
最簡單的方式就是,當有一個"/election"節點,客戶端們為此節點創建一個順序、臨時節點,每個客戶端創建的子節點都會自動帶上一個序號后綴,并且最早創建的序號最小,只需要選舉序號最小的子節點對應的客戶端作為leader即可。
當然這些肯定是遠遠不夠的,還要有假如leader宕機出現故障,必須要重新推舉新的leader機制。一種解決辦法就是所有的應用客戶端都監聽序號最小的子節點來判斷自己是否可以成為leader,因為假如leader客戶端宕機,那么最小序號節點也會消失,所以會產生新的最小序號節點,也就是產生新的leader。但是這樣做會產生羊群效應(herd effect):所有的客戶端都接收到了最小序號子節點被刪除的通知,接下來所有客戶端都調用getChilrden()獲取"/election"的子節點列表,如果客戶端數量很大,將會給zookeeper服務器帶來一定的壓力。為了避免羊群效應,每個客戶端只需要監聽自己對應子節點的前一個節點就足夠了,這樣當leader客戶端宕機,最小子序列節點被刪除,那么最小序列子節點的下一個節點對應的客戶端就成為新的leader。
對應在Apache Curator中的相關實現類為
- org.apache.curator.framework.recipes.leader.LeaderLatch
核心類,主入口
- org.apache.curator.framework.recipes.leader.LeaderLatchListener leader latch監聽器,當leader狀態發生改變時回調,該接口有兩個方法
??public?void?isLeader();
??//當失去leader時調用
??public?void?notLeader();
示例
????????????????????????CuratorFramework?simpleClient?=?MyZookeeperClient.createSimpleClient("192.168.0.104:2181");????????????????????????simpleClient.start();
????????????????????????MyLeaderLatch?leaderLatch?=?new?MyLeaderLatch(simpleClient,"/leader_election"?,new?LeaderLatchListener(){
????????????????????????????public?void?isLeader()?{
????????????????????????????????System.out.println("Client:"+clientIndex+"?is?leader");
????????????????????????????}
????????????????????????????public?void?notLeader()?{
????????????????????????????????System.out.println("Client:"+clientIndex+"?lose?leader");
????????????????????????????}
????????????????????????});
????????????????????????leaderLatch.start();
4.2 利用分布式鎖實現
相關類:
- org.apache.curator.framework.recipes.leader.LeaderSelector
核心類,選舉主入口,構造LeaderSelector 必須傳入LeaderSelectorListener
- org.apache.curator.framework.recipes.leader.LeaderSelectorListener
leader selector監聽器,當被選為leader時回調。當某節點被選舉為leader時,調用takeLeadership,當takeLeadership方法執行完畢后,則此節點就會放棄leader,從而致使重新選舉,即leader得生命周期等于takeLeadership方法得周期。
- org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter
LeaderSelectorListenerAdapter是對LeaderSelectorListener的一個抽象實現,覆寫了stateChanged方法,并當選舉失敗時拋出CancelLeadershipException,官方推薦使用該監聽器
- org.apache.curator.framework.recipes.leader.CancelLeadershipException示例:
????????????????simpleClient.start();
????????????????MyLeaderSelector?leaderSelector?=?new?MyLeaderSelector(simpleClient,?"/leader_selector",?new?LeaderSelectorListenerAdapter()?{
????????????????????public?void?takeLeadership(CuratorFramework?client)?throws?Exception?{
????????????????????????System.out.println("Client:"?+?clientIndex?+?"?get?leader!");
????????????????????}
????????????????});
????????????????//開始選舉
????????????????leaderSelector.start();
??參考文章
https://github.com/Snailclimb/JavaGuide/blob/master/docs/system-design/framework/zookeeper/
https://zookeeper.apache.org/doc/r3.6.2/zookeeperProgrammers.html
https://www.cnblogs.com/qlqwjy/p/10517231.html
ZooKeeper 的應用場景
分布式服務框架 Zookeeper —— 管理分布式環境中的數據
https://www.runoob.com/w3cnote_genre/zookeeper/page/2
http://curator.apache.org/
總結
以上是生活随笔為你收集整理的zookeeper中展示所有节点_分布式协调服务之Zookeeper的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle数据库函数手册,Oracle
- 下一篇: 分数小数互换图_重复控制器学习心得(二)