ZooKeeper 分布式過程協(xié)同技術詳解
簡介
分布式系統(tǒng)
分布式系統(tǒng)是同時跨越多個物理主機, 獨立運行的多個軟件所組成的系統(tǒng)锻梳。
-
分布式系統(tǒng)中的兩種進程通信方式
- 方式一: 直接通過網絡進行信息交換
- 方式二: 讀寫某些共享存儲
分布式系統(tǒng)中的兩種進程通信方式 -
分布式系統(tǒng)需注意以下幾點
- 消息延遲: (如網絡擁堵)箭券,導致消息不是按順序到達
- 處理器性能: 導致消息延遲處理
- 時鐘偏移: 導致系統(tǒng)作出錯誤決策
Zookeeper使用場景
- 適用場景
- 選舉集群主節(jié)點
- 檢測崩潰
- 存儲集群元數(shù)據(jù)
- 服務發(fā)現(xiàn)
- 數(shù)據(jù)分片
- 不適用場景
- 海量數(shù)據(jù)存儲
主-從應用
主節(jié)點失效
- 備份主節(jié)點: 在主節(jié)點崩潰時接管主節(jié)點的角色, 進行故障轉移,并恢復到舊主節(jié)點崩潰時的狀態(tài)
- 腦裂: 誤判主節(jié)點崩潰(如消息延遲), 而導致集群中出現(xiàn)兩個主節(jié)點
從節(jié)點失效
通信故障
- 某臺機器網絡連接斷開, 導致斷開的機器和網絡中的某一從節(jié)點執(zhí)行同一任務
主-從應用的需求
綜上所述, 我們對主-從應用的需求如下:
- 主節(jié)點選舉
- 崩潰檢測
- 組成員關系管理
- 元數(shù)據(jù)管理
了解Zookeeper
Zookeeper基礎
Zookeeper數(shù)據(jù)樹結構示例
節(jié)點稱為znode節(jié)點
API
znode節(jié)點是數(shù)據(jù)操作的最小單元, Zookeeper不允許局部寫入或讀取zonde節(jié)點的數(shù)據(jù)
API | 說明 |
---|---|
create /path data |
創(chuàng)建一個名為/path 的znode節(jié)點,并包含數(shù)據(jù)data create -e /master "master1.example.com:2223" (-e表示臨時節(jié)點)create -s /tasks/task- "cmd" (-s創(chuàng)建有序節(jié)點) |
delete /path |
刪除名為/path 的zonde |
exists /path |
檢查是否存在名為/path 的節(jié)點 |
set /path data |
設置名為/path 的zonde的數(shù)據(jù)為data |
get /path |
返回名為/path 節(jié)點的數(shù)據(jù)信息 |
getChildren /path |
返回/path 節(jié)點的所有子節(jié)點列表 |
ls /path |
查看/path 下的節(jié)點, 例如ls / 查看根節(jié)點下有哪些節(jié)點 |
znode的不同類型
新建znode時, 還需要指定該節(jié)點的類型(mode),不同的類型決定了znode節(jié)點的行為方式
節(jié)點類型 | 重點說明 | 刪除時機 | 案例 |
---|---|---|---|
持久節(jié)點(persistent) | 只能通過調用delete刪除 | 保存<font color=red>從</font>節(jié)點的任務分配情況,即使分配任務的<font color=red>主</font>節(jié)點節(jié)點崩潰了 | |
臨時節(jié)點(ephemeral) | <font color=red>Zookeeper不允許臨時節(jié)點有子節(jié)點(因為臨時節(jié)點會自動刪除)</font> | 1.創(chuàng)建該節(jié)點的客戶端崩潰或斷開連接 2.主動調用delete刪除 |
檢測主/從節(jié)點崩潰時,需要每個節(jié)點建立臨時節(jié)點 |
有序節(jié)點(sequential) | 創(chuàng)建時,一個序號會被追加到路徑后,例如創(chuàng)建路徑為task/task- ,則最終路徑為task/task-1
|
名稱唯一,可直觀查看znode的創(chuàng)建順序 |
上面3中類型排列組合可知, znode節(jié)點有4種類型:
- 持久節(jié)點
- 臨時節(jié)點
- 持久有序節(jié)點
- 臨時有序節(jié)點
監(jiān)視與通知
一種常見的輪詢問題
基于通知機制
監(jiān)視點
通知機制是單次出發(fā)的操作, 即設置一次監(jiān)視點, 只會發(fā)出一次變動通知。通過每次設置監(jiān)視點前讀取Zookeeper的狀態(tài)來防止錯過任何變更疑枯。
使用版本控制
Zookeeper架構
架構總覽
Zookeeper服務器運行在兩種模式下:
模式 | 說明 |
---|---|
獨立模式(standalone) | 即單機模式 |
仲裁模式(quorum) | 即一組集群 |
仲裁
仲裁模式下,Zookeeper復制集群種所有服務器的數(shù)據(jù)樹荆永。但如果讓一個客戶端等所有服務器完成數(shù)據(jù)保存后再繼續(xù)废亭,便會導致無法接受的延遲問題。Zookeeper的解決方案是使用法定人數(shù)屁魏,即只要法定個數(shù)的服務器完成數(shù)據(jù)保存后滔以,客戶端即可繼續(xù)。
會話(Session)
- 當客戶端通過某一個特定語言套件來創(chuàng)建一個Zookeeper句柄時氓拼,它就會通過服務建立一個會話(Session)你画。然后通過TCP協(xié)議與服務器通信
- 當會話無法與當前連接的服務器繼續(xù)通信時,Zookeeper客戶端就可能透明地將會話轉移到另一個服務器上
- 同一個會話提供了順序保障桃漾,即請求會先進先出
開始使用Zookeeper
安裝
- 下載Zookeeper安裝包并解壓.
tar -xvzf zookeeper-3.4.8.tar.gz
- 創(chuàng)建配置文件(conf目錄下有樣例配置)坏匪。
mv conf/zoo_sample.cfg conf/zoo.cfg
. - 最好將data移除
/tmp
目錄, 防止Zookeeper填滿根分區(qū), 修改zoo.cfg中的配置dataDir=/tmp/zookeeper
- 啟動Zookeeper服務器.
bin/zkServer.sh start
(bin/zkServer.sh start-foreground
可在屏幕上看到服務器日志輸出) - 啟動Zookeeper客戶端.
bin/zkCli.sh
日志 -
命令使用方式
生命周期和會話
- 客戶端與服務器因網絡超時斷開連接后, 客戶端仍然保持connecting狀態(tài)。因為對聲明會話超時負責的是服務端撬统,而不是客戶端适滓。
- 會話超時設置為時間t
端 | 時間 | 行為 |
---|---|---|
服務端 | t | 經過時間t后,服務端接收不到這個會話的任何消息恋追,服務端會聲明會話過期 |
客戶端 | t/3 | 經過t/3時間未收到服務端消息凭迹,則主動向服務器發(fā)送心跳消息 |
客戶端 | 2t/3 | 仍未收到服務端消息,則開始尋找其它的服務器,此時它還有t/3的時間去尋找 |
- 仲裁模式(集群模式)下苦囱,Zookeeper需要傳遞可用的服務器列表給客戶端嗅绸,告知客戶端可以連接的服務器信息并選擇一個進行連接
- 斷開連接后,客戶端不能連接到一個比自己狀態(tài)舊的服務器撕彤,Zookeeper處理方式見下圖
仲裁模式
我們可以在一臺機器上運行多個Zookeeper鱼鸠,來構建仲裁模式(集群模式)
- 配置如下
# 在數(shù)據(jù)文件存儲目錄下建立文件myid,并寫入內容1(zookeeper id),便可定義該zookeeper服務器ID。 命令: echo 1>/tmp/z1/data/myid
# 這樣做的好處是, 部署到不同機器的zookeeper不需要改配置文件
dataDir=/tmp/z1/data
# 客戶端連接的端口號
clientPort=2182
# /root/dev/research/zookeeper-3.4.8/conf/zoo.cfg
# server.{n即zookeeper服務器id}=[ip地址或hostname]:[集群通信端口號]:[群首選舉端口號]
server.1=127.0.0.1:2222:2223
server.2=127.0.0.1:3333:3334
server.3=127.0.0.1:4444:4445
-
實踐(Zookeeper部署示意圖)
Zookeeper部署示意圖- 最初只啟動z3羹铅,從日志可以看出z3會瘋狂嘗試連接到其它服務器蚀狰,然后失敗。
Zookeeper部署示意圖-
然后再啟動z2职员,此時達到了法定人數(shù)麻蹋。從日志可以看出,z3被選為了leader廉邑,z2作為follower被激活
Zookeeper-3日志Zookeeper-2日志
-
使用zkCli.sh訪問集群
./zkCli.sh -server 127.0.0.1:2182,127.0.0.1:2181
(如果希望客戶端只連接上海機房集群,可以只輸入上海zookeeper服務器ip)隨機連接集群中一臺機器zookeeper可以實現(xiàn)簡單的負載均衡哥蔚,但是無法按權重來進行負載倒谷。
-
現(xiàn)在我們來實現(xiàn)一個原語:通過zookeeper實現(xiàn)鎖
通過zookeeper實現(xiàn)鎖
一個主-從模式例子的實現(xiàn)
主節(jié)點角色
從節(jié)點,客戶端(略)糙箍,見上面分析圖
# 連接Zookeeper
[root@iZ11bh64aveZ bin]# ./zkCli.sh -server 127.0.0.1:2182,127.0.0.1:2181
# -e表示臨時節(jié)點
[zk: 127.0.0.1:2182,127.0.0.1:2181(CONNECTED) 0] create -e /master "master1.example.com:2223"
Created /master
[zk: 127.0.0.1:2182,127.0.0.1:2181(CONNECTED) 1] ls /
[zookeeper, master]
[zk: 127.0.0.1:2182,127.0.0.1:2181(CONNECTED) 2] get /master
master1.example.com:2223
cZxid = 0x10000000c
ctime = Wed Feb 22 23:05:34 CST 2017
mZxid = 0x10000000c
mtime = Wed Feb 22 23:05:34 CST 2017
pZxid = 0x10000000c
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x25a6118b1220004
dataLength = 24
numChildren = 0
# /master已經存在渤愁,無法重復創(chuàng)建
[zk: 127.0.0.1:2182,127.0.0.1:2181(CONNECTED) 4] create -e /master "master1.example.com:2223"
Node already exists: /master
# 對/master節(jié)點設置監(jiān)視點
[zk: 127.0.0.1:2182,127.0.0.1:2181(CONNECTED) 5] stat /master true
cZxid = 0x10000000c
ctime = Wed Feb 22 23:05:34 CST 2017
mZxid = 0x10000000c
mtime = Wed Feb 22 23:05:34 CST 2017
pZxid = 0x10000000c
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x25a6118b1220004
dataLength = 24
numChildren = 0
任務隊列化(有序節(jié)點的應用)
可以使用有序節(jié)點來達到任務隊列化的目的。這樣做有兩個好處:
- 序列號指定了任務被隊列化的順序深夯;
- 可以通過很少的工作為任務創(chuàng)建基于序列號的唯一路徑抖格。
使用Zookeeper的API
建立Zookeeper會話
句柄
- Zookeeper的API圍繞句柄(handle)而構建,每個API調用都需要傳遞handle咕晋。它代表與Zookeeper之間的一個會話
句柄 | 說明 |
---|---|
只要會話活著雹拄,句柄就仍然有效 | 例如會話在斷開后被遷移到另一臺服務器,由于此時會話還有效掌呜,因此句柄仍然有效 |
如果句柄關閉滓玖,則終止會話 | 如果句柄關閉,Zookeeper客戶端會告知服務器終止這個會話 |
- | 如果Zookeeper發(fā)現(xiàn)客戶端死掉质蕉,就會使該會話無效势篡。如果客戶端嘗試使用該會話的那個句柄連接,那么服務器會通知客戶端該會話已失效模暗,這個句柄進行的任何操作都會返回錯誤 |
創(chuàng)建Zookeeper句柄API
- API
/**
connectString: 連接zookeeper的主機和端口信息禁悠,如:127.0.0.1:2182,127.0.0.1:2181
sessionTimeout: 超時時間,單位ms。一般設置為5-10秒兑宇。sessionTimeout=15s表示Zookeeper如果有15s無法和客戶端通信碍侦,則會終止該會話。
watcher:Watcher對象(需自己實現(xiàn)Watcher接口)隶糕,用于監(jiān)控會話(如建立/失去連接瓷产,Zookeeper數(shù)據(jù)變化,會話過期等事件)
*/
Zookeeper(String connectString,int sessionTimeout, Watcher watcher)
- 實現(xiàn)一個Watcher
package org.apache.zookeeper.book.luyunfei;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
/**
* Created by luyunfei on 24/02/2017.
*/
public class Master implements Watcher {
@Override
public void process(WatchedEvent watchedEvent) {
// 監(jiān)控到連接斷開時,自己不要關閉會話后再啟動一個新的會話枚驻,這樣會增加系統(tǒng)負載拦英,并導致更長時間的中斷, Zookeeper客戶端庫會處理
System.out.println("監(jiān)控: "+watchedEvent);
}
public static void main(String[] args) throws InterruptedException, IOException {
String connectString = "139.196.53.21:2182,139.196.53.21:2181";
// 注意:如果服務器發(fā)現(xiàn)請求的會話超時時間太長或太短,服務器會調整會話超時時間
int sessionTimeout = 10 * 1000;
Master obj = new Master();
ZooKeeper zooKeeper = new ZooKeeper(connectString, sessionTimeout, obj);
Thread.sleep(600000);
//客戶端可以調用Zookeeper.close()方法主動結束會話. 否則即便客戶端斷開連接测秸,會話也不會立即消失,服務端要等會話超時以后才結束會話
zooKeeper.close();
}
}
## 執(zhí)行上述代碼,啟動客戶端
2017-02-24 22:49:29,359 - INFO - [main:ZooKeeper@438] - Initiating client connection, connectString=139.196.53.21:2182,139.196.53.21:2181 sessionTimeout=10000 watcher=org.apache.zookeeper.book.luyunfei.Master@3e6fa38a
2017-02-24 22:49:29,399 - INFO - [main-SendThread(139.196.53.21:2182):ClientCnxn$SendThread@1032] - Opening socket connection to server 139.196.53.21/139.196.53.21:2182. Will not attempt to authenticate using SASL (unknown error)
2017-02-24 22:49:29,488 - INFO - [main-SendThread(139.196.53.21:2182):ClientCnxn$SendThread@876] - Socket connection established to 139.196.53.21/139.196.53.21:2182, initiating session
2017-02-24 22:49:29,649 - INFO - [main-SendThread(139.196.53.21:2182):ClientCnxn$SendThread@1299] - Session establishment complete on server 139.196.53.21/139.196.53.21:2182, sessionid = 0x25a7098967b0000, negotiated timeout = 10000
監(jiān)控: WatchedEvent state:SyncConnected type:None path:null
## 此時關閉Zookeeper服務器(/tmp/z3/zookeeper-3.4.8/bin/zkServer.sh stop), 程序監(jiān)控到Disconnected事件
2017-02-24 22:57:31,009 - INFO - [main-SendThread(139.196.53.21:2182):ClientCnxn$SendThread@1158] - Unable to read additional data from server sessionid 0x25a7098967b0000, likely server has closed socket, closing socket connection and attempting reconnect
監(jiān)控: WatchedEvent state:Disconnected type:None path:null
....
....
## 此時又開啟Zookeeper服務器(/tmp/z3/zookeeper-3.4.8/bin/zkServer.sh start), 客戶端自動重新連接服務
2017-02-24 22:59:02,559 - INFO - [main-SendThread(139.196.53.21:2182):ClientCnxn$SendThread@1299] - Session establishment complete on server 139.196.53.21/139.196.53.21:2182, sessionid = 0x25a7098967b0000, negotiated timeout = 10000
監(jiān)控: WatchedEvent state:SyncConnected type:None path:null
- 當看到Disconnected事件時灾常,千萬不要自己創(chuàng)建一個新的Zookeeper句柄來重新連接服務霎冯,Zookeeper客戶端庫會負責在通信恢復時為你重新連接服務。
- 假設有3臺Zookeeper服務(法定人數(shù)為2),一個服務器故障不會導致服務終端钞瀑∩蜃玻客戶端會很快收到Disconnected事件,之后便為SyncConnected事件
- 再說一遍: 不要自己試著管理Zookeeper客戶端連接(如監(jiān)控到連接斷開時,自己不要關閉會話后再啟動一個新的會話雕什,這樣會增加系統(tǒng)負載缠俺,并導致更長時間的中斷)显晶。Zookeeper客戶端庫會處理,它會很快重建會話壹士,以便將影響最小化磷雇。
Zookeeper管理接口
Zookeeper有兩種管理接口: JMX和命令,先介紹命令管理接口(stat,dump命令)
# stat命令
[root@iZ11bh64aveZ bin]# telnet 127.0.0.1 2181
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
#(手動敲入stat)
stat
Zookeeper version: 3.4.8--1, built on 02/06/2016 03:18 GMT
Clients:
/127.0.0.1:60538[0](queued=0,recved=1,sent=0)
Latency min/avg/max: 0/0/0
Received: 2
Sent: 1
Connections: 1
Outstanding: 0
Zxid: 0x300000002
Mode: leader
Node count: 4
Connection closed by foreign host.
[root@iZ11bh64aveZ bin]#
- dump命令
# dump命令
[root@iZ11bh64aveZ bin]# telnet 127.0.0.1 2181
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
# (手動敲入dump)
dump
SessionTracker dump:
Session Sets (4):
0 expire at Fri Feb 24 23:27:28 CST 2017:
0 expire at Fri Feb 24 23:27:32 CST 2017:
0 expire at Fri Feb 24 23:27:34 CST 2017:
1 expire at Fri Feb 24 23:27:38 CST 2017:
0x25a70a1e2b80000
ephemeral nodes dump:
Sessions with Ephemerals (0):
Connection closed by foreign host.
[root@iZ11bh64aveZ bin]#
# 此時停止客戶端躏救,過了一段時間才會出現(xiàn)如下信息(即會話過一段時間后才消失唯笙。這是因為直到會話超時時間過了以后,服務端才會結束這個會話)
# 最好在客戶端停止時盒使,會話立即消失崩掘。客戶端可以調用Zookeeper.close()方法主動結束會話
[root@iZ11bh64aveZ bin]# telnet 127.0.0.1 2181
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
dump
SessionTracker dump:
Session Sets (0):
ephemeral nodes dump:
Sessions with Ephemerals (0):
Connection closed by foreign host.
[root@iZ11bh64aveZ bin]#
給上面的Master程序增加管理權(創(chuàng)建主節(jié)點)
創(chuàng)建znode節(jié)點
創(chuàng)建Zookeeper節(jié)點API create()----同步調用版本
要重視對異常的處理, 例如: 如果create執(zhí)行創(chuàng)建主節(jié)點成功了少办,此時苞慢,客戶端還不知道自己成功創(chuàng)建了主節(jié)點,然后客戶端死掉了英妓。這個時候就不會再有其它任何進程稱為主節(jié)點進程了挽放。
/**
String path: 節(jié)點path
byte[] data: 節(jié)點數(shù)據(jù), 只能傳入字節(jié)數(shù)組類型的數(shù)據(jù). 如果沒有數(shù)據(jù),則可傳new byte[0]
List<ACL> acl: ACL策略鞋拟。例如
1. ZooDefs.Ids.OPEN_ACL_UNSAFE為所有人提供了所有權限(該策略在不可信環(huán)境下非常不安全)
2. 其它策略
CreateMode: 節(jié)點類型(枚舉骂维,如:臨時節(jié)點,臨時有序節(jié)點贺纲,持久節(jié)點航闺,持久有序節(jié)點)
拋出兩類異常:
1. KeeperException
1.1 ConnectionLossException: KeeperException異常的子類,發(fā)生于客戶度與Zookeeper服務端失去連接時,通常由網絡原因導致
注意:雖然Zookeeper會自己處理重建連接猴誊,但是我們必須知道未決請求的狀態(tài)(是否已經處理/需重新請求)
2. InterruptedException:發(fā)生于客戶端線程調用了Thread.interrupt, 通常是因為應用程序部分關閉潦刃,但還在其他相關應用的方法中使用
處理方式:1. 向上直接拋出異常,讓程序最外層捕獲懈叹,然后主動關閉zk句柄(zookeeper.stop()),然后做清理善后
2. 如果句柄沒有關閉乖杠,則可能會有其它異步執(zhí)行的后續(xù)操作,這種情況做清理善后會比較棘手
*/
create(String path, byte[] data, List<ACL> acl, CreateMode createMode) throws KeeperException, InterruptedException
創(chuàng)建Zookeeper節(jié)點舉例(創(chuàng)建節(jié)點)
// 創(chuàng)建zonde節(jié)點代碼
public void runForMaster(ZooKeeper zooKeeper) throws KeeperException, InterruptedException {
Random random = new Random(this.hashCode());
String serverId = Integer.toHexString(random.nextInt());
// 創(chuàng)建節(jié)點
zooKeeper.create(
"/master", // 節(jié)點path
serverId.getBytes(), // 節(jié)點數(shù)據(jù), 這里定義節(jié)點數(shù)據(jù)為代表該客戶端的隨機ID
ZooDefs.Ids.OPEN_ACL_UNSAFE,// 該節(jié)點的安全策略
CreateMode.EPHEMERAL// 節(jié)點類型(枚舉澄成,如:臨時節(jié)點胧洒,臨時有序節(jié)點,持久節(jié)點墨状,持久有序節(jié)點)
);
}
getData API----同步調用版本
/**
path: znode節(jié)點路徑
watch:
boolean類型版本api: 表示是否想要監(jiān)聽后續(xù)的數(shù)據(jù)變更,true則使用創(chuàng)建句柄時所設置的Watcher對象監(jiān)控事件
Watcher類型版本api: 表示使用新Watcher對象監(jiān)視變更
Stat: getData方法可填充節(jié)點的元數(shù)據(jù)信息卫漫,stat表示新填充的元數(shù)據(jù)信息,對象定義如下
public class Stat implements Record {
private long czxid;
private long mzxid;
private long ctime;
private long mtime;
private int version;
private int cversion;
private int aversion;
private long ephemeralOwner;
private int dataLength;
private int numChildren;
private long pzxid;
}
返回值: znode節(jié)點數(shù)據(jù)的字節(jié)數(shù)組
*/
byte[] getData(String path, boolean watch, Stat stat)
byte[] getData(String path, Watcher watcher, Stat stat)
// 我們通過Stat結構肾砂,可以獲得當前主節(jié)點創(chuàng)建的時間
Stat stat = new Stat();
byte[] data = zooKeeper.getData("/master", false, stat);
Date startDate = new Date(stat.getCtime());//ctime單位為秒
getData 舉例(申請成為主節(jié)點)
String serverId = Integer.toHexString(new Random(this.hashCode()).nextInt());
boolean isLeader = false;
ZooKeeper zooKeeper;
// 創(chuàng)建zonde節(jié)點(申請成為主節(jié)點)
public void runForMaster() throws KeeperException, InterruptedException {
while (true) {
try {
// 創(chuàng)建/master節(jié)點列赎,如果執(zhí)行成功,本客戶端將會成為主節(jié)點
zooKeeper.create(
"/master",
serverId.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL
);
} catch (KeeperException.NodeExistsException e) {
// 主節(jié)點已經存在了镐确,不再申請成為主節(jié)點
isLeader = false;
break;
} catch (KeeperException.ConnectionLossException e) {
// 這里留空包吝,以便在網絡異常情況下饼煞,繼續(xù)while循環(huán)來申請成為主節(jié)點
}
// 如果已經存在主節(jié)點,則跳出while循環(huán)诗越,不再申請成為主節(jié)點
if (checkMaster()) break;
}
}
// 檢查是否存在主節(jié)點
public boolean checkMaster() throws KeeperException, InterruptedException {
while (true) {
try {
Stat stat = new Stat();
// 通過獲茸┣啤/master節(jié)點數(shù)據(jù),并和自身的serverId比較掺喻,來檢查活動主節(jié)點
byte[] data = zooKeeper.getData("/master", false, stat);
isLeader = new String(data).equals(serverId);
// 已經存在主節(jié)點了芭届,返回true,告知runForMaster()不要再進行while循環(huán)了
return true;
} catch (KeeperException.NoNodeException e) {
return false;
} catch (KeeperException.ConnectionLossException e) {
}
}
}
getData API----異步調用版本
雖然是異步的, 但是順序能夠得到保證感耙。因為為了保持順序褂乍,只會有一個單獨的線程按照接收順序處理響應包
/**
StringCallback: 提供回調方法的對象,它通過傳入的上下文參數(shù)(Object ctx)來獲取數(shù)據(jù)即硼。它實現(xiàn)StringCallback接口
Object ctx: 用戶指定上下文信息逃片,最終會傳入回調函數(shù)中
*/
void create(String path, byte[] data, List<ACL> acl, CreateMode createMode, StringCallback cb, Object ctx)
/**
以下演示了如何創(chuàng)建一個StringCallback回調方法的對象。
注意: 因為只有一個單獨的線程處理所有回調調用只酥,因此一個回調函數(shù)阻塞褥实,會導致后續(xù)所有回調函數(shù)都阻塞。
所以要避免在回調函數(shù)里做集中操作(即while(true){...})或阻塞操作(如調用synchronized方法).以便其被快速處理
*/
AsyncCallback.StringCallback cb = new AsyncCallback.StringCallback() {
/**
rc : 返回碼裂允,0: 正常返回损离;其它表示對應的KeeperException異常
path: create()傳入的參數(shù),即znode路徑
ctx: create()傳入的參數(shù)绝编,即上下文
name: znode節(jié)點最終的名稱(一般path和那么值一樣僻澎。單如果采用CreateMode.SEQUENTIAL有序模式,則name為最終名稱)
*/
public void processResult(int rc, String path, Object ctx, String name) {
switch (Code.get(rc)) {
case CONNECTIONLOSS:
checkMaster();
break;
case OK:
state = MasterStates.ELECTED;
takeLeadership();
break;
case NODEEXISTS:
state = MasterStates.NOTELECTED;
masterExists();
break;
default:
state = MasterStates.NOTELECTED;
LOG.error("Something went wrong when running for master.",
KeeperException.create(Code.get(rc), path));
}
LOG.info("I'm " + (state == MasterStates.ELECTED ? "" : "not ") + "the leader " + serverId);
}
};
AsyncCallback.StringCallback cb = new AsyncCallback.StringCallback() {
public void processResult(int rc, String path, Object ctx, String name) {
}
};
getData API----異步調用版本
異步版本比同步版本簡單, 沒有while(true){...}十饥,防止一直卡在本線程導致其它線程無法執(zhí)行
void checkMaster() {
zk.getData("/master", false, masterCheckCallback, null);
}
設置元數(shù)據(jù)
這里和create API沒啥不同窟勃,有個技巧見代碼注釋
public void bootstrap(){
createParent("/workers", new byte[0]);
createParent("/assign", new byte[0]);
createParent("/tasks", new byte[0]);
createParent("/status", new byte[0]);//new byte[0] 表示傳入空數(shù)據(jù)
}
void createParent(String path, byte[] data){
zk.create(path,
data,
Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT,
createParentCallback,
data);// A----這里將data作為上下文傳入到回調函數(shù)中,以便在下面CONNECTIONLOSS時再遞歸調用createParent函數(shù)逗堵。
}
StringCallback createParentCallback = new StringCallback() {
public void processResult(int rc, String path, Object ctx, String name) {
switch (Code.get(rc)) {
case CONNECTIONLOSS:
/*
* Try again. Note that registering again is not a problem.
* If the znode has already been created, then we get a
* NODEEXISTS event back.
*/
createParent(path, (byte[]) ctx);//呼應A
break;
case OK:
LOG.info("Parent created");
break;
case NODEEXISTS:
LOG.warn("Parent already registered: " + path);
break;
default:
LOG.error("Something went wrong: ",
KeeperException.create(Code.get(rc), path));
}
}
};
注冊從節(jié)點
略
Zookeeper會嚴格維護執(zhí)行順序秉氧,并提供強有力的有序保障。然而在多線程下還需要小心面對順序問題:比方說遇到異常重發(fā)請求時蜒秤,重發(fā)請求可能排在其他線程請求后面了汁咏。
處理狀態(tài)變化(監(jiān)視點+通知)
引言
輪詢(不好的方式)
例如監(jiān)視主節(jié)點崩潰, 假設備份節(jié)點以50ms/次的頻率積極輪詢, 那么輪詢的請求量= (50ms*備份節(jié)點個數(shù)n)/次。雖然Zookeeper可以很輕松處理這些請求作媚,但主節(jié)點崩潰的情況很少發(fā)生梆暖,這些請求其實是多余的。
監(jiān)視點(改進方式)
通過監(jiān)視點(watch)掂骏,客戶端可以對指定的znode節(jié)點注冊一個通知請求,在發(fā)生變化時就會收到一個單次的通知厚掷。
如何檢測主節(jié)點崩潰
- 主節(jié)點在zookeeper樹上創(chuàng)建一個臨時節(jié)點,來標示主節(jié)點鎖。
- 備份節(jié)點注冊一個監(jiān)視器來監(jiān)視這個鎖是否存在
- 如果主節(jié)點崩潰山宾,這個臨時節(jié)點就會自動刪除择吊;同時備份節(jié)點會受到通知,它們就可以開始進行主節(jié)點選舉县匠。
案例:單次觸發(fā)器
術語 | 說明 |
---|---|
事件(event) | 一個znode節(jié)點執(zhí)行了更新操作 |
監(jiān)視點(watch) | 如:znode節(jié)點被賦值,或被刪除 |
通知(notification) | 注冊了監(jiān)視點的應用客戶端收到的事件報告消息 |
- 監(jiān)視點是一次性的觸發(fā)器: 例如,對/master設置監(jiān)視點后,/master節(jié)點數(shù)據(jù)被修改了了芒划,客戶端會收到一次通知, 但下次數(shù)據(jù)再變化,客戶端就不會再收到通知了欧穴。除非再次設置監(jiān)視點民逼。
- 當一個zookeeper客戶端與服務端A斷開連接后(注意,并非會話過期涮帘,會話過期會刪除監(jiān)視點)拼苍,連接到服務端B〉饔В客戶端會給B發(fā)送未觸發(fā)的監(jiān)視點列表疮鲫。B會檢查列表中注冊監(jiān)視點后是否有變化,如有弦叶,通知客戶端俊犯。沒有,注冊監(jiān)視點
事件丟失
- 事件是可能丟失的伤哺,但并不是問題燕侠。
- 事件丟失: 一個事件A發(fā)生,通知客戶端默责。在客戶端繼續(xù)添加監(jiān)視點之前贬循,又發(fā)生了事件B,此時沒有監(jiān)視點桃序,事件B不會發(fā)送通知杖虾,于是丟失
- 為什么不是問題: 下一個監(jiān)視點設置的時候,可以查看最新的狀態(tài)(終態(tài))媒熊。中間的變化不用關心
批量通知
多個事件分攤到一個通知上具有積極作用奇适,比如進行高頻率的更新操作時,就比較輕量了芦鳍。
如何設置監(jiān)視點
Zookeeper的API中所有讀操作: getData,getChildren,exists均可以設置監(jiān)視點
// API
public byte[] getData(final String path,Watcher watcher(自定義監(jiān)視點),Stat stat)
public byte[] getData(String path,boolean watch(true=使用默認監(jiān)視點),Stat stat)
// API中watcher的實現(xiàn)類
public class Master implements Watcher {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("監(jiān)控: "+watchedEvent);
}
}
WatchedEvent的數(shù)據(jù)結構
public class WatchedEvent {
private final KeeperState keeperState;// 會話狀態(tài),枚舉類型: Disconnected,SyncConnected,AuthFailed,ConnectedReadOnly,SaslAuthenticated,Expired
private final EventType eventType;// 事件類型, 枚舉: NodeCreated,NodeDeleted,NodeDataChanged,NodeChildrenChanged,None(無事件發(fā)生嚷往,而是Zookeeper的會話狀態(tài)發(fā)生了變化)
private String path;//事件類型不是None時,返回一個znode路徑
}
- 監(jiān)視點有兩種類型
- 數(shù)據(jù)監(jiān)視點: exists,getData可以設置數(shù)據(jù)監(jiān)視點
- 子節(jié)點監(jiān)視點: getChildren可以設置子節(jié)點監(jiān)視點柠衅,這種監(jiān)視點只在znode子節(jié)點創(chuàng)建或刪除時才被觸發(fā)
事件類型 | 可設置該事件監(jiān)視點的API |
---|---|
NodeCreated | exists |
NodeDeleted | exists,getData |
NodeDataChanged | exists,getData |
NodeChildrenChanged | getChildren |
- 監(jiān)視點一旦設置就無法移除皮仁,如果要移除,只有兩個方法
- 觸發(fā)這個監(jiān)視點
- 使其會話被關閉或過期
API的普遍模型
以下以exists為例,介紹API的普遍模型贷祈,該框架使用非常廣泛
// 異步調用方式
zk.exists("/myZnode",myWatcher,existsCallback,null應用上下文趋急,會作為callback參數(shù));
Watcher myWatcher = new Watcher(){
public void process(WatchedEvent e){
// Watcher的實現(xiàn)
}
}
StatCallback existsCallback = new StatCallback(){
public void processResult(int rc, String path, Object ctx, Stat stat){
//exists的回調對象
}
}
故障處理
故障發(fā)生點
- Zookeeper服務
- 網絡
- 應用
可恢復的故障場景
場景 | 說明 |
---|---|
正常情況 | 客戶端在從Zookeeper獲得響應時,可以非呈铺埽肯定它與其它客戶端獲得的響應均保持一致 |
客戶端與Zookeeper服務連接丟失時 | 客戶端會使用Disconnected事件和ConnectionLossException異常來表示自己無法了解當前的系統(tǒng)狀態(tài) |
Disconnected事件和ConnectionLossException異常
處理
- 客戶端會不斷嘗試重新連接另一個Zookeeper服務器呜达,直到重新建立了會話
- 一旦會話重新建立,Zookeeper會做如下事情:
- 產生一個SyncConnected事件粟耻,并開始處理請求
- 注冊之前已經注冊過的監(jiān)視點查近,對期間發(fā)生的變更產生監(jiān)視點事件
典型原因
一個典型原因是因為Zookeeper服務器故障
影響和處理
連接丟失時,如果客戶端還存在進行中的請求要處理挤忙,就會產生很大的影響霜威,開發(fā)中要謹慎處理
- 連接丟失時,雖然會返回ConnectionLossException和CONNECTIONLOSS返回碼饭玲,但客戶端無法通過這些異常和返回碼來判斷請求是否已經被處理
- 客戶端開發(fā)時侥祭,需要正確處理連接丟失的情況,以保證最小的系統(tǒng)開銷和損壞茄厘。而不是簡單處理(如重啟客戶端)
舉例
- 以下是出現(xiàn)兩個群首的例子矮冬,如果開發(fā)不仔細,系統(tǒng)中會出現(xiàn)兩個群首次哈。
- 因此胎署,當一個進程收到Disconnected事件時,在重新連接之前窑滞,進程需要掛起群首的操作琼牧。
- 客戶端失去連接一段時間,客戶端開發(fā)者通常會選擇關閉會話哀卫。有一點需要注意巨坊,由于此時已失去連接了,Zookeeper服務端并不會感知客戶端關閉了會話此改,服務端依然會等待會話過期時間過去后才聲明會話已過期
FAQ
當網絡中斷持續(xù)一段時間趾撵,客戶端連接另一個服務器可能會發(fā)生一個長延時。為什么客戶端沒有網絡中斷的某一個時刻(如2倍會話超時時間)作出判斷共啃,而一直連接那個超時的服務器呢占调?
- Zookeeper將這種策略問題的決定權交給開發(fā)者處理(即程序員自己寫代碼處理),而不是客戶端API移剪。因為開發(fā)者可以很容易實現(xiàn)關閉句柄這種策略
- Zookeeper集群集體停機導致的網絡中斷究珊,時間凍結,然而恢復后纵苛,會話超時時間被重置剿涮,因此會出現(xiàn)較長的延遲言津。對于Zookeeper集群故障,客戶端是不需要做額外處理的幔虏。
- 總之纺念,對于網絡中斷的情況,開發(fā)者應該根據(jù)自己的實際情況選擇自己關閉會話想括,而不是依賴客戶端API(它才不管你)
Disconnected導致的錯過監(jiān)視點事件的特殊情況
- Disconnected恢復后,Zookeeper客戶端庫會在會話重新建立時烙博,建立所有已經存在的監(jiān)視點
- 然后客戶端會發(fā)送監(jiān)視點列表和最后已知的zxid(最終狀態(tài)的時間戳)瑟蜈,服務器會接受并檢查監(jiān)視點列表中各znode節(jié)點的修改時間戳,如果晚于zxid渣窜,服務器就會觸發(fā)這個監(jiān)視點
- 每個Zookeeper操作都完全符合以上邏輯铺根。除了exists,因為exists可以在一個不存在的節(jié)點上設置監(jiān)視點
- 因此exists會導致如下一種錯過監(jiān)視點事件的特殊情況乔宿,解決方案是:盡量避免監(jiān)視一個znode節(jié)點的創(chuàng)建事件位迂。如果一定要監(jiān)視創(chuàng)建事件,也要選擇監(jiān)視存活時期更長的znode節(jié)點
Disconnected恢復后详瑞,自動重連處理的危害
- 通常掂林,Disconnected恢復后,有些Zookeeper封裝庫通過簡單的補發(fā)命令自動處理連接丟失的故障
- 有時候會導致錯誤的結果坝橡,比如:在建立/leader節(jié)點時泻帮,出現(xiàn)Disconnected。重連后计寇,再執(zhí)行建立/leader節(jié)點就可能出錯锣杂,因為其它節(jié)點可能已經建立了/leader
不可恢復的故障
Zookeeper丟棄會話的情況
- 會話過期
- 已認證的會話無法再次與Zookeeper完成認證
丟棄會話的影響
- 會話被意外丟棄,會導致臨時性節(jié)點丟失
處理方式
- 最簡單的方法就是終止進程并重啟番宁。這樣可以通過一個新的會話重新初始化自己的狀態(tài)
- 如果不重啟元莫,首先必須要清楚與舊會話關聯(lián)的應用內部的狀態(tài)信息,然后重新初始化新的狀態(tài)
從不可恢復故障自動恢復的危害
現(xiàn)在Zookeeper句柄與會話之間是一對一的關系蝶押,早先的Zookeeper不是踱蠢,所以會出現(xiàn)這種情況: 舊句柄會話是群首,新句柄使用同一個會話操作只有群首有權操作的數(shù)據(jù)播聪。
群首選舉和外部資源
- 只要客戶端與Zookeeper進行任何交互操作朽基,Zookeeper都會保持同步。注意前提是: 客戶端與Zookeeper進行了交互
- 如果客戶端與Zookeeper沒有交互离陶,則Zookeeper無法保證上述視圖的一致性
- 沒有交互的情況舉例:
- 客戶端服務器過載稼虎,導致無法及時發(fā)送心跳,進而導致會話超時招刨,客戶端卻仍然以為自己仍然是主節(jié)點
- 一個很普遍的資源中心化管理方法(用來確保一致性): Zookeeper確保每次只有一個主節(jié)點可以獨占訪問一個外部資源
協(xié)調外部資源的一個棘手問題
下圖展示了在協(xié)調外部資源(如DB)時出現(xiàn)的一個很棘手的問題霎俩。(超載,時鐘偏移都可能導致該問題)
解決方案:
確保應用不會在超載或時鐘偏移的環(huán)境中運行: 小心監(jiān)控系統(tǒng)負載;良好設計的多線程應用打却;時鐘同步程序保證時鐘同步
-
使用一種名為隔離的技巧杉适,分布式系統(tǒng)常常使用這種方法用于確保資源的獨占訪問(即連接外部資源時使用一個版本號,如果外部資源已經接收到更高版本的隔離符號柳击,則請求或連接就會被拒絕)猿推。具體做法是:使用Stat結構成員變量czixd,它表示創(chuàng)建該節(jié)點時的zxid捌肴,zxid為唯一的單調遞增序列號蹬叭,所以可以使用czxid作為一個隔離符號
Zookeeper內部原理
請求,事物,標識符
請求類型
請求類型 | 舉例 | 處理方式 | 說明 |
---|---|---|---|
只讀請求 | exists,getData,getChildren | Zookeeper服務器會在本地(群首或從節(jié)點)處理 | 因為在本地處理,所以Zookeeper在處理以只讀請求為主要負載時状知,性能會很高秽五。增加更多服務器到集群,可大幅提高整體處理能力饥悴,處理更多讀請求 |
寫請求 | create,delete,setData | 將會被轉發(fā)給群首處理 | - |
事務
以對/z節(jié)點setData為例, 事務= 新的數(shù)據(jù)字段+ 新的版本號坦喘。處理該事務時,服務端會用事務中的數(shù)據(jù)信息替換/z節(jié)點中原有的數(shù)據(jù)信息西设,并會用事務中的版本號更新該節(jié)點版本號(而不是簡單對節(jié)點版本號+1)
- 一個事務就是一個單位瓣铣,不會被其它事務干擾。(早期济榨,Zookeeper通過服務器單線程來保證事務順序執(zhí)行坯沪,后來增加了多線程支持)
- 事務還具有冪等性。即可以對同一個事務執(zhí)行多次
標識符
群首產生了一個事務擒滑,就會為該事務分配一個標識符腐晾,稱之為Zookeeper會話ID(zxid), 通過zxid可以判斷執(zhí)行順序
- zxid = 時間戳+計數(shù)器
群首選舉
群首為集群中的服務器選擇出來的一個服務器,并會一直被集群認可丐一。群首的作用: 對客戶端寫請求進行排序, 并轉換為事務
單個服務器啟動過程
選主投票
-
群首選舉通知消息(leader election notifications)
- 當一個服務器進入LOOKING狀態(tài)藻糖,就會向集群中每個服務器發(fā)送一個通知
- 一個服務器發(fā)送的投票信息為: (1,5), 表示該服務器sid為1,最近執(zhí)行的事務zxid為5
- 出于群首選舉的目的库车,zxid只有一個數(shù)組洋满,而其他協(xié)議中阵漏,zxid則還有時間戳epoch和計數(shù)器
投票- 當一個服務器接收到仲裁數(shù)量的服務器發(fā)來的投票都一樣時裆泳,就表示群首選舉成功蝗柔;如果被選舉的群首為自己,則該服務器行使群首角色,否則就成為追隨者并嘗試連接群首(不一定連接成功,一旦連接成功,則會進行狀態(tài)同步,在同步完成后,追隨者才可以處理新的請求)
選主過程
- Zookeeper實現(xiàn)選主的Java類為QuorumPeer,超時時間定義在FastLeaderElection中(200ms), 如果要自定義選主實現(xiàn)算法,可實現(xiàn)quorum包中的Election接口
Zab協(xié)議
- Zab: Zookeeper原子廣播協(xié)議(Zookeeper Atomic Broadcast protocol)
- 在接收到一個寫請求后锋爪,追隨者會將請求轉發(fā)給群首,群首將探索性地執(zhí)行該請求,并將執(zhí)行結果以事務的方式對狀態(tài)進行廣播
- 引入Zab協(xié)議,以幫助服務器確認一個事務是否已經提交
Zab對下列事項提供保障 | 說明 |
---|---|
如果群首按順序廣播事務T1,T2为迈。那么每個服務器在提交T2前保證T1已經提交完成 | 保證事務在服務器之間傳送順序一致 |
如果某個服務器按照T1,T2的順序提交事務,所有其他服務器也必然會在T2前提交T1 | 保證服務器不會跳過任何事務 |
多群首問題
- Zookeeper使用時間戳來解決多群首問題(集群中出現(xiàn)多個群首),zxid第一個元素為時間戳信息。
- 阻止系統(tǒng)中同時出現(xiàn)多個群首是非常困難的捏题,因此廣播協(xié)議不能基于以上假設循狰。為了解決這個問題关炼,Zab協(xié)議提供了以下保障
Zab對下列事項提供保障 | 說明 |
---|---|
群首確保提交完所有之前的時間戳內需要提交的事務色鸳,才開始廣播新事務 | 1.群首不會馬上處于活動狀態(tài)吏砂,直到確保仲裁數(shù)量的服務器認可這個群首新的時間戳 2.一個時間戳的最初狀態(tài)氛雪,必須包含之前已經提交的事務井氢,或已經被其他服務器接受约急,但尚未提交完成的事務 3.如果一個提案消息時間戳為4奴饮,但它在新群首處理第一個提案消息(時間戳6)之前沒有提交,那么舊提案將永遠不會被提交 |
任何時間點危彩,都不會出現(xiàn)兩個被仲裁支持的群首 | 保證服務器不會跳過任何事務 |
- 群首發(fā)生重疊的情況: [舊群首失效----新群首生效]期間谒府,仲裁服務器中有一個服務器還未追隨新群首盛龄,因此它接收舊群首的提案A匿值,其它的服務器追隨新群首,而未接收提案A喷楣。此時事務A依然會提交叽讳。因為新群首在生效前會學習舊仲裁服務器之前接受的所有提議,并保證他們不會再接收來自舊群首的提議
- 時間戳發(fā)生轉換時渐逃,Zookeeper使用兩種不同的方式來更新追隨者優(yōu)化這個過程
- 追隨者之后群首不多:群首只需發(fā)送缺失的事務點
- 滯后太多:發(fā)送群首擁有的最新完整快照
觀察者
- 服務器類型:群首雹食,追隨者埠通,觀察者
- 觀察者码撰,追隨者共同點是:提交來自群首的提議轩性;不同點是:觀察者不參與選舉
- 引入觀察者的目的:
- 提高讀請求的可擴展性。寫操作吞吐率與仲裁者數(shù)量成反比狠鸳,引入追隨者能提高讀服務器數(shù)量揣苏,而不改變仲裁者數(shù)量,從而不降低寫操作吞吐率件舵,提高讀操作效率
- 進行跨多個數(shù)據(jù)中心部署卸察。引入觀察者后,更新請求能夠以高吞吐率和低延遲的方式在一個數(shù)據(jù)中心進行铅祸,接下來再傳播到異地的其他數(shù)據(jù)中心得到執(zhí)行
服務器的構成
- 服務器:群首坑质,追隨者,觀察者根本上都是服務器临梗。
- 處理流水線: 處理一個請求的一系列過程
- 請求處理器:處理流水線上的每一個過程就是請求處理器
本地存儲
SyncRequestProcessor處理器用于在處理提議時寫入日志和快照涡扼。在接受一個提議時,一個服務器(追隨者或群首服務器)就會將提議的事務持久化到日志中盟庞,該事務日志保存在服務器的本地磁盤中
日志和磁盤的使用
為了寫日志更快吃沪,使用一下兩種手段
- 組提交: 即事務先放在內存隊列中,定期flush到磁盤什猖。
SyncRequestProcessor.run()
- 補白: 在文件中預分配磁盤存儲塊票彪,而不是每次寫到文件結尾時,文件系統(tǒng)都需要分配一個新的存儲塊
- 為避免受到系統(tǒng)中其他寫操作干擾不狮,強烈推薦將事務日志寫入到一個獨立的磁盤降铸,與 操作系統(tǒng)文件,快照文件分開
快照
- 快照是Zookeeper數(shù)據(jù)樹的拷貝副本摇零,每個服務器會經常以序列化整個樹的方式來提取快照推掸,并保存到文件中
- 服務器進行快照時不需要進行協(xié)作,也不需要暫停處理請求。
- 如果快照過程中终佛,數(shù)據(jù)樹由于處理請求而發(fā)生變化俊嗽,那么稱這樣的快照是模糊的,它不能反映出任意給定的時間點數(shù)據(jù)樹的準確狀態(tài)
服務器與會話
- 獨立模式下
- 單個服務器跟蹤和維護所有會話(SessionTracker類 和 SessionTrackerImpl類)
- 仲裁模式下
- 群首服務器跟蹤和維護會話(和獨立模式的服務器一樣铃彰,都是SessionTracker類 和 SessionTrackerImpl類)
- 追隨者服務器僅僅是簡單地把客戶端連接的會話信息轉發(fā)給群首(LearnerSessionTracker類)
-
為保證會話存活绍豁,服務器需要接收會話的心跳信息,心跳的形式有:
- 新的請求
- 顯示的ping消息(LearnerHandler.run())
- 以上兩種情況,服務器通過更新會話的過期時間來出發(fā)會話活躍(SessionTrackerImpl.touchSession())
- 仲裁模式下牙捉,群首服務器發(fā)送一個ping給追隨者們竹揍,追隨者返回上次ping之后的一個session列表
-
管理會話過期
-
Zookeeper將"會話過期(名詞)"維護在過期隊列(expiry queue)中,它分為多個bucket
- 使用bucket模式來管理的主要原因是減少讓會話過期這項工作的系統(tǒng)開銷邪铲。以適當?shù)募毩6葋頇z查會話過期
-
服務器與監(jiān)視點
客戶端
客戶端庫主要有兩個類
- Zookeeper類
- 寫客戶端代碼時芬位,需要實例化該類來建立一個會話。
- 一旦建立了一個會話带到,Zookeeper會使用服務端生成的會話標識符(SessionTrackerImpl類)來關聯(lián)該會話
- ClientCnxn類
- 管理連接到server的Socket連接
- 該類維護一個可連接的Zookeeper服務器列表昧碉,在連接斷掉時無縫切換到其他服務器(使用同一個會話)
- 重連會重置所有監(jiān)視點到新服務器(ClientCnxn.SendThread.primeConnection()), 可使用disableAutoWatchReset禁用(默認開啟)
序列化
- 使用場合
- 網絡傳輸
- 磁盤保存
- Zookeeper使用了Hadoop中的Jute來做序列化(org.apache.jute)
- Jute定義文件為zookeeper.jute,它包含所有的消息定義和文件記錄
運行Zookeeper
配置Zookeeper服務器
- Zookeeper配置文件為: zoo.cfg, 很多參數(shù)也可以通過java的系統(tǒng)屬性傳遞, 如
-Dzookeeper.propertyName
- data目錄: 它可以保存一些差異化文件揽惹,其中myid文件用于區(qū)分各個服務器
key | 說明 |
---|---|
基本配置 | - |
clientPort | 給客戶端連接的端口號被饿,默認2181 |
dataDir | 配置內存數(shù)據(jù)庫保存的模糊快照目錄,id文件也保存在該目錄下. 并不需要配置到一個專用存儲設備上搪搏,快照后臺異步寫入狭握,且不會鎖數(shù)據(jù)庫 |
dataLogDir | 事務日志存儲目錄,事務日志順序同步寫入疯溺,推薦使用專用的日志存儲設備 |
tickTime | 定義Zookeeper使用的基本時間度量單位(毫秒). 默認3000毫秒论颅。最小超時時間=1 tick,客戶端最小會話超時時間= 2 tick囱嫩,更低的tickTime可以更快發(fā)現(xiàn)超時恃疯,但會導致更高的網絡流量(心跳消息)和cpu(會話存儲器處理) |
存儲配置 | - |
preAllocSize | 預分配的事務日志文件大小(KB),默認64MB。預分配的目的是減少文件尋址操作的次數(shù)和其它開銷墨闲。每次快照后會重啟一個新的事務日志今妄,因此100次快照會有100*64KB磁盤開銷,如果事務文件平均100B损俭,那么設置100KB比較合適 |
snapCount | 1.每次快照之間的事務數(shù),默認100000(10w). 2.服務器重啟恢復狀態(tài)耗時(A)=讀取快照耗時(B)+快照后所發(fā)生事務的執(zhí)行時間(C)潘酗,使用快照可以減少C杆兵,但會影響服務器性能。 3.集群中仲裁服務器最好不要同時進行快照(將snapCount設置為隨機數(shù))仔夺; 4.如果前一個快照任務正在進行琐脏,后一個快照任務就會等待 |