首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

基於 ZooKeeper 臨時節點實現主備自動切換

     編輯:PHP 相關知識

基於 ZooKeeper 臨時節點實現主備自動切搶佔

分散式系統中,主備高可用是核心需求。ZooKeeper 的 臨時節點 特性在會話結束或過期時自動刪除,配合 順序節點 可優雅實現多節點主備切換。本文詳細解析 C 語言實現的完整方案。

核心原理

  • ZOO_EPHEMERAL:客戶端會話斷開時,節點自動刪除
  • ZOO_SEQUENCE:節點名稱自動附加遞增序號,保證唯一性與順序
  • 搶佔機制:多節點同時嘗試建立同一路徑節點,只有首個成功者成為 Master
  • 監聽機制:備節點監聽節點刪除事件,觸發新一輪搶佔

完整實現代碼解析

1. ZooKeeper 初始化與會話管理

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_STATE
  • 會話過期 (ZOO_EXPIRED_SESSION_STATE) 需重新 zookeeper_init 或重啟進程

2. 主節點搶佔邏輯

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 無需手動刪除,網路分區/進程崩潰自動釋放主權
  • 節點資料存放 IP,外部系統可直接讀取得知當前 Master
  • 備節點採用**輪詢重試**,簡單可靠;高頻場景可改用 zoo_wexists 監聽節點刪除事件
  • 搶佔成功後 sleep(2000) 實現「優雅切換」,避免雙 Master 衝突

3. Watcher 回調處理會話事件

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();
    }
}

4. 日誌輸出導向

// 預設輸出到 stderr,生產環境建議導向自有日誌系統
zoo_set_log_stream(my_log_file);
zoo_set_debug_level(ZOO_LOG_LEVEL_INFO);  // 控制日誌級別

進階優化方向

方案 A:輪詢 → 事件驅動(推薦高併發場景)

// 備節點註冊監聽,節點刪除時自動觸發搶佔
zoo_wexists(zh, _zk_path.c_str(), watcher_callback, context);

優點:即時感知、無輪詢開銷。缺點:需處理「驚群效應」,建議加入隨機退避。

方案 B:順序節點實現公平排隊

// 建立順序臨時節點:/master/election/node-
zoo_create(zh, "/master/election/node-", data, len, 
           &ZOO_OPEN_ACL_UNSAFE, ZOO_EPHEMERAL | ZOO_SEQUENCE, 
           buffer, sizeof(buffer));

所有節點建立順序節點,序號最小者為 Master。刪除時下一序號自動晉升,實現**公平隊列**,避免搶佔風暴。

生產環境注意事項

  1. 會話超時設定:zookeeper_init 的 timeout=5000 (ms) 需大於網路抖動時間,建議 10-30 秒
  2. 網路分區處理:網路抖動導致誤判斷掉線,ZooKeeper 內部有「伽羅瓦協議」保證一致性,但應用層需冪等設計
  3. 權限控制:生產環境勿用 ZOO_OPEN_ACL_UNSAFE,應配置 ACL 限制存取
  4. 監控指標:監控 zk_session_state、切換頻率、延遲,設定告警

總結

ZooKeeper 臨時節點 + 輪詢/監聽 機制,以不到 100 行核心代碼實現了生產級主備切換。關鍵在於:

  • 利用 ZooKeeper 內建的一致性保證,避免自造分布式鎖的坑
  • 會話綁定生命週期,進程異常退出自動釋放,無孤兒鎖風險
  • IP 嵌入節點資料,實現服務發現與主備切換雙重功能

此模式廣泛應用於 Kafka Controller、HBase Master、YARN ResourceManager 等核心組件,經過大規模生產驗證。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved