分散式系統中,主備高可用是核心需求。ZooKeeper 的 臨時節點 特性在會話結束或過期時自動刪除,配合 順序節點 可優雅實現多節點主備切換。本文詳細解析 C 語言實現的完整方案。
bool X::init_zookeeper() {
// _clientid 為 NULL 表示首次連線;會話過期時傳入上次 ID 嘗試恢復
_zhandle = zookeeper_init(zk_hosts, zk_watcher, 5000, _clientid, this, 0);
if (NULL == _zhandle) {
MYLOG_ERROR("init zookeeper failed: %s\n", zerror(errno));
return false;
}
MYLOG_INFO("init zookeeper(%s) successfully\n", zk_hosts);
return true;
}
關鍵點:
zookeeper_init 為**非同步**呼叫,回傳非 NULL 僅代表句柄建立成功,不代表會話已連線zk_watcher 中收到 ZOO_SESSION_EVENT + ZOO_CONNECTED_STATEZOO_EXPIRED_SESSION_STATE) 需重新 zookeeper_init 或重啟進程bool X::run() {
while (true) {
mooon::sys::CUtils::millisleep(1000); // 備節點每秒嘗試一次
if (!is_master() && !change_to_master())
continue; // 搶佔失敗,下一輪重試
do_work(); // 成為 Master 後執行核心業務
}
}
bool X::change_to_master() {
static uint64_t log_counter = 0;
// 嘗試建立臨時節點:/master/test
// 節點資料為本機 IP,便於識別當前 Master
int errcode = zoo_create(_zhandle, _zk_path.c_str(),
_myip.c_str(), _myip.size()+1,
&ZOO_OPEN_ACL_UNSAFE, ZOO_EPHEMERAL, NULL, 0);
if (errcode != ZOK) {
_is_master = false;
// 降低備狀態日誌頻率:每 600 次 (10分鐘) 輸出一次
if (0 == log_counter++ % 600) {
MYLOG_DEBUG("become master[%s] failed: (%d)%s\n",
_zk_path.c_str(), errcode, zerror(errcode));
}
return false;
} else {
_is_master = true;
log_counter = 0;
MYLOG_INFO("become master[%s]\n", _zk_path.c_str());
// 關鍵緩衝:讓原 Master 完成正在進行的工作
mooon::sys::CUtils::millisleep(2000);
return true;
}
}
設計亮點:
ZOO_EPHEMERAL 無需手動刪除,網路分區/進程崩潰自動釋放主權zoo_wexists 監聽節點刪除事件sleep(2000) 實現「優雅切換」,避免雙 Master 衝突void zk_watcher(zhandle_t *zh, int type, int state, const char *path, void *context) {
X* x = static_cast(context);
MYLOG_DEBUG("zh=%p, type=%d, state=%d, context=%p, path=%s\n",
zh, type, state, context, path);
// 會話建立成功
if ((ZOO_SESSION_EVENT == type) && (ZOO_CONNECTED_STATE == state)) {
x->on_zookeeper_connected(path);
}
// 會話過期,需重新初始化
else if ((ZOO_SESSION_EVENT == type) && (ZOO_EXPIRED_SESSION_STATE == state)) {
x->on_zookeeper_expired();
}
}
// 預設輸出到 stderr,生產環境建議導向自有日誌系統 zoo_set_log_stream(my_log_file); zoo_set_debug_level(ZOO_LOG_LEVEL_INFO); // 控制日誌級別
// 備節點註冊監聽,節點刪除時自動觸發搶佔 zoo_wexists(zh, _zk_path.c_str(), watcher_callback, context);
優點:即時感知、無輪詢開銷。缺點:需處理「驚群效應」,建議加入隨機退避。
// 建立順序臨時節點:/master/election/node-
zoo_create(zh, "/master/election/node-", data, len,
&ZOO_OPEN_ACL_UNSAFE, ZOO_EPHEMERAL | ZOO_SEQUENCE,
buffer, sizeof(buffer));
所有節點建立順序節點,序號最小者為 Master。刪除時下一序號自動晉升,實現**公平隊列**,避免搶佔風暴。
zookeeper_init 的 timeout=5000 (ms) 需大於網路抖動時間,建議 10-30 秒ZOO_OPEN_ACL_UNSAFE,應配置 ACL 限制存取zk_session_state、切換頻率、延遲,設定告警ZooKeeper 臨時節點 + 輪詢/監聽 機制,以不到 100 行核心代碼實現了生產級主備切換。關鍵在於:
此模式廣泛應用於 Kafka Controller、HBase Master、YARN ResourceManager 等核心組件,經過大規模生產驗證。