加入 Hanzo 測試,任何付費方案享 50% 折扣

在同一個行程裡終結 Raft:改以無領導者的後量子共識 Lux Quasar 換下 ZooKeeper 的共識引擎

本文說明 Hanzo Datastore 如何把 ClickHouse Keeper 底下的 NuRaft 引擎換成 Lux Quasar。換上的是後量子、沒有單一領導者的共識,直接連進同一套 C++ 伺服器,複製層繼續呼叫原來的 ZooKeeper 介面,呼叫方式維持不變。

本頁內容

Hanzo Datastore 今天協調複本資料表的方式與 ClickHouse 相同:一個內嵌、相容 ZooKeeper 的 Keeper,在伺服器行程裡跑 Raft。它運作正常。它也是這套堆疊裡最後一塊古典、依賴領導者、量子時代之前的零件。本文說明我們如何終結它,以及為什麼「終結 ZK」並不意味著重寫複製。

把 Keeper 拆開:介面與引擎

一聽到「替換 ZooKeeper」,直覺是以為得去碰每一個 Replicated*MergeTree。不必。ClickHouse Keeper 本來就是兩件可以分開的東西,編進了同一個守護行程:

  1. ZooKeeper 介面 — 一棵可線性化的鍵值樹,帶有循序節點、版本與監看。原始碼裡它是 KeeperStorage,由 KeeperStateMachine 包著(src/Coordination/KeeperStateMachine.h,class IKeeperStateMachine : public nuraft::state_machine)。這是契約。每張複本資料表、每一次 system.zookeeper 查詢、每一次領導者選舉,說的都是它。

  2. 共識引擎 — 接收一串寫入要求,讓節點法定人數就其順序達成一致,再依該順序在狀態機上呼叫 commit(log_idx, data)。今天這是 NuRaft:KeeperServer::launchRaftServer(src/Coordination/KeeperServer.cpp)組出 nuraft::raft_server、asio_service、KeeperStateManager 與 KeeperLogStore。

乾淨的觀察,也就是 Rich Hickey 的那個觀察:KeeperStateMachine 已經對著通用共識介面來寫(nuraft::state_machine:pre_commit、commit、rollback,以及快照)。它不知道也不在乎底下是 Raft。引擎是可以換掉的值,不是焊死在介面上的位置。

所以:留下狀態機,換掉引擎。 複製所依賴的 ZooKeeper 介面原樣保留。死去的是 NuRaft、領導者選舉,以及必須另跑一套 Raft 法定人數的要求。

用什麼替換

引擎變成 Quasar,來自 Lux 的無領導者、基於有向無環圖、後量子的共識。Quasar 在這裡的工作與 Raft 相同:就日誌順序達成一致,並在一筆紀錄成為最終結果時發出訊號。不同的是達成一致的方式:沒有單一領導者需要容錯移轉,用 ML-DSA 簽章代替經典簽章,用置信閾值的最終性代替固定任期裡的多數。

關鍵在於,Quasar 不是我們用殼層叫出去的 Go 服務。它交付的是帶 C++ 可用二進位介面的原生 C 引擎(libluxconsensus)。Datastore 伺服器是 C++。於是引擎連進同一行程:沒有邊車,沒有到協調器的網路一跳,執行時也沒有第二種語言。

接縫已經證明,對著真正的狀態機

這不是草圖,也不再是模型。我們寫了一支程式,連結真正的 ClickHouse 伺服器程式庫(libdbms.a),驅動正式環境的 KeeperStateMachine<KeeperMemoryStorage>,也就是 ReplicatedMergeTree 用來協調的那個類別,全程跑在 Quasar 上,排序路徑裡沒有 NuRaft。它位於 Datastore 樹的 src/Coordination/examples/keeper_quasar_poc.cpp,並在真實的 Linux 主機上編譯執行(aarch64,clang-21)。

