最后一篇:面試遇到 ZK 的問題,橫趟!

作者:HelloGitHub-老荀
本文是 HelloZooKeeper 系列的最后一篇文章,接下來主要聊聊面試中如果被問到 ZooKeeper 的問題如何回答,也可以當(dāng)作學(xué)完本系列的測試。
準(zhǔn)備好了嗎?面試開始嘍~
一、模擬面試
終于來到重頭戲了,本小節(jié)我會從網(wǎng)上找到一些關(guān)于 ZK 的面試題進(jìn)行剖析講解,并且站在面試官的基礎(chǔ)上分析考點(diǎn),相信看完這一節(jié),出去面試再碰到 ZK 相關(guān)的問題你便能披荊斬棘、所向披靡!
我先給大家模擬一個(gè)面試的場景:
面試官:我看你簡歷上用過 ZK,能給我介紹下嗎?你是怎么理解 ZK 的作用呢?
(如果你把百度百科中的定義背給他聽,我只能說 666,千萬別這樣,會被別人當(dāng)成傻子。)
我:我的理解 ZK 是一個(gè)脫離于應(yīng)用的第三方進(jìn)程,類似的數(shù)據(jù)庫,消息隊(duì)列,Redis 等都是扮演這個(gè)角色,擁有一定的數(shù)據(jù)存儲和查詢能力,可以讓我們在現(xiàn)在都是分布式部署的應(yīng)用之間“傳遞”數(shù)據(jù),其次 ZK 支持的回調(diào)通知,讓應(yīng)用可以在一些業(yè)務(wù)場景中感知到數(shù)據(jù)的變化并及時(shí)作出相應(yīng)的反應(yīng)。最后,ZK 本身也支持集群部署具有高可用的特點(diǎn),是一個(gè)可靠的第三方中間件。
面試官:嗯,你剛剛提到了回調(diào)通知,能仔細(xì)跟我聊聊 ZK 是怎么去實(shí)現(xiàn)的嗎?
我:各種編程語言的客戶端都會對這個(gè)回調(diào)通知進(jìn)行抽象,通常需要開發(fā)者聲明一個(gè) callback 的對象,在 Java 的客戶端中這個(gè)接口是 Watcher,ZK 服務(wù)端提供了一些方法,比如 getData、exists 或者最新版本中的 addWatch 都可以用來向 ZK 注冊回調(diào)通知,而向服務(wù)端發(fā)送的回調(diào)通知,只會告訴服務(wù)端我當(dāng)前的這個(gè)路徑需要被通知,服務(wù)端得知后,會在內(nèi)存中記錄下來,路徑和客戶端之間的關(guān)系,客戶端自己也需要記錄下來,路徑和具體回調(diào)的關(guān)系。當(dāng)被訂閱的路徑發(fā)生事件的時(shí)候,各種增刪改吧,服務(wù)端就會從內(nèi)存中的記錄去查看有沒有需要通知的客戶端,有的話會發(fā)送一個(gè)通知的請求給客戶端,客戶端收到通知后,就會從本地的記錄中取出對應(yīng)的回調(diào)對象去執(zhí)行 callback 方法!
(實(shí)際情況,我覺得面試官可能不會讓你一直說下去,應(yīng)該是互相聊的一個(gè)狀態(tài))
面試官:嗯,說得挺詳細(xì),那你剛剛提到的 getData、exists、 addWatch 三種注冊有什么區(qū)別嗎?
我:getData、exists 以及 getChildren 注冊的通知都是一次性的,當(dāng)服務(wù)端通知過一次后,就會刪除內(nèi)存中的記錄,之后如果仍然需要通知的話,客戶端就要去繼續(xù)注冊,而 addWatch 注冊的回調(diào)通知是永久性的,只需要注冊一次可以一直被通知。
面試官:嗯好,你剛剛還提到了 ZK 有一定的數(shù)據(jù)存儲能力,你能說說 ZK 是怎么保存和整理數(shù)據(jù)的嗎?
我:ZK 的數(shù)據(jù)體現(xiàn)在兩部分。
面試官:哦?哪兩部分?
我:內(nèi)存中和磁盤上。
面試官:那你先說說內(nèi)存里 ZK 是怎么存儲數(shù)據(jù)
我:從邏輯上來講,ZK 內(nèi)存中的數(shù)據(jù)其實(shí)是一個(gè)樹形結(jié)構(gòu),從 / 根節(jié)點(diǎn)開始,逐級向下用 / 分割,每一個(gè)節(jié)點(diǎn)下面還可以有多個(gè)子節(jié)點(diǎn),就類似于 Unix 中的目錄結(jié)構(gòu),但在實(shí)際中,ZK 是使用一個(gè) HashMap 去存儲整個(gè)樹形結(jié)構(gòu)的數(shù)據(jù)的,key 是對應(yīng)的全路徑字符串,value 則是一個(gè)節(jié)點(diǎn)對象,包含了節(jié)點(diǎn)的各種信息。
面試官:能說說你覺得為什么要這么設(shè)計(jì)嗎?
(其實(shí)我覺得一般面試官不會這么問,以下回答也是我個(gè)人的猜想)
我:首先 HashMap 查詢速度很快,是 Java 標(biāo)準(zhǔn)庫中一個(gè)非常重要的數(shù)據(jù)結(jié)構(gòu),在許多地方都有用到。ZK 本身并不需要排序或者是范圍求值的操作,所以 HashMap 完全可以滿足查詢的需求。至于為什么邏輯上要設(shè)計(jì)成樹形結(jié)構(gòu),父子節(jié)點(diǎn),這個(gè)可能是因?yàn)檫@個(gè)結(jié)構(gòu)和 Unix 文件系統(tǒng)很像,非常便于理解以及基于路徑進(jìn)行數(shù)據(jù)的分類,而且最新的 ZK 中有一些功能是依賴了父子遞歸這個(gè)特性的(比如 addWatch),如果是普通的 key-value 是無法滿足的。
面試官:嗯好,那你再說說磁盤上 ZK 是怎么存儲數(shù)據(jù)的呢?
我:ZK 在磁盤上規(guī)定了兩種文件類型,一種是 log 文件,一種是 snapshot。log 文件是增量記錄,負(fù)責(zé)對每一個(gè)寫請求進(jìn)行保存,snapshot 文件是全量記錄,是對內(nèi)存的快照。
面試官:ZK 是怎么保證內(nèi)存中的數(shù)據(jù)和磁盤中的數(shù)據(jù)的一致性呢?
我:真正的強(qiáng)一致性,ZK 無法保證。對于每一次的寫請求,ZK 是采取先記錄磁盤再修改內(nèi)存的,所以保證了如果出現(xiàn)意外的話,優(yōu)先記錄磁盤可以盡可能的保證數(shù)據(jù)的完整。如果 ZK 是正常退出的話,也會強(qiáng)制刷磁盤文件和生成 snapshot,保證了一致性,但如果是非正常退出的話,極端情況下的一部分?jǐn)?shù)據(jù)是會丟失的。
面試官:你剛剛也提到了 ZK 本身也可以集群部署的?能多聊一點(diǎn)嗎?
我:ZK 的配置文件 zoo.cfg 中可以配置其他節(jié)點(diǎn)的信息,各個(gè)節(jié)點(diǎn)通過 dataDir 目錄下的 myid 文件進(jìn)行區(qū)分,不同節(jié)點(diǎn)之間可以相互通信,客戶端連上集群中的任意一個(gè)節(jié)點(diǎn)都可以進(jìn)行通信。
面試官:ZK 集群中有幾種不同的角色?你知道嗎?
我:有 Leader、Follower、Observer 三種角色。
面試官:說說他們之間的區(qū)別吧
我:集群中有且只能有一個(gè) Leader,Leader 負(fù)責(zé)對整個(gè)集群的寫請求事務(wù)進(jìn)行提交,在一個(gè)集群選出 Leader 之前是無法對外提供服務(wù)的。Follower 和 Observer 都只能處理讀請求,區(qū)別是 Follower 有投票權(quán)可以參與 Leader 的競選,Observer 無法參與 Leader 的競選。
面試官:那你可以跟我講講,選舉 Leader 依靠哪些信息嗎?
我:每一個(gè)節(jié)點(diǎn)都會維護(hù)三個(gè)最重要的信息:epoch、zxid、myid。epoch 代表選舉的輪次,優(yōu)先比較,如果相同則繼續(xù)比較下一級。zxid 代表本節(jié)點(diǎn)處理過的最大事務(wù) ID,越大代表當(dāng)前節(jié)點(diǎn)經(jīng)手的寫請求越多,知道的也就越多,第二優(yōu)先級比較,如果還相同則比較 myid,myid 整個(gè)集群中不能重復(fù),所以最終一定能分出勝負(fù)。勝利的節(jié)點(diǎn)當(dāng)選 Leader。
(準(zhǔn)確的說,epoch 和 zxid 是一個(gè)字段,一個(gè)記錄在高 32 位,一個(gè)記錄在低 32 位)
面試官:不同節(jié)點(diǎn)之間怎么通信呢?怎么去進(jìn)行選舉?
我:每一個(gè) ZK 節(jié)點(diǎn)在啟動(dòng)的時(shí)候,會通過讀取配置文件中的集群信息,與其他節(jié)點(diǎn)建立 Socket 連接,集群間的通信就是通過這個(gè) Socket。每個(gè)節(jié)點(diǎn)選舉的時(shí)候都把自己認(rèn)為的候選人信息廣播出去,同時(shí)也接收來自其他節(jié)點(diǎn)的候選人信息,通過比較后,失敗的一方會更改自己的候選人信息并重新進(jìn)行廣播,反復(fù)直到某一個(gè)節(jié)點(diǎn)得到半數(shù)以上投票,選舉就完成了。
面試官:不同的節(jié)點(diǎn)角色,在處理讀寫請求上有什么不同嗎?你先聊聊 Leader 吧
我:好滴,Leader 作為集群中的老大,負(fù)責(zé)對收到的寫請求發(fā)起提案 PROPOSAL,告訴其他節(jié)點(diǎn)當(dāng)前收到一個(gè)寫請求,其他節(jié)點(diǎn)收到后,會在本地進(jìn)行歸檔,其實(shí)就是寫入文件輸出流,完畢后會發(fā)送一個(gè) ACK 給 Leader,Leader 統(tǒng)計(jì)到半數(shù)以上的 ACK 之后會再次發(fā)送給其他節(jié)點(diǎn)一個(gè) COMMIT,其他節(jié)點(diǎn)收到 COMMIT 之后就可以修改內(nèi)存數(shù)據(jù)了。讀請求的話不需要提案直接查詢內(nèi)存中的數(shù)據(jù)返回即可。
面試官:那 Follower 或 Observer 呢?
我:他們收到讀請求是一樣的,直接返回本地的內(nèi)存數(shù)據(jù)即可。但是寫請求的話,會將當(dāng)前請求轉(zhuǎn)發(fā)給 Leader,然后由 Leader 去處理,就和之前的流程是一樣的。
面試官:不同的請求 ZK 是如何保證順序呢?
我:這個(gè)順序的保證最終是落實(shí)在一個(gè)先進(jìn)先出的隊(duì)列,優(yōu)先進(jìn)該隊(duì)列的請求會被先處理,所以能保證順序。
面試官:不同的客戶端的請求怎么保證順序呢?A 先發(fā)送了一個(gè)創(chuàng)建節(jié)點(diǎn),在該請求返回之前,B 發(fā)送了一個(gè)查詢該節(jié)點(diǎn),B 會阻塞到 A 執(zhí)行完畢再查詢嗎?還是直接返回查詢不到節(jié)點(diǎn)?
我:B 會直接返回查不到。不同的客戶端之間的順序 ZK 不保證,原因是在底層 ZK 是通過一個(gè) Map 去分別放置不同的客戶端的請求的,不同的客戶端的 key 是不一樣的,而這個(gè) Map 的 value 則是我剛剛提到的先進(jìn)先出的隊(duì)列。所以只有同一個(gè)客戶端的請求能被順序執(zhí)行,不同的客戶端是無法保證的。
面試官:能說說不同的客戶端的 key 是什么嗎?怎么保證不同。
我:每一個(gè)客戶端在連接至 ZK 后會被分配一個(gè) sessionId,這個(gè) sessionId 是通過當(dāng)前時(shí)間戳、節(jié)點(diǎn)的 myid 和一個(gè)遞增特性生成的一個(gè) long 類型字段,可以保證不會重復(fù)。
面試官:說到 session,你知道 ZK 的會話是怎么維持的嗎?
我:你問的是客戶端和服務(wù)端之間的會話嗎?
面試官:是的,你能跟我說說嗎?
我:每一個(gè)客戶端在連接 ZK 的時(shí)候會同時(shí)上報(bào)自己的超時(shí)時(shí)間,加上剛剛的 sessionId,ZK 的服務(wù)端會在本地維護(hù)一個(gè)映射關(guān)系,通過計(jì)算可以計(jì)算出該 sessionId 的超時(shí)時(shí)間,并且 ZK 自己也有一個(gè) tickTime 的配置,通過一個(gè)算法可以將不同客戶端不同超時(shí)間都映射到相同間隔的時(shí)間點(diǎn)上,再將這個(gè)超時(shí)時(shí)間和 sessionId 關(guān)系存起來。
面試官:映射到相同的時(shí)間點(diǎn)上有什么好處嗎?
我:這樣服務(wù)端在啟動(dòng)后,后臺會有一個(gè)線程,通過這個(gè)統(tǒng)一的時(shí)間間隔,取出 session 過期的客戶端,向他們發(fā)送會話過期的消息,極大的節(jié)約了性能。
面試官:客戶端是怎么去更新會話的超時(shí)時(shí)間呢?
我:首先客戶端的每次操作都會刷新這個(gè)超時(shí)時(shí)間,其次客戶端必須設(shè)計(jì)一個(gè) PING 的操作,用于在客戶端空閑的時(shí)候主動(dòng)去刷新會話超時(shí)時(shí)間,防止過期。
面試官:除了客戶端和服務(wù)端之間的會話,還有別的嗎?
我:服務(wù)端和服務(wù)端之間也有心跳,而且服務(wù)端的心跳是由 Leader 主動(dòng)發(fā)起的,向其他節(jié)點(diǎn)發(fā)送 PING 請求,而其他節(jié)點(diǎn)收到 PING 后,需要把本地的會話信息一并發(fā)送給 Leader。
編不下去了,上面一些題具有我強(qiáng)烈的主觀偏好性,我覺得如果面試官是個(gè)菜雞的話,這些問題大部分都問不出來,所以重點(diǎn)是不在于我怎么回答,而是當(dāng)你對背后的原理了然于胸時(shí),自然是神擋殺神,佛擋殺佛。
我說說我認(rèn)為比較重要的幾個(gè)特性:
回調(diào)通知,ZK 其他原理可以不懂,但是怎么用回調(diào)是肯定要知道的。 選舉,ZK 最具特色的一個(gè)屬性,基本都會問一下。 持久化,說清楚兩種文件的區(qū)別。 會話,會話的概念,以及怎么維持。
最后通過一個(gè)模擬面試回答了一下我認(rèn)為 ZK 中比較有特點(diǎn)的面試問題,如果大家對面試問題還有什么疑問記得留言給我噢~必須給你們安排上!
二、網(wǎng)上真題
我大部分題目是網(wǎng)上直接搜的 ZooKeeper面試題(2020最新版)但是過濾了一些太 low 的題目。真題保留如下:
4. ZooKeeper 怎么保證主從節(jié)點(diǎn)的狀態(tài)同步?
我上面說了 Leader 在接受到寫請求后,會發(fā)起提案,然后等待其他節(jié)點(diǎn)的 ACK,這個(gè) ACK 是要求半數(shù)以上通過才能繼續(xù)下去的,所以能收到半數(shù)以上的 ACK 說明集群中的一半以上都已經(jīng)完成了本地磁盤的歸檔,自然是保證了主從之間的數(shù)據(jù)同步。
5. 四種類型的數(shù)據(jù)節(jié)點(diǎn) Znode
我之前的文章中有介紹現(xiàn)在 ZK 中有 7 種節(jié)點(diǎn)類型,關(guān)于新節(jié)點(diǎn)的原理我還沒來得及講,所以他如果這么問了你可以很官方的回答他:
持久節(jié)點(diǎn) 持久順序節(jié)點(diǎn) 臨時(shí)節(jié)點(diǎn) 臨時(shí)順序節(jié)點(diǎn)
他一般后面會接著問兩者的區(qū)別,臨時(shí)節(jié)點(diǎn)會隨著客戶端的會話斷開而自動(dòng)刪除,原理就是在創(chuàng)建臨時(shí)節(jié)點(diǎn)的時(shí)候,服務(wù)端會維護(hù)一個(gè) sessionId 和它對應(yīng)的臨時(shí)節(jié)點(diǎn)路徑列表,當(dāng)關(guān)閉會話時(shí),把這個(gè)列表里的路徑都拿出來一一刪除即可。而順序節(jié)點(diǎn)的區(qū)別就在于 ZK 會自動(dòng)為路徑加上數(shù)字的后綴,僅此而已。
并發(fā)創(chuàng)建時(shí),順序節(jié)點(diǎn)怎么保證后綴數(shù)字唯一呢?
ZK 的請求是放入隊(duì)列里一個(gè)個(gè)處理的,所以其實(shí)并沒有所謂的并發(fā),前一個(gè)請求處理完再處理下一個(gè)請求,自然就能保證后綴數(shù)字的唯一性了。
10. ACL 權(quán)限控制機(jī)制
ZK 將權(quán)限分為兩大類,兩大類又能繼續(xù)細(xì)分:
客戶端的角色權(quán)限 IP 用戶名密碼 world,最寬泛的權(quán)限,也就是沒有權(quán)限 super,特殊的用戶名密碼,相當(dāng)于管理員權(quán)限 節(jié)點(diǎn)的數(shù)據(jù)權(quán)限 Create,創(chuàng)建 Delete,刪除 Read,讀 Write,寫 ACL,讀寫權(quán)限
11. Chroot 特性
chroot 是 ZK 設(shè)計(jì)給客戶端的命名空間隔離,作為不同客戶端的根節(jié)點(diǎn),由客戶端去維護(hù),總的來說就是發(fā)送請求之前把 chroot 的路徑拼接上,再去請求服務(wù)端。chroot 對于服務(wù)端是透明的,完全不知道的。
15. 數(shù)據(jù)同步
Learner 和 Leader 之間同步數(shù)據(jù)是一個(gè)比較漫長和復(fù)雜的過程,總的來說可以大致分為以下步驟:
Learner 上報(bào)自己的信息給 Leader Leader 根據(jù) Learner 信息決定使用何種同步方法 DIFF,直接從最近的 500 個(gè)提案中恢復(fù)數(shù)據(jù),直接發(fā)送提案即可 TRUNC,通常出現(xiàn)于 Learner 是前 Leader,需要降級自己的數(shù)據(jù)達(dá)到和 Leader 一致 SNAP,Leader 直接發(fā)送整個(gè)內(nèi)存快照給 Follower Leader 和 Learner 開始同步 同步完成后開始對外提供服務(wù)

三、配置大全
托大家的福,我把 ZK 的源碼全部(爆肝)瀏覽了一遍,找到了至少 99% 的配置選項(xiàng),ZK 的配置大致可以分為 3 種:
啟動(dòng)命令行傳入的參數(shù) zoo.cfg配置文件中的參數(shù)當(dāng)前環(huán)境變量中的參數(shù)

3.1 命令行參數(shù)
命令行參數(shù)很少,而且沒有對應(yīng)的配置名稱,這里我簡單介紹下:
單機(jī)版只支持兩種形式的命令行傳參
客戶端監(jiān)聽端口加 data 目錄,上一節(jié)源碼調(diào)試中用的就是這一個(gè)形式,例如: 2181 /your/zk/data/path或者只傳一個(gè)參數(shù), zoo.cfg的路徑,例如:/your/zoocfg/path
集群版更簡單只支持 zoo.cfg 的路徑一個(gè)參數(shù)
3.2 zoo.cfg 文件中的配置
我仔細(xì)查看源碼的時(shí)候發(fā)現(xiàn)有些配置實(shí)際作用時(shí)需要計(jì)算又或者是一魚兩吃,被多個(gè)地方使用,所以很難一步到位的講清楚,所以下面的介紹僅供參考,配置項(xiàng)加星號(*)的是我未來打算開篇講解的。
| 配置項(xiàng) | 默認(rèn)值(單位) | 介紹 |
|---|---|---|
| dataDir | /tmp/zookeeper | 存放 snapshot、myid 文件路徑 |
| clientPort | 2181 | 監(jiān)聽客戶端請求端口 |
| tickTime | 2000(毫秒) | 影響客戶端會話檢查間隔、服務(wù)端之間心跳間隔 |
| syncLimit | 5 | tickTime * syncLimit 決定了服務(wù)端心跳超時(shí)時(shí)間 |
| initLimit | 10 | tickTime * initLimit 決定了 ACK 的超時(shí)時(shí)間 |
| dataLogDir | 和 dataDir 一致 | 存放 log 文件路徑 |
| minSessionTimeout | tickTime * 2 | 客戶端的超時(shí)時(shí)間最小值 |
| maxSessionTimeout | tickTime * 20 | 客戶端的超時(shí)時(shí)間最大值 |
| electionAlg | 3 | 選舉算法(1,2 已被廢棄) |
| localSessionsEnabled* | false | 啟用本地會話 |
| localSessionsUpgradingEnabled* | false | 本地會話可以升級成全局會話 |
| clientPortAddress | - | 客戶端的 host 要求,不配置的話可以接受任意發(fā)向 2181 的請求 |
| secureClientPort | - | SSL 安全端口號 |
| secureClientPortAddress | - | SSL 安全 host |
| observerMasterPort* | - | 使 Observer 通過 Follower 去了解集群中的選舉情況 |
| clientPortListenBacklog | 50 | TCP 服務(wù)端用于臨時(shí)存放已完成三次握手的請求的隊(duì)列的最大長度 |
| maxClientCnxns | 60 | 客戶端最大連接數(shù) |
| connectToLearnerMasterLimit | 0 | 決定了 Follower 連接 Leader 的超時(shí)時(shí)間 |
| quorumListenOnAllIPs | false | 服務(wù)端是否接受來自任意 IP 地址的請求 |
| peerType | - | 選項(xiàng) observer / participant,決定節(jié)點(diǎn)角色 |
| syncEnabled | true | Learner 是否需要本地持久化文件 |
| dynamicConfigFile* | - | 動(dòng)態(tài)配置路徑 |
| autopurge.snapRetainCount | 3 | 保留多少個(gè)最新的 snapshot 文件 |
| autopurge.purgeInterval | 0(小時(shí)) | 間隔多久進(jìn)行一次 snapshot 的清理 |
| standaloneEnabled | true | 是否允許單機(jī)模式啟動(dòng) |
| reconfigEnabled* | false | 是否允許動(dòng)態(tài)配置 |
| sslQuorum | false | 集群間是否使用 SSL 通信 |
| portUnification | false | 是否允許不安全連接 |
| sslQuorumReloadCertFiles | false | 啟用密鑰更新時(shí)自動(dòng)加載 |
| quorum.auth.enableSasl | false | 啟用集群間 SASL 鑒權(quán) |
| quorum.auth.serverRequireSasl | false | |
| quorum.auth.learnerRequireSasl | false | |
| quorum.auth.learner.saslLoginContext | QuorumLearner | |
| quorum.auth.server.saslLoginContext | QuorumServer | |
| quorum.auth.kerberos.servicePrincipal | zkquorum/localhost | |
| quorum.cnxn.threads.size | 20 | 集群間異步建立連接線程池最大線程數(shù) |
| jvm.pause.info-threshold.ms | 1000(毫秒) | INFO 輸出暫停統(tǒng)計(jì)閾值 |
| jvm.pause.warn-threshold.ms | 10000(毫秒) | WARN 輸出暫停統(tǒng)計(jì)閾值 |
| jvm.pause.sleep.time.ms | 500(毫秒) | JVM 暫停統(tǒng)計(jì)線程 sleep 間隔 |
| jvm.pause.monitor* | false | 是否啟用 JVM 暫停統(tǒng)計(jì) |
| metricsProvider.className* | DefaultMetricsProvider(全路徑) | 統(tǒng)計(jì)實(shí)現(xiàn)類路徑 |
| multiAddress.enabled | false | |
| multiAddress.reachabilityCheckTimeoutMs | 1000(毫秒) | |
| multiAddress.reachabilityCheckEnabled | true | |
| (以開頭)server.* | 集群配置 | |
| (以開頭)group* | 分組配置 | |
| (以開頭)weight* | 權(quán)重 | |
| (以開頭)metricsProvider.* | 自定義的統(tǒng)計(jì)配置 |
以上就是 3.6.2 中 zoo.cfg 所有的官方配置選項(xiàng)了
3.3 環(huán)境變量配置
Java 程序想要指定環(huán)境變量有兩種方法:
只需要在啟動(dòng)的時(shí)候在后面加上 -DpropertyKey=propertyValue即可ZK 還支持一種簡單的方式就是在 zoo.cfg中直接指定(指定時(shí)不需要寫zookeeper.的前綴)。只要不是在上面 2.2 中 ZK 自己定義的配置項(xiàng)里,ZK 啟動(dòng)的時(shí)候讀取這些配置會自動(dòng)幫他們添加zookeeper.前綴并加入當(dāng)前環(huán)境變量中
如果該配置是 follower.nodelay,就只能用第一種方式添加環(huán)境變量了。
讓我們也來看看 ZK 自己定義了哪些環(huán)境變量配置吧
| 配置項(xiàng) | 默認(rèn)值 | 配置 |
|---|---|---|
| zookeeper.server.realm* | - | 客戶端配置 |
| zookeeper.clientCnxnSocket | ClientCnxnSocketNIO(全路徑) | 客戶端配置,通信的實(shí)現(xiàn)類 |
| zookeeper.client.secure | true | 客戶端配置 |
| zookeeper.request.timeout | 0 | 客戶端配置,異步 API 超時(shí)時(shí)間 |
| zookeeper.server.principal | - | 客戶端配置 |
| zookeeper.sasl.client.username | zookeeper | 客戶端配置 |
| zookeeper.sasl.client.canonicalize.hostname | true | 客戶端配置 |
| zookeeper.disableAutoWatchReset | false | 客戶端配置,會話超時(shí)自動(dòng)清空 watcher |
| zookeeper.sasl.clientconfig | - | |
| zookeeper.sasl.client | true | 啟用 SASL |
| zookeeper.ssl(.quorum).authProvider | x509 | SSL 實(shí)現(xiàn)類,加 quorum 的是服務(wù)端的配置,下同 |
| zookeeper.ssl(.quorum).protocol | TLSv1.2 | |
| zookeeper.ssl(.quorum).enabledProtocols | - | |
| zookeeper.ssl(.quorum).ciphersuites | 根據(jù)不同的 jvm 版本 | |
| zookeeper.ssl(.quorum).keyStore.location | - | |
| zookeeper.ssl(.quorum).keyStore.password | - | |
| zookeeper.ssl(.quorum).keyStore.type | - | |
| zookeeper.ssl(.quorum).trustStore.location | - | |
| zookeeper.ssl(.quorum).trustStore.password | - | |
| zookeeper.ssl(.quorum).trustStore.type | - | |
| zookeeper.ssl(.quorum).context.supplier.class | - | |
| zookeeper.ssl(.quorum).hostnameVerification | true | |
| zookeeper.ssl(.quorum).crl | false | |
| zookeeper.ssl(.quorum).ocsp | false | |
| zookeeper.ssl(.quorum).clientAuth | - | |
| zookeeper.ssl(.quorum).handshakeDetectionTimeoutMillis | 5000(毫秒) | |
| zookeeper.kinit | /usr/bin/kinit | |
| zookeeper.jmx.log4j.disable | false | 禁用 jmx log4j |
| zookeeper.admin.enableServer* | true | 是否啟用 Admin Server |
| zookeeper.admin.serverAddress* | 0.0.0.0 | |
| zookeeper.admin.serverPort* | 8080 | |
| zookeeper.admin.idleTimeout* | 30000 | |
| zookeeper.admin.commandURL* | /commands | |
| zookeeper.admin.httpVersion* | 11 | |
| zookeeper.admin.portUnification | false | |
| zookeeper.DigestAuthenticationProvider.superDigest | - | 管理員賬號密碼 |
| zookeeper.ensembleAuthName | - | |
| zookeeper.requireKerberosConfig | - | |
| zookeeper.security.auth_to_local | DEFAULT | |
| (以開頭)zookeeper.authProvider.* | - | 自定義 scheme 校驗(yàn)規(guī)則 |
| zookeeper.letAnySaslUserDoX | - | |
| zookeeper.SASLAuthenticationProvider.superPassword | - | |
| zookeeper.kerberos.removeHostFromPrincipal | - | |
| zookeeper.kerberos.removeRealmFromPrincipal | - | |
| zookeeper.X509AuthenticationProvider.superUser | - | |
| zookeeper.4lw.commands.whitelist* | - | 四字命令白名單 |
| zookeeper.preAllocSize | 65536 * 1024 | |
| zookeeper.forceSync | yes | |
| zookeeper.fsync.warningthresholdms | 1000(毫秒) | fsync 告警閾值 |
| zookeeper.txnLogSizeLimitInKb | -1(KB) | log 文件大小 |
| zookeeper.datadir.autocreate | true | data 目錄自動(dòng)創(chuàng)建 |
| zookeeper.db.autocreate | true | |
| zookeeper.snapshot.trust.empty | false | 不信任空的 snapshot 文件 |
| zookeeper.snapshot.compression.method | 空字符串 | snapshot 文件壓縮實(shí)現(xiàn) |
| zookeeper.commitProcessor.numWorkerThreads | CPU 核心數(shù) | |
| zookeeper.commitProcessor.shutdownTimeout | 5000(毫秒) | |
| zookeeper.commitProcessor.maxReadBatchSize | -1 | |
| zookeeper.commitProcessor.maxCommitBatchSize | 1 | |
| zookeeper.fastleader.minNotificationInterval | 200(毫秒) | 收集選票超時(shí)時(shí)間(初始) |
| zookeeper.fastleader.maxNotificationInterval | 60000(毫秒) | 收集選票超時(shí)時(shí)間(最大) |
| zookeeper.leader.maxTimeToWaitForEpoch | -1 | |
| zookeeper.leader.ackLoggingFrequency | 1000 | |
| zookeeper.testingonly.initialZxid | - | 初始化 zxid,僅供測試! |
| zookeeper.leaderConnectDelayDuringRetryMs | 100 | Leaner 連接 Leader 超時(shí)時(shí)間 |
| follower.nodelay | true | 設(shè)置 TCP no delay |
| zookeeper.forceSnapshotSync | false | Learner 強(qiáng)制使用 snapshot 和 Leader 進(jìn)行同步 |
| zookeeper.leader.maxConcurrentSnapSyncs | 10 | |
| zookeeper.leader.maxConcurrentDiffSyncs | 100 | |
| zookeeper.observer.reconnectDelayMs | 0(毫秒) | Observer 延遲重連至 Leader |
| zookeeper.observer.election.DelayMs | 200(毫秒) | Observer 延遲開始選舉 |
| zookeeper.observerMaster.sizeLimit | 32 * 1024 * 1024 | |
| zookeeper.electionPortBindRetry | 3 | 選舉端口連接重試次數(shù) |
| zookeeper.tcpKeepAlive | false | Socket keep alive 設(shè)置 |
| zookeeper.cnxTimeout | 5000(毫秒) | Socket 超時(shí)時(shí)間 |
| zookeeper.multiAddress.enabled | false | |
| zookeeper.multiAddress.reachabilityCheckTimeoutMs | 1000(毫秒) | |
| zookeeper.multiAddress.reachabilityCheckEnabled | true | |
| zookeeper.quorumCnxnTimeoutMs | -1 | |
| zookeeper.observer.syncEnabled | true | Observer 是否需要本地歸檔 |
| zookeeper.bitHashCacheSize | 10 | 位圖初始緩存大小 |
| zookeeper.messageTracker.BufferSize | 10 | |
| zookeeper.messageTracker.Enabled | false | |
| zookeeper.pathStats.slotCapacity | 60 | |
| zookeeper.pathStats.slotDuration | 15 | |
| zookeeper.pathStats.maxDepth | 6 | |
| zookeeper.pathStats.sampleRate | 0.1 | |
| zookeeper.pathStats.initialDelay | 5 | |
| zookeeper.pathStats.delay | 5 | |
| zookeeper.pathStats.topPathMax | 20 | |
| zookeeper.pathStats.enabled | false | |
| zookeeper.watcherCleanThreshold | 1000 | |
| zookeeper.watcherCleanIntervalInSeconds | 600 | |
| zookeeper.watcherCleanThreadsNum | 2 | |
| zookeeper.maxInProcessingDeadWatchers | -1 | |
| zookeeper.watchManagerName | WatchManager(全路徑) | |
| zookeeper.connection_throttle_tokens | 0 | |
| zookeeper.connection_throttle_fill_time | 1 | |
| zookeeper.connection_throttle_fill_count | 1 | |
| zookeeper.connection_throttle_freeze_time | -1 | |
| zookeeper.connection_throttle_drop_increase | 0.02 | |
| zookeeper.connection_throttle_drop_decrease | 0.002 | |
| zookeeper.connection_throttle_decrease_ratio | 0 | |
| zookeeper.connection_throttle_weight_enabled | false | |
| zookeeper.connection_throttle_global_session_weight | 3 | |
| zookeeper.connection_throttle_local_session_weight | 1 | |
| zookeeper.connection_throttle_renew_session_weight | 2 | |
| zookeeper.extendedTypesEnabled* | false | 是否啟用 TTL 節(jié)點(diǎn)類型 |
| zookeeper.emulate353TTLNodes* | false | 是否兼容 3.5.3 的 TTL |
| zookeeper.client.portUnification | false | |
| zookeeper.netty.server.outstandingHandshake.limit | -1 | |
| zookeeper.netty.advancedFlowControl.enabled | false | |
| zookeeper.nio.sessionlessCnxnTimeout | 10000(毫秒) | |
| zookeeper.nio.numSelectorThreads | CPU 核心數(shù) / 2 再開方 | |
| zookeeper.nio.numWorkerThreads | CPU 核心數(shù) * 2 | |
| zookeeper.nio.directBufferBytes | 64 * 1024(字節(jié)) | |
| zookeeper.nio.shutdownTimeout | 5000(毫秒) | |
| zookeeper.request_stale_connection_check | true | |
| zookeeper.request_stale_latency_check | false | |
| zookeeper.request_throttler.shutdownTimeout | 10000(毫秒) | |
| zookeeper.request_throttle_max_requests | 0 | |
| zookeeper.request_throttle_stall_time | 100 | |
| zookeeper.request_throttle_drop_stale | true | |
| zookeeper.serverCnxnFactory | NIOServerCnxnFactory(全路徑) | |
| zookeeper.maxCnxns | 0 | |
| zookeeper.snapshotSizeFactor | 0.33 | |
| zookeeper.commitLogCount | 500 | |
| zookeeper.sasl.serverconfig | Server | |
| zookeeper.globalOutstandingLimit | 1000 | |
| zookeeper.enableEagerACLCheck | false | |
| zookeeper.skipACL | no | |
| zookeeper.allowSaslFailedClients | false | |
| zookeeper.sessionRequireClientSASLAuth | false | |
| zookeeper.digest.enabled | true | |
| zookeeper.closeSessionTxn.enabled | true | |
| zookeeper.flushDelay | 0 | |
| zookeeper.maxWriteQueuePollTime | zookeeper.flushDelay / 3 | |
| zookeeper.maxBatchSize | 1000 | |
| zookeeper.intBufferStartingSizeBytes | 1024 | |
| zookeeper.maxResponseCacheSize | 400 | |
| zookeeper.maxGetChildrenResponseCacheSize | 400 | |
| zookeeper.snapCount | 100000 | |
| zookeeper.snapSizeLimitInKb | 4194304(千字節(jié)) | |
| zookeeper.largeRequestMaxBytes | 100 * 1024 * 1024 | |
| zookeeper.largeRequestThreshold | -1 | |
| zookeeper.superUser | - | |
| zookeeper.audit.enable | false | 是否啟用 audit 日志 |
| zookeeper.audit.impl.class | Log4jAuditLogger(全路徑) | audit 日志功能實(shí)現(xiàn)類 |

ZK 的配置還是很多的,有些我這里 TODO 了,以后有機(jī)會和大家詳細(xì)介紹下~而且相當(dāng)一部分的配置 ZK 官方的文檔中已經(jīng)給出了解釋,可以查看 ZK 3.6.2 配置文檔。
我這里還要吐槽下,ZK 中有些配置是用 true 或者 false,有些使用 yes 或者 no,明顯是兩個(gè)(波)人開發(fā)的,這種不應(yīng)該做一個(gè)統(tǒng)一嗎?yes 或 no 真的很多余...
四、系列結(jié)語
感謝你們能看到這里,陪伴這個(gè)系列從開始到現(xiàn)在!這個(gè)項(xiàng)目從有想法立項(xiàng)到之后跟蛋蛋溝通,再到正式開始編寫,到最后我寫下這段結(jié)語,大概經(jīng)歷了三個(gè)多月(你們看到的時(shí)候應(yīng)該是更晚),現(xiàn)在回頭再看之前寫的東西,感慨頗深。

(截圖來自-B 站何同學(xué)的視頻)
如果以我自己的自控能力,這玩意自己搞,搞著搞著可能就涼了,在此感謝蛋蛋給予我的幫助和鼓勵(lì)。關(guān)于 ZK 我的確之前有研究過一段時(shí)間,但是以現(xiàn)在的眼光看,當(dāng)時(shí)的研究其實(shí)也就是皮毛而已(可能現(xiàn)在也還是),很多東西是我這次整理時(shí)現(xiàn)學(xué)的,收獲非常多,最直觀的感受就是,我以后出去面試不會再害怕 ZK 相關(guān)的問題了。
感謝大家這 3 個(gè)月的陪伴,本系列終結(jié)嘍!如果還有什么想學(xué)的開源框架和技術(shù)可以留言告訴我們,后續(xù)繼續(xù)為大家安排免費(fèi)的干貨教程。
最最后,來個(gè)大大的贊吧!


??「點(diǎn)擊關(guān)注」更多驚喜等待你!
閱讀原文點(diǎn)亮 Star 吧
