不懂就问:ZooKeeper 集群如何进行数据同步?
本文作者:HelloGitHub-老荀
Hi,這里是 HelloGitHub 推出的 HelloZooKeeper 系列,免費(fèi)開源、有趣、入門級的 ZooKeeper 教程,面向有編程基礎(chǔ)的新手。
項(xiàng)目地址:https://github.com/HelloGitHub-Team/HelloZooKeeper
前一篇文章我們介紹了 ZK 是如何進(jìn)行持久化的,這章我們將正式學(xué)習(xí) Follower 或 Observer 是如何在選舉之后和 Leader 進(jìn)行數(shù)據(jù)同步的。
一、選舉完成
經(jīng)歷了選舉之后,我們的馬果果榮耀當(dāng)選當(dāng)前辦事處集群的 Leader,所以現(xiàn)在假設(shè)各個辦事處的關(guān)系圖是這樣:
我們現(xiàn)在就來說說馬小云和馬小騰是如何同馬果果進(jìn)行數(shù)據(jù)同步的。
結(jié)束了累人的選舉后,馬小云和馬小騰以微弱的優(yōu)勢輸?shù)袅烁偁?#xff0c;只能委屈成為 Follower。整理完各自的情緒后,他們要做的第一件事情就是通過話務(wù)員上報(bào)自己的信息給馬果果,使用了專門的暗號 FOLLOWERINFO, 數(shù)據(jù)主要有自己的 epoch 和 myid:
然后是馬果果這邊,他收到 FOLLOWERINFO 之后也會進(jìn)行統(tǒng)計(jì),直到達(dá)到半數(shù)以上后,綜合各個 Follower 給的信息會計(jì)算出新的 epoch,然后將這個新的 epoch 隨著暗號 LEADERINFO 回發(fā)給其他 Follower
然后再回到馬小云和馬小騰這邊,收到 LEADERINFO 之后將新的 epoch 記錄下來,然后回復(fù)給馬果果一個 ACKEPOCH 暗號并帶上自己這邊的最大 zxid,表示剛剛的 LEADERINFO 收到了
然后馬果果這邊也會等待半數(shù)以上的 ACKEPOCH 的通知,收到之后會根據(jù)各個 Follower 的信息給出不同的同步策略。關(guān)于不同的同步策略,這里我先入為主的給大家介紹一下:
DIFF,如果 Follower 的記錄和 Leader 的記錄相差的不多,使用增量同步的方式將一個一個寫請求發(fā)送給 Follower
TRUNC,這個情況的出現(xiàn)代表 Follower 的 zxid 是領(lǐng)先于當(dāng)前的 Leader 的(可能是以前的 Leader),需要 Follower 自行把多余的部分給截?cái)?#xff0c;降級到和 Leader 一致
SNAP,如果 Follower 的記錄和當(dāng)前 Leader 相差太多,Leader 直接將自己的整個內(nèi)存數(shù)據(jù)發(fā)送給 Follower
至于采用哪一種策略,是如何進(jìn)行判斷的,接下來一一進(jìn)行講解。
1.1 DIFF
每一個 ZK 節(jié)點(diǎn)在收到寫請求后,會維護(hù)一個寫請求隊(duì)列(默認(rèn)是 500 大小,通過 zookeeper.commitLogCount 配置),將寫請求記錄在其中,這個隊(duì)列中的最早進(jìn)入的寫請求當(dāng)時(shí)的 zxid 就是 minZxid(以下簡稱 min),最后一個進(jìn)入的寫請求的 zxid 就是 maxZxid(以下簡稱 max),達(dá)到上限后,會移除最早進(jìn)入的寫請求,知道了這兩個值之后,我們來看看 DIFF 是怎么判斷的。
1.1.1 從內(nèi)存中的寫請求隊(duì)列恢復(fù)
一種情況就是如果當(dāng) Follower 通過 ACKEPOCH 上報(bào)的 zxid 是在 min 和 max 之間的話,就采用 DIFF 策略進(jìn)行數(shù)據(jù)同步。
我們的例子中 Leader 的 zxid 是 99,說明這個存儲 500 個寫請求的隊(duì)列根本沒有放滿,所以 min 是 1 max 是 99,很顯然 77 以及 88 是在這個區(qū)間內(nèi)的,那馬果果就會為另外兩位 Follower 找到他們各自所需要的區(qū)間,先發(fā)送一個 DIFF 給 Follower,然后將一條條的寫請求包裝成 PROPOSAL 和 COMMIT 的順序發(fā)給他們
1.1.2 從磁盤文件 log 恢復(fù)
另一種情況是如果 Follower 的 zxid 不在 min 和 max 的區(qū)間內(nèi)時(shí),但當(dāng) zookeeper.snapshotSizeFactor 配置大于 0 的話(默認(rèn)是 0.33),會嘗試使用 log 進(jìn)行 DIFF,但是需要同步的 log 文件的總大小不能超過當(dāng)前最新的 snapshot 文件大小的三分之一(以默認(rèn) 0.33 為例)的話,才可以通過讀取 log 文件中的寫請求記錄進(jìn)行 DIFF 同步。同步的方法也和上面一樣,先發(fā)送一個 DIFF 給 Follower 然后從 log 文件中找到該 Follower 的區(qū)間,再一條條的發(fā)送 PROPOSAL 和 COMMIT。
而 Follower 收到 PROPOSAL 的暗號消息后,就會像處理客戶端請求那樣去一條條處理,慢慢就會將數(shù)據(jù)恢復(fù)成和 Leader 是一致的。
1.2 SNAP
假設(shè)現(xiàn)在三個辦事處是這樣的
馬果果的寫請求隊(duì)列在默認(rèn)配置下記錄了 277 至 777 的寫請求,又假設(shè)現(xiàn)在的場景不滿足上面 1.1.2 的情況,馬果果就知道當(dāng)前需要通過 SNAP 的情況進(jìn)行同步了。
馬果果會先發(fā)送一個 SNAP 的請求給馬小云和馬小騰讓他們準(zhǔn)備起來
緊接著就會當(dāng)前內(nèi)存中的數(shù)據(jù)整個序列化(和 snapshot 文件是一樣的)然后一起發(fā)送給馬小云和馬小騰。
而馬小云和馬小騰收到馬果果發(fā)來的整個 snapshot 之后會先清空自己當(dāng)前的數(shù)據(jù)庫的所有信息,接著直接將收到的 snapshot 反序列化就完成了整個內(nèi)存數(shù)據(jù)的恢復(fù)。
1.3 TRUNC
最后一種策略的場景假設(shè)是這樣:
假設(shè)馬小騰是上一個 Leader,但是經(jīng)歷了停電以后恢復(fù)重新以 Follower 的身份加入集群,但是他的 zxid 要比 max 還大,這個時(shí)候馬果果就會給馬小騰發(fā)送 TRUNC,(至于圖中為什么馬小云不舉例為 TRUNC,因?yàn)槿绻?strong>馬小云的 zxid 也比馬果果要大的話,馬果果在當(dāng)前場景下就不可能當(dāng)選 Leader 了)。
馬果果就會發(fā)送 TRUNC 給馬小騰(這里忽略馬小云)
假設(shè)馬小騰的本地 log 文件目錄下是這樣的:
/tmp └──?zookeeper└──?log└──?version-2└──?log.0└──?log.500└──?log.800而馬小騰收到 TRUNC 之后,會找到本地 log 文件中所有大于 777 的 log 文件刪除,即這里的 log.800 ,然后會在 log.500 這個文件找到 777 這個 zxid 記錄并且把當(dāng)前文件的讀寫指針修改至 777 的位置,之后針對該文件的讀寫操作就會從 777 開始,這樣就會把之后的那些記錄給覆蓋了。
而馬果果這邊當(dāng)判斷完同步策略并發(fā)送給另外兩馬之后,便會發(fā)送一個 NEWLEADER 的信息給他們
而馬小云和馬小騰在收到 NEWLEADER 之后,若之前是通過 SNAP 方式同步數(shù)據(jù)的話,這里會強(qiáng)制快照一份新的 snapshot 文件在自己這里。然后會回復(fù)給馬果果一個 ACK 的消息,告訴他自己的同步數(shù)據(jù)已經(jīng)完成了
然后馬果果同樣會等待半數(shù)一樣的 ACK 接收完成后,再發(fā)送一個 UPTODATE 給其他兩馬,告訴他們現(xiàn)在辦事處數(shù)據(jù)已經(jīng)都一致了,可以開始對外提供服務(wù)了
然后馬小云和馬果果收到 UPTODATE 之后會再回復(fù)一個 ACK 給馬果果,但是這次馬果果收到這次的 ACK 之后不會做處理,所以在 UPTODATE 之后,各個辦事處就已經(jīng)算可以正式對外提供服務(wù)了。
上面說了這么多,但是馬小云和馬小騰都是 Follower,如果是 Observer 呢?怎么用上面的步驟同步呢?
區(qū)別就在第一步,Follower 發(fā)送的是 FOLLOWERINFO,而 Observer 發(fā)送的是 OBSERVERINFO 除此之外沒有任何區(qū)別,和 Follower 是一樣的步驟進(jìn)行數(shù)據(jù)同步。
二、繼續(xù)深挖
現(xiàn)在把其中的一些細(xì)節(jié)再用猿話說明一下,三種不同的數(shù)據(jù)同步策略,Leader 在發(fā)送 Follower 的時(shí)候采用的具體方法是不太相同的
2.1 三種策略發(fā)送方式
如果采用的是 DIFF 或者 TRUNC 的同步方法的話,Leader 其實(shí)不是在找到有差異數(shù)據(jù)的時(shí)候發(fā)送過去的,而是按照順序先放入一個隊(duì)列,最后再統(tǒng)一啟動一個線程去一個個發(fā)送的
DIFF :
TRUNC:
但是以 SNAP 方式同步的話就不會放入該隊(duì)列,無論是 SNAP 消息還是之后整個序列化后的內(nèi)存快照 snapshot 都會直接通過服務(wù)端間的 socket 直接寫入。
2.2 上帝視角
讓我們把三種策略消息交互的全過程再看一遍,這里就以馬小云舉例了
2.2.1 DIFF
2.2.2 TRUNC
2.2.3 SNAP
可以看到首尾是一樣的,就是中間的請求根據(jù)不同的策略會有不同的請求發(fā)送。差不多到這里關(guān)于 Follower 或 Observer 是如何同 Leader 同步消息,整體的邏輯都介紹完了。
2.3 小結(jié)
Follower 和 Observer 同步數(shù)據(jù)的方式一共有三種:DIFF、SNAP、TRUNC
DIFF 需要 Follower 或 Observer 和 Leader 的數(shù)據(jù)相差在 min 和 max 范圍內(nèi),或者配置了允許從 log 文件中恢復(fù)
TRUNC 是當(dāng) Follower 或 Observer 的 zxid 比 Leader 還要大的時(shí)候,該節(jié)點(diǎn)需要主動刪除多余 zxid 相關(guān)的數(shù)據(jù),降級至 Leader 一致
SNAP 作為最后的數(shù)據(jù)同步手段,由 Leader 直接將內(nèi)存數(shù)據(jù)整個序列化完并發(fā)送給 Follower 或 Observer,以達(dá)到恢復(fù)數(shù)據(jù)的目的
我看了下文章的字?jǐn)?shù)還行,決定加一點(diǎn)料,開一個小篇講一下 ACL,這個我拖了很久沒解釋的坑。
三、沒有規(guī)矩,不成方圓
先帶大家重拾記憶,之前創(chuàng)建節(jié)點(diǎn)代碼片段中的 ZooDefs.Ids.OPEN_ACL_UNSAFE 就是 ACL 的參數(shù)
client.create("/更新視頻/跳舞/20201101",?"這是Data,既可以記錄一些業(yè)務(wù)數(shù)據(jù)也可以隨便寫".getBytes(),?ZooDefs.Ids.OPEN_ACL_UNSAFE,?CreateMode.PERSISTENT);首先如果配置了 zookeeper.skipACL 該參數(shù)為 yes(注意大小寫),表示當(dāng)前節(jié)點(diǎn)放棄 ACL 校驗(yàn),默認(rèn)是 no
那這個 ACL 是怎么規(guī)定的,有哪些權(quán)限,又是怎么在服務(wù)端體現(xiàn)的呢?首先 ACL 整體分為 Permission 和 Scheme 兩部分,Permission 是針對操作的權(quán)限,而 Scheme 是指定使用哪一種鑒權(quán)模式,下面我們一起來了解下。
3.1 權(quán)限 Permission 介紹
首先 ZK 將權(quán)限分為 5 種:
READ(以下簡稱 R),獲取節(jié)點(diǎn)數(shù)據(jù)或者獲取子節(jié)點(diǎn)列表
WRITE(以下簡稱 W),設(shè)置節(jié)點(diǎn)數(shù)據(jù)
CREATE(以下簡稱 C),創(chuàng)建節(jié)點(diǎn)
DELETE(以下簡稱 D),刪除節(jié)點(diǎn)
ADMIN(以下簡稱 A),設(shè)置節(jié)點(diǎn)的 ACL 權(quán)限
然后該 5 種權(quán)限在代碼層面就是簡單的 int 數(shù)據(jù),而判斷是否有權(quán)限只需要用 & 操作即可,和目標(biāo)權(quán)限 & 完結(jié)果只要不等于 0 就說明擁有該權(quán)限,細(xì)節(jié)如下:
??int??binary R??1???00001 W??2???00010 C??4???00100 D??8???01000 A??16??10000假設(shè)現(xiàn)在的客戶端權(quán)限為 RWC,對應(yīng)的數(shù)值就是各個權(quán)限相加 1 + 2 + 4 = 7
??int??binary RWC?7???00111對任意有 R、W、C 權(quán)限需求的節(jié)點(diǎn),求 & 的結(jié)果都不為 0,所以就能判斷該客戶端是擁有 RWC 這 3 個權(quán)限的。
但是如果當(dāng)該客戶端對目標(biāo)節(jié)點(diǎn)進(jìn)行刪除時(shí),做 & 判斷權(quán)限的話,可以得到結(jié)果為 0,表示該客戶端不具備刪除的權(quán)限,就會返回給客戶端權(quán)限錯誤
??int??binary RWC?7???00111 D??8??&?01000 ------------------ 結(jié)果?0???000003.2 Scheme 介紹
Scheme 有 4 種,分別是 ip、world、digest、super,但是其實(shí)就是兩大類,一種是針對 IP 地址的 ip,另一種是使用類似“用戶名:密碼”的 world、digest、super。其實(shí)整個 ACL 是分三個部分的,scheme:id:perms ,id 的取值取決于 scheme 的種類,這里是 ip 所以 id 的取值就是具體的 IP 地址,而 perms 則是我上一小節(jié)介紹的 RWCDA。
這三部分的前兩部分 scheme:id 相當(dāng)于告訴服務(wù)端 “我是誰?”,而最后的部分 perms 則是代表了 “我能做什么?”,這兩個問題,任意一個問題出錯都會導(dǎo)致服務(wù)端拋出 NoAuthException 的異常,告訴客戶端權(quán)限不夠。
3.2.1 IP
我們先來直接看一段代碼,其中的 IP 10.11.12.13 我是隨便寫的
ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null); List<ACL>?aclList?=?new?ArrayList<>(); aclList.add(new?ACL(ZooDefs.Perms.ALL,?new?Id("ip",?"10.11.12.13"))); String?path?=?client.create("/abc",?"test".getBytes(),?aclList,?CreateMode.PERSISTENT); System.out.println(path);?//?輸出?/abc client.close();可以看到 /abc 是可以被正確輸出的,而且通過查看 / 的子節(jié)點(diǎn)列表是可以看到 /abc 節(jié)點(diǎn)的
ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null); List<String>?children?=?client.getChildren("/",?false); System.out.println(children);?//?輸出?[abc,?zookeeper] client.close();但是現(xiàn)在如果去訪問該節(jié)點(diǎn)的數(shù)據(jù)的話就會得到報(bào)錯
ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null); byte[]?data?=?client.getData("/abc",?false,?null); System.out.println(new?String(data)); client.close(); Exception?in?thread?"main"?org.apache.zookeeper.KeeperException$NoAuthException:?KeeperErrorCode?=?NoAuth?for?/abc讀者可以試試把上面的 IP 改成 127.0.0.1 重新創(chuàng)建節(jié)點(diǎn),之后就能正常訪問了,一般生產(chǎn)環(huán)境中 IP 模式用的不多(也可能是我用的不多),如果要用 IP 控制訪問的話,通過防火墻白名單之類的手段即可,這個層面我認(rèn)為不需要 ZK 去管。
3.2.2 World
這個模式應(yīng)該是最常用的(手動狗頭)
我們還是來看一段代碼
ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null); List<ACL>?aclList?=?new?ArrayList<>(); aclList.add(new?ACL(ZooDefs.Perms.READ,?new?Id("world",?"anyone")));?//?區(qū)別是這行 String?path?=?client.create("/abc",?"test".getBytes(),?aclList,?CreateMode.PERSISTENT); System.out.println(path);?//?輸出?/abc client.close();我把 scheme 改成了 World 模式,而 World 模式的 id 取值就是固定的 anyone 不能用其他值,而且我還設(shè)置了 perms 為 R,所以這個節(jié)點(diǎn)只能讀數(shù)據(jù),但不能做其他操作,如果使用 setData 對其進(jìn)行數(shù)據(jù)修改的話也會得到權(quán)限的錯誤
ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null); Stat?stat?=?client.setData("/abc",?"newData".getBytes(),?-1);?//?NoAuth?for?/abc現(xiàn)在再回頭看之前的 ZooDefs.Ids.OPEN_ACL_UNSAFE,其實(shí)就是 ZK 提供的常用的靜態(tài)常量,代表不校驗(yàn)權(quán)限
Id?ANYONE_ID_UNSAFE?=?new?Id("world",?"anyone"); ArrayList<ACL>?OPEN_ACL_UNSAFE?=?new?ArrayList<ACL>(Collections.singletonList(new?ACL(Perms.ALL,?ANYONE_ID_UNSAFE)));3.2.3 Digest
這個就是我們熟悉的用戶名密碼了,還是先上代碼
ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null);List<ACL>?aclList?=?new?ArrayList<>(); aclList.add(new?ACL(ZooDefs.Perms.ALL,?new?Id("digest",?DigestAuthenticationProvider.generateDigest("laoxun:kaixin"))));?//?1String?path?=?client.create("/abc",?"test".getBytes(),?aclList,?CreateMode.PERSISTENT); System.out.println(path); client.close();這個寫法中必須要注意的是 1 處的 username:password 的字符串必須通過 DigestAuthenticationProvider.generateDigest 的方法包裝一下,用這個方法會對傳入的字符串進(jìn)行編碼。
包裝完后 laoxun:kaixin 其實(shí)變成了 laoxun:/xQjqfEf7WHKtjj2csJh1/aEee8=,這個過程如下:
laoxun:kaixin 對整個字符串先進(jìn)行 SHA1 加密
對加密后的結(jié)果進(jìn)行 Base64 編碼
將用戶名和編碼后的結(jié)果拼接
上面的代碼還有一種寫法如下,使用 addAuthInfo 在客戶端上下文中添加權(quán)限信息
ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null); client.addAuthInfo("digest",?"laoxun:kaixin".getBytes());?//?1.? List<ACL>?aclList?=?new?ArrayList<>(); aclList.add(new?ACL(ZooDefs.Perms.ALL,?new?Id("auth",?"")));?//?2.?這里的?Id?是固定寫法 String?path?=?client.create("/abc",?"test".getBytes(),?aclList,?CreateMode.PERSISTENT); System.out.println(path); client.close();這里有兩個改動,在 1 處使用 addAuthInfo 的方法可以在當(dāng)前客戶端的會話中添加 auth 信息,Digest 的 id 取值為 username:password 直接用明文即可,無論是 username 還是 password 都是自定義的。
然后是查詢代碼
ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null); client.addAuthInfo("digest",?"laoxun:kaixin".getBytes());?//?這行如果注釋的話就會報(bào)錯 byte[]?data?=?client.getData("/abc",?false,?null); System.out.println(new?String(data));?//?test不管創(chuàng)建的時(shí)候是何種寫法,查詢的時(shí)候都要使用 addAuthInfo 在會話中添加權(quán)限信息,才能對該節(jié)點(diǎn)進(jìn)行查詢
3.2.4 Super
聽名字就知道這個模式是管理員的模式了,因?yàn)橹皠?chuàng)建的那些節(jié)點(diǎn),如果設(shè)置了用戶名密碼,其他客戶端是無法訪問的,如果該客戶端自己退出了,這些節(jié)點(diǎn)就無法去操作了,所以需要管理員這一個角色來對其進(jìn)行降維打擊。
首先 Super 模式是要開啟的,我這里假設(shè)管理員的用戶名為 HelloZooKeeper,密碼為 niubi,經(jīng)過編碼后就是 HelloZooKeeper:PT8Sb6Exg9YyPCS7fYraLCsqzR8=, 然后需要在服務(wù)端啟動的環(huán)境中指定 zookeeper.DigestAuthenticationProvider.superDigest 配置,參數(shù)就是 HelloZooKeeper:PT8Sb6Exg9YyPCS7fYraLCsqzR8= 即可。
創(chuàng)建節(jié)點(diǎn)假設(shè)還是以 laoxun:kaixin 的模式,然后通過管理員的密碼也能進(jìn)行正常的訪問
ZooKeeper?client?=?new?ZooKeeper("127.0.0.1:2181",?3000,?null); client.addAuthInfo("digest",?"HelloZooKeeper:niubi".getBytes());?//?1. byte[]?data?=?client.getData("/abc",?false,?null); System.out.println(new?String(data));?//?test client.close();這里可以看到 1 處的 Super 模式本質(zhì)上還是 Digest,指定的 scheme 為 digest,然后之后的 id 取值采用的是明文,而非編碼后的格式,切記!
3.3 Permission 匯總表格
我這里列出大部分服務(wù)端提供的操作對應(yīng)的 Permission 權(quán)限:
| create | 父節(jié)點(diǎn)的 CREATE | 創(chuàng)建節(jié)點(diǎn) |
| create2 | 父節(jié)點(diǎn)的 CREATE | 創(chuàng)建節(jié)點(diǎn),同時(shí)返回節(jié)點(diǎn)數(shù)據(jù) |
| createContainer | 父節(jié)點(diǎn)的 CREATE | 創(chuàng)建容器節(jié)點(diǎn) |
| createTTL | 父節(jié)點(diǎn)的 CREATE | 創(chuàng)建帶超時(shí)時(shí)間的節(jié)點(diǎn) |
| delete | 父節(jié)點(diǎn)的 DELETE | 刪除節(jié)點(diǎn) |
| setData | 當(dāng)前節(jié)點(diǎn)的 WRITE | 設(shè)置節(jié)點(diǎn)數(shù)據(jù) |
| setACL | 當(dāng)前節(jié)點(diǎn)的 ADMIN | 設(shè)置節(jié)點(diǎn)的權(quán)限信息 |
| reconfig | 當(dāng)前節(jié)點(diǎn)的 WRITE | 重新設(shè)置一些配置(之后有機(jī)會介紹) |
| getData | 當(dāng)前節(jié)點(diǎn)的 READ | 查詢節(jié)點(diǎn)數(shù)據(jù) |
| getChildren | 當(dāng)前節(jié)點(diǎn)的 READ | 獲取子節(jié)點(diǎn)列表 |
| getChildren2 | 當(dāng)前節(jié)點(diǎn)的 READ | 獲取子節(jié)點(diǎn)列表 |
| getAllChildrenNumber | 當(dāng)前節(jié)點(diǎn)的 READ | 獲取所有子節(jié)點(diǎn)(包含孫子節(jié)點(diǎn))數(shù)量 |
| getACL | 當(dāng)前節(jié)點(diǎn)的 ADMIN 或 READ | 獲取節(jié)點(diǎn)的權(quán)限信息 |
可以看到刪除和創(chuàng)建節(jié)點(diǎn)看的是父節(jié)點(diǎn)的權(quán)限,只有讀寫才是看的自己本身的權(quán)限。另外如果表格中沒有出現(xiàn)的操作可以認(rèn)為不需要 ACL 權(quán)限校驗(yàn),其他要么是只需要客戶端是一個合法的 session 或者本身是一些比較特殊的功能,例如:createSession、closeSession 等。至于關(guān)于 session 的更多內(nèi)容,留到下一篇再講吧~哈哈
3.4 ACL 背后的原理
我們剛剛花了一點(diǎn)篇幅介紹了 ACL 是什么,怎么用?現(xiàn)在深入了解下 ACL 在 ZK 的服務(wù)端底層是怎么去實(shí)現(xiàn)的吧~為了節(jié)約篇幅,這次就直接進(jìn)入猿話講解了。
首先祭出之前的一張圖,喚醒下大家的記憶
圖中權(quán)限部分(藍(lán)色字體)之前的文章直接省略跳過了,沒有進(jìn)行解釋,今天我們就好好講講這個權(quán)限字段。
從圖上也能看到權(quán)限這個字段是直接以數(shù)字(long 類型,64 位的整型數(shù)字)的方式保存在服務(wù)端的節(jié)點(diǎn)中的,而 -1 是一個特殊的值代表不進(jìn)行權(quán)限的校驗(yàn)對應(yīng)的就是之前的 OPEN_ACL_UNSAFE 常量。
而 ACL 權(quán)限無論是創(chuàng)建節(jié)點(diǎn)時(shí)提供的(ACL 參數(shù)是一個 List),還是通過 addAuth 方法提供的(這個方法可以被調(diào)用多次),這兩種設(shè)計(jì)都表示一個客戶端是可以擁有多種權(quán)限的,比如:多個用戶名密碼,多個 IP 地址等等。
ACL 我之前講過是由 3 個部分組成的,即 scheme:id:perms 為了簡潔的表示我會在之后使用該形式去表示一個 ACL。
服務(wù)端會使用兩個哈希表把目前接收到的 ACL 列表和其對應(yīng)的數(shù)字雙向的關(guān)系保存起來,類似這樣(圖中的 ACL 取值是我隨意編造的):
ZK 服務(wù)端會維護(hù)一個從 1 開始的數(shù)字,收到一個新的 ACL 會同時(shí)放入這兩個哈希表(源碼中對應(yīng)的就是兩個 Map,一個是 Map<List<ACL>, Long>,一個是 Map<Long, List<ACL>>),除了這兩個哈希表以外,ZK 服務(wù)端還為每一個客戶端都維護(hù)了一個會話中的權(quán)限信息,該權(quán)限信息就是客戶端通過 addAuth 添加的,但是這個客戶端的權(quán)限信息只保存了 scheme:id 部分,所以結(jié)合以下三個信息就可以對客戶端的本次操作進(jìn)行權(quán)限校驗(yàn)了:
兩個哈希表表示的節(jié)點(diǎn)的信息 scheme:id:perms,可以有多個
客戶端會話上下文中的權(quán)限信息僅 id:perms ,可以有多個
本次操作對應(yīng)的權(quán)限要求,即 3.3 表格中列出的所需權(quán)限
校驗(yàn)的流程如下:
這里額外提一下,校驗(yàn)器是可以自定義的,用戶可以自定義自己的 scheme 以及自己的校驗(yàn)邏輯,需要在服務(wù)端的環(huán)境變量中配置以 zookeeper.authProvider. 開頭的配置,對應(yīng)的值則對應(yīng)一個 class 類全路徑,這個類必須實(shí)現(xiàn) org.apache.zookeeper.server.auth.AuthenticationProvider 接口,而且這個類必須能被 ZK 服務(wù)端加載到,這樣就可以解析自定義的 scheme 控制整個校驗(yàn)邏輯了,這個功能比較高級,我也沒用過,大家就當(dāng)補(bǔ)充知識了解下~
今天我們了解了 Follower 和 Observer 是如何同 Leader 進(jìn)行數(shù)據(jù)同步的,以及 ZK 提供的權(quán)限管理 ACL 究竟是怎么回事,下一篇我們將聊聊 ZK 的 session 管理,客戶端和服務(wù)端之間是怎么保持會話的,以及服務(wù)端不同節(jié)點(diǎn)之間的心跳又是怎么保持的?
最后給文章點(diǎn)個贊吧~什么?你說不想點(diǎn)?
老規(guī)矩,如果你有任何對文章中的疑問也可以是建議或者是對 ZK 原理部分的疑問,歡迎來倉庫中提問,或者閱讀原文來語雀話題討論。
地址:https://github.com/HelloGitHub-Team/HelloZooKeeper
????「點(diǎn)擊關(guān)注」更多驚喜等待你!
總結(jié)
以上是生活随笔為你收集整理的不懂就问:ZooKeeper 集群如何进行数据同步?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个绝好的大型软件ISO下载FTP站!
- 下一篇: 详解设计模式:工厂方法模式