這條流程忠實於分派器今天驅動 NuRaft 的方式:兩個階段,要求被附加時是 preprocess,共識將其順序定稿時是 commit:

state_machine->preprocess(rfs, /*lock_mutex*/ true);     // append-time, pre-consensus (≙ nuraft pre_commit)
auto buf = IKeeperStateMachine::getZooKeeperLogEntry(rfs); // the real ZooKeeper log entry
lux_chain_add_block(chain, &blk);   // the entry rides Quasar (the slot raft_server occupies)
lux_consensus_process_vote(...);    // validators agree — no leader
lux_consensus_is_accepted(chain, id, &accepted);
state_machine->commit(height, *buf); // finality → the real KeeperStorage mutates, in order

拿十個要求的 ReplicatedMergeTree 協調序列來跑,包括複本註冊、區塊編號遞增、領導者選舉。它預先處理每個要求,經 Quasar 排序,定稿,提交到真正的 KeeperStorage,並斷言得到的樹完全正確,每筆依共識順序各套用一次:

== REAL Keeper state machine reached consensus on Quasar — zero Raft, zero ZK ensemble ==

其下是最小的 C 二進位介面示範(pkg/c/examples/cpp_consumer.cpp,make test-cpp),連結 libluxconsensus 並定稿單一塊:同一具引擎,不依賴 ClickHouse。兩者都可重現。Datastore 的例子是選擇性的(-DENABLE_EXAMPLES=1 -DLUXCONSENSUS_DIR=...),從不進入一般建置。

按一對一對應到 Keeper:

今天的 Keeper / NuRaftQuasar 原生
nuraft::raft_server 為日誌排序lux_chain_t 為日誌排序
KeeperLogStore / ChangelogQuasar 的有向無環圖日誌
Raft 領導者選舉無領導者的置信投票
state_machine->commit(idx, data)同一次呼叫,在 is_accepted 時觸發
經典簽章,固定任期的領導者ML-DSA 簽章,沒有領導者
為了運轉而另外準備的 ZK/Keeper 法定人數行程內、伺服器內的共識

KeeperStateMachine::commit 是接合點。今天由 NuRaft 呼叫,明天由 Quasar 驅動程式呼叫同一個方法。狀態機分辨不出差別,而這正是它屬於替換、不屬於重寫的性質。

為什麼這比「後量子」更要緊

  • 沒有領導者。 Raft 每個任期選出一個領導者,它一失敗,寫入就停到重新選舉。Quasar 的無領導者協定拿掉了這種佇列前端依賴。
  • 不必再跑第二套法定人數。 協調不再是你要單獨供應、監看和備份的系統。它就是這台伺服器。
  • 構造上就是後量子。 保護協定的簽章是 ML-DSA,不是經典曲線,未來的量子對手無法偽造共識訊息。
  • 傳輸只有一個故事。 它與其餘方向合在一起:現在是原生 TCP/HTTP,下一步是 ZAP,以及各條鏈正在使用的同一套 Lux 原語。

如實的現況

上面的架構是真的,證明現在跑在正式環境的 KeeperStateMachine 上,不是模型,並在真實 Linux 主機上建置通過。剩下的是引擎內部的替換:在 KeeperServer 內部、與 NuRaft 並排放入 Quasar 共識後端,把它設為預設,再把 KeeperServer 還在做的事,快照、復原、工作階段過期、重新組態,在整套 Coordination 測試變綠的前提下搬上去。這是真工作。我們不會在證明提交路徑的那天告訴你 Raft 已經消失。我們告訴你的是,接縫對著真實型別站得住,接合點是 KeeperStateMachine::commit,這就是全部關鍵。

它落地時,contrib/NuRaft/ 和 Raft 引擎離開這棵樹。只要複製還在說 ZooKeeper 介面,介面就留下。這就是不碰任何一個 MergeTree、卻終結最後一塊前量子零件的辦法。

留言

使用 Hanzo ID 登入後即可留言。

正在載入留言…

繼續閱讀