點贊再看,養成習慣,公眾號搜一搜【一角錢小助手】關注更多原創技術文章。
本文 GitHub org_hejianhui/JavaStudy 已收錄,有我的系列文章。
前言
Zookeeper 系列文章最近會繼續輸出,上一篇我們主要講了Zookeeper的主要概念和特性,介紹單機部署和客戶端主要命令操作以及對節點說明。接下來會對Zookeeper 客戶端 API的使用、集群部署與特性、典型使用場景實踐以及ZAB一致性協議核心源碼剖析分幾塊來繼續講一講。
客戶端API常規應用
Zookeeper 提供了 Java 和 C 兩種語言的客戶端。本文學習的是 Java 客戶端。引入最新的maven依賴:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.2</version>
</dependency>
知識點:
- 初始連接
- 創建、查看節點
- 監聽節點
- 設置節點權限
- 第三方客戶端ZkClient
初始連接
常規的客戶端類是 org.apache.zookeeper.ZooKeeper,實例化該類之后將會自動與集群建立連接。構造參數說明如下:
創建、查看節點
創建節點
通過org.apache.zookeeper.ZooKeeper#create()即可創建節點,其參數說明如下:
查看節點
通過org.apache.zookeeper.ZooKeeper#getData()即可創建節點,其參數說明如下:
查看子節點
通過org.apache.zookeeper.ZooKeeper#getChildren()即可獲取子節點,其參數說明如下:
監聽節點
在getData() 與getChildren()兩個方法中可分別設置監聽數據變化和子節點變化。通過設置watch為true,當前事件觸發時會調用zookeeper()構建函數中Watcher.process()方法。也可以添加watcher參數來實現自定義監聽。一般采用后者。
注:所有的監聽都是一次性的,如果要持續監聽需要觸發后在添加一次監聽。
設置節點ACL權限
ACL包括結構為 scheme:``id:``permission
(有關ACL的介紹參照上一篇《Zookeeper特性與節點說明》)
客戶端中由org.apache.zookeeper.data.ACL 類表示,類結構如下:
- ACL
- Id
- scheme // 對應權限模式scheme
- id // 對應模式中的id值
- perms // 對應權限位permission
關于權限位的表示方式:
每個權限位都是一個唯一數字,將其合時通過或運行生成一個全新的數字即可
@InterfaceAudience.Public
public interface Perms {
int READ = 1 << 0;
int WRITE = 1 << 1;
int CREATE = 1 << 2;
int DELETE = 1 << 3;
int ADMIN = 1 << 4;
int ALL = READ | WRITE | CREATE | DELETE | ADMIN;
}
第三方客戶端ZkClient
zkClient 是在Zookeeper客戶端基礎之上封裝的,使用上更加友好。主要變化如下:
- 可以設置持久監聽,或刪除某個監聽
- 可以插入JAVA對象,自動進行序列化和反序列化
- 簡化了基本的增刪改查操作。
<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.11</version>
</dependency>
Zookeeper集群
知識點:
- 集群部署
- 集群角色說明
- 選舉機制
- 數據同步機制
- 四字運維命令
Zookeeper集群的目的是為了保證系統的性能承載更多的客戶端連接設專門提供的機制。通過集群可以實現以下功能:
- 讀寫分離:提高承載,為更多的客戶端提供連接,并保障性能。
- 主從自動切換:提高服務容錯性,部分節點故障不會影響整個服務集群。
半數以上運行機制說明:
集群至少需要三臺服務器,并且強烈建議使用奇數個服務器。因為zookeeper 通過判斷大多數節點的存活來判斷整個服務是否可用。比如3個節點,掛掉了2個表示整個集群掛掉,而用偶數4個,掛掉了2個也表示其并不是大部分存活,因此也會掛掉。
集群部署
配置語法
server.<節點ID>=:<數據同步端口>:<選舉端口>
- 節點ID:服務id手動指定1至125之間的數字,并寫到對應服務節點的 {dataDir}/myid 文件中。
- IP地址:節點的遠程IP地址,可以相同。但生產環境就不能這么做了,因為在同一臺機器就無法達到容錯的目的。所以這種稱作為偽集群。
- 數據同步端口:主從同時數據復制端口,(做偽集群時端口號不能重復)。
- 選舉端口:主從節點選舉端口,(做偽集群時端口號不能重復)。
配置文件示例:
tickTime=2000
dataDir=/var/lib/zookeeper/
clientPort=2181
initLimit=5
syncLimit=2
#以下為集群配置,必須配置在所有節點的zoo.cfg文件中
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888
集群配置流程
- 分別創建3個data目錄用于存儲各節點數據
mkdir data
mkdir data/1
mkdir data/3
mkdir data/3
- 編寫myid文件
echo 1 > data/1/myid
echo 3 > data/3/myid
echo 2 > data/2/myid
- 編寫配置文件
conf/zoo1.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=data/1
clientPort=2181
#集群配置
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
conf/zoo2.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=data/2
clientPort=2182
#集群配置
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
conf/zoo3.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=data/3
clientPort=2183
#集群配置
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
4.分別啟動
./bin/zkServer.sh start conf/zoo1.cfg
./bin/zkServer.sh start conf/zoo2.cfg
./bin/zkServer.sh start conf/zoo3.cfg
5.分別查看狀態
./bin/zkServer.sh status conf/zoo1.cfg
Mode: follower
./bin/zkServer.sh status conf/zoo2.cfg
Mode: leader
./bin/zkServer.sh status conf/zoo3.cfg
Mode: follower
檢查集群復制情況
- 分別連接指定節點
zkCli.sh 后加參數-server 表示連接指定IP與端口。
./bin/zkCli.sh -server 127.0.0.1:2181
./bin/zkCli.sh -server 127.0.0.1:2182
./bin/zkCli.sh -server 127.0.0.1:2183
- 任意節點中創建數據,查看其它節點已經同步成功。
注意: -server參數后同時連接多個服務節點,并用逗號隔開 127.0.0.1:2181,127.0.0.1:2182
集群角色說明
zookeeper 集群中總共有三種角色,分別是leader(主節點)follower(子節點) observer(次級子節點)
observer配置:
只要在集群配置中加上observer后綴即可,示例如下:
server.3=127.0.0.1:2889:3889:observer
選舉機制
通過 ./bin/zkServer.sh status <zoo配置文件> 命令可以查看到節點狀態
./bin/zkServer.sh status conf/zoo1.cfg
Mode: follower
./bin/zkServer.sh status conf/zoo2.cfg
Mode: leader
./bin/zkServer.sh status conf/zoo3.cfg
Mode: follower
可以發現中間的2182 是leader狀態.其選舉機制如下圖:
投票機制說明
- 第一輪投票全部投給自己
- 第二輪投票給myid比自己大的相鄰節點
- 如果得票超過半數,選舉結束。
Zookeeper默認采用了FastLeaderElection
算法,且投票數大于半數則勝出的機制;
選舉觸發
當集群中的服務器出現以下兩種情況時會進行Leader的選舉
- 服務節點初始化啟動(全新集群選舉)
- 半數以上的節點無法和Leader建立連接
當節點初始起動時會在集群中尋找Leader節點,如果找到則與Leader建立連接,其自身狀態變化follower或observer。如果沒有找到Leader,當前節點狀態將變化LOOKING,進入選舉流程。
在集群運行期間如果有follower或observer節點宕機只要不超過半數并不會影響整個集群服務的正常運行。但如果leader宕機,將暫停對外服務,所有follower將進入LOOKING 狀態,進入選舉流程。
全新集群選舉:
全新集群選舉是新搭建起來的,沒有數據ID和邏輯時鐘數據影響集群的選舉;
假設當前集群有5臺服務器,它們的編號為 1 – 5,按編號依次啟動 Zookeeper 服務:
- 服務器1啟動,首先會給自己投1票,由于其他機器還沒有啟動,所以它還無法接收到投票信息的反饋;因此服務器1一直處于 LOOKING 狀態;
- 服務器2啟動,首先給自己投1票;發起投票對比,這是它會與服務器1交換結果,由于服務器2的編號大,所以服務器2勝出,此時服務器1將票投給服務器2,服務器2的票數沒有大于集群半數,所以服務器1、2兩個的狀態都是 LOOKING;
- 服務器3啟動,首先給自己投1票;與之前的服務器1、2交換結果,由于服務器3的編號最大所以服務器3勝出,那么服務器1、2都會將票投給服務器3,此時服務器3的票數為3,正好大于半數(5/2);所以服務器3成為 leader,服務1、2成為 follower
- 服務器4啟動,首先給自己投1票;與之前的服務器1、2、3交換結果,發現已經產生了 leader,則服務器4直接為 follower;
- 服務器5啟動和服務器4一樣,也成為 follower;
非全新集群選舉
對于正常運行的 Zookeeper 集群,一旦中途有服務器宕機(leader),則需要重新選舉時,選舉的時候就要引入服務器ID、數據ID(zxid)和邏輯時鐘(投票次數,每一輪投票晚上,投票次數都會增加)。
- 首先統計邏輯時鐘是否相同,邏輯時鐘小,則說明途中存在宕機問題。因此該數據不完整,那么選舉結果被忽略,重新投票選舉;
- 統一邏輯時鐘之后,對比數據ID值,數據ID反映了數據的新舊程度,因此數據ID大的節點勝出;
- 如果邏輯時鐘和數據ID值都相同,那么就去比較數據ID,數據ID大的勝出;
簡單的講,非全新集群選舉時是優中選優,保證 Leader 是 Zookeeper 集群中數據最完整、最可靠的一臺服務器。
在集群運行期間如果有follower或observer節點宕機,只要不超過半數并不會影響整個集群服務的正常運行。但如果leader宕機,將暫停對外服務,所有follower將進入LOOKING 狀態,進入選舉流程。
數據同步機制
Zookeeper 的數據同步是為了保證各節點中數據的一致至性,同步時涉及兩個流程,一個是正常的客戶端數據提交,另一個是集群某個節點宕機在恢復后的數據同步。
客戶端寫入請求
寫入請求的大致流程是,收leader接收客戶端寫請求,并同步給各個子節點。如下圖:
但實際情況要復雜的多,比如client 它并不知道哪個節點是leader 有可能寫的請求會發給follower ,由follower在轉發給leader進行同步處理
客戶端寫入流程說明:
- client向zk中的server發送寫請求,如果該server不是leader,則會將該寫請求轉發給leader server,leader將請求事務以proposal形式分發給follower;
- 當follower收到收到leader的proposal時,根據接收的先后順序處理proposal;
- 當Leader收到follower針對某個proposal過半的ack后,則發起事務提交,重新發起一個commit的proposal
- Follower收到commit的proposal后,記錄事務提交,并把數據更新到內存數據庫;
- 當寫成功后,反饋給client。
服務節點初始化同步
在集群運行過程當中如果有一個follower節點宕機,由于宕機節點沒過半,集群仍然能正常服務。當leader 收到新的客戶端請求,此時無法同步給宕機的節點。造成數據不一致。為了解決這個問題,當節點啟動時,第一件事情就是找當前的Leader,比對數據是否一致。不一致則開始同步,同步完成之后在進行對外提供服務。
如何比對Leader的數據版本呢?這里通過ZXID事物ID來確認。比Leader就需要同步。
ZXID說明
ZXID是一個長度64位的數字,其中低32位是按照數字遞增,任何數據的變更都會導致,低32位的數字簡單加1。高32位是leader周期編號,每當選舉出一個新的leader時,新的leader就從本地事物日志中取出ZXID,然后解析出高32位的周期編號,進行加1,再將低32位的全部設置為0。這樣就保證了每次新選舉的leader后,保證了ZXID的唯一性而且是保證遞增的。
思考題:
如果leader 節點宕機,在恢復后它還能被選為leader嗎?
四字運維命令
ZooKeeper響應少量命令。每個命令由四個字母組成。可通過telnet或nc向ZooKeeper發出命令。
這些命令默認是關閉的,需要配置4lw.commands.whitelist來打開,可打開部分或全部示例如下:
#打開指定命令
4lw.commands.whitelist=stat, ruok, conf, isro
#打開全部
4lw.commands.whitelist=*
安裝Netcat工具,已使用nc命令
#安裝Netcat 工具
yum install -y nc
#查看服務器及客戶端連接狀態
echo stat | nc localhost 2181
注: mac os 10.13 后就沒有內置telnet函數,要新安裝:
brew install telnet
常用命令
echo stat|nc 127.0.0.1 2181 查看哪個節點被選擇作為follower或者leader
echo ruok|nc 127.0.0.1 2181 測試是否啟動了該Server,若回復imok表示已經啟動。
echo dump| nc 127.0.0.1 2181 ,列出未經處理的會話和臨時節點。
echo kill | nc 127.0.0.1 2181 ,關掉server
echo conf | nc 127.0.0.1 2181 ,輸出相關服務配置的詳細信息。
echo cons | nc 127.0.0.1 2181 ,列出所有連接到服務器的客戶端的完全的連接 / 會話的詳細信息。
echo envi |nc 127.0.0.1 2181 ,輸出關于服務環境的詳細信息(區別于 conf 命令)。
echo reqs | nc 127.0.0.1 2181 ,列出未經處理的請求。
echo wchs | nc 127.0.0.1 2181 ,列出服務器 watch 的詳細信息。
echo wchc | nc 127.0.0.1 2181 ,通過 session 列出服務器 watch 的詳細信息,它的輸出是一個與 watch 相關的會話的列表。
echo wchp | nc 127.0.0.1 2181 ,通過路徑列出服務器 watch 的詳細信息。它輸出一個與 session 相關的路徑。
命令列表
- conf:3.3.0中的新增功能:打印有關服務配置的詳細信息。
- cons:3.3.0中的新增功能:列出了連接到該服務器的所有客戶端的完整連接/會話詳細信息。包括有關已接收/已發送的數據包數量,會話ID,操作等待時間,最后執行的操作等信息。
- crst:3.3.0中的新增功能:重置所有連接的連接/會話統計信息。
- dump:列出未完成的會話和臨時節點。這僅適用于領導者。
- envi:打印有關服務環境的詳細信息
- ruok:測試服務器是否以非錯誤狀態運行。如果服務器正在運行,它將以imok響應。否則,它將完全不響應。響應“ imok”不一定表示服務器已加入仲裁,只是服務器進程處于活動狀態并綁定到指定的客戶端端口。使用“ stat”獲取有關狀態仲裁和客戶端連接信息的詳細信息。
- srst:重置服務器統計信息。
- srvr:3.3.0中的新功能:列出服務器的完整詳細信息。
- stat:列出服務器和連接的客戶端的簡要詳細信息。
- wchs:3.3.0中的新增功能:列出有關服務器監視的簡要信息。
- wchc:3.3.0中的新增功能:按會話列出有關服務器監視的詳細信息。這將輸出具有相關監視(路徑)的會話(連接)列表。請注意,根據手表的數量,此操作可能會很昂貴(即影響服務器性能),請小心使用。
- dirs:3.5.1中的新增功能:以字節為單位顯示快照和日志文件的總大小
- wchp:3.3.0中的新增功能:按路徑列出有關服務器監視的詳細信息。這將輸出具有關聯會話的路徑(znode)列表。請注意,根據手表的數量,此操作可能會很昂貴(即影響服務器性能),請小心使用。
- mntr:3.4.0中的新增功能:輸出可用于監視集群運行狀況的變量列表。
Zookeeper 系列文章,敬請關注,下篇講講典型使用場景實踐以及ZAB一致性協議!