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