hive 常驻集群:像 OSPF 一样在线,像 git 一样可信
hive 常驻集群:像 OSPF 一样在线,像 git 一样可信
面向:要在多台机器上长期跑 agent 的人。 读完你应该能回答三个问题:为什么还要一层集群?它什么时候说话算数?主节点挂了怎么办?
0. 一句话定位
git 仍然是任务状态的唯一真相;集群是「在线状态 + 进度通知」的快速控制面。
claim/<tid>的原子性、res/<tid>的审计、land的合流,全部不动。
- 集群负责:谁在线、谁在跑哪张卡、进度到哪一步、谁是主节点。
- 建议每台跑 agent 的机器都起一个节点;不起也能用——只是跨机信息回到 「等下一次
git fetch」的旧速度,且失去主节点故障时的自动接管。
- 分布式能消除单点(一台机器关机/重启,其他节点继续提供服务),但不是物理意义上的 「永不宕机」:所有节点同时断电、网络全断、或 Git 中心仓不可达时,服务仍会降级。
这条边界很重要:集群里的租约是建议性的,不是第二套锁。真正决定「这张卡归谁」的仍然是 git 分支。
1. 拓扑
每台跑 agent 的机器各起一个 hive cluster serve。它们彼此发现、注册、心跳; 其中一台是主节点(leader),其余是跟随者(follower)。主节点只做协调,不做代码合并。
2. 启动与接入
2.1 每台机器
以下命令在 hive 工作目录里跑(hive 仓 =
./hive);Windows 把/换成\。
hive work 和 hive orchestrate 启动时会顺手 cluster ensure:本机没起就后台拉起。 不想自动起:HIVE_CLUSTER_AUTOSTART=0。
2.2 新机器加入
同一广播域内什么都不用配:新节点启动后发 UDP 发现,收到 announce 就向主节点注册。
跨网段、广播被禁、或机器在不同 VLAN 时,用静态 seed:
seed 会写进 ~/.hive/cluster/config.json,下次启动继续用。
2.3 端口与防火墙
| 端口 | 协议 | 用途 | 默认 |
| 8788 | TCP | 集群 HTTP API |
|
| 8789 | UDP | 广播发现 |
|
只开 TCP 8788 也能工作(静态 seed 或手动 join);UDP 只是省掉手填地址。
端口默认值只在 hive_ports.py 定义一次;完整分配、预留段、 防火墙和冲突排查见 端口与服务规划。现场查看/体检:
2.4 共享 token(跨机建议必配)
设置后,/api/cluster/state、注册、心跳、投票、进度写入都要求 X-Hive-Cluster-Token 或 Authorization: Bearer ...。/api/cluster/health 保持公开,方便探活。
3. 发现与注册流程
注册成功后:
- 新节点每
HIVE_CLUSTER_HEARTBEAT秒(默认 1s)向主节点发心跳;
- 主节点把成员变化、进度、租约、事件通过心跳响应/广播带回来;
- 其他节点通过主节点或彼此的心跳逐步收敛到同一份视图。
4. 选主、任期、化身号、宽限期
4.1 任期(term)= fencing token
每次选举 term + 1。所有写入(进度、租约、投票)都带 term:
- 旧主节点/旧进程带着旧 term 写入,新主节点返回
409 stale;
- 节点看到更高 term,立即让位并把
voted_for清空;
- 同 term 的双主(网络分区恢复后)按 node id 大者优先,另一个主动降级。
这解决的是「僵尸主节点复活后继续发号施令」——仅靠心跳超时解决不了。
4.2 化身号(incarnation)
节点每次启动把 incarnation 推到 max(上次+1, 当前毫秒),持久化在 ~/.hive/cluster/node.json。其他节点只接受更高或相等化身号的记录:
- 重启后的节点能干净覆盖关于自己的「已经死了」的旧传闻;
- 旧进程即使还能发包,低化身号会被忽略,不会把新进程的状态顶掉。
4.3 选举规则
- 跟随者超过
leader_lease(默认 5s)没收到主节点心跳 → 清空 leader;
- 等一个错开的选举超时(
election_min+ rank ×election_jitter,默认 3s 起);
- 候选者
term+1,向当前可见的活节点拉票;
- 多数门槛 =
floor(活节点数/2)+1,单节点集群为 1;
- 拿到多数 → 成为主节点,立即广播心跳。
- 跟随者之间也会互发轻量心跳:主节点死后,B/C 仍能确认对方活着,多数门槛才是 2; 否则双方都可能把对方判死,各自以「1 节点多数」当选(早期实现踩过这个双主坑)。
取舍:多数按「当前可见的活节点」算,而不是固定配置成员数。 2 节点集群里主节点退出后,剩下那台自己就是多数,所以能接管; 代价是网络分区时两边可能各自选出主节点。恢复后由 term 收敛。 这是家用/小团队局域网「可用性优先」的选择,不是 Raft 的强一致承诺。
4.3.1 人工指定主控(谁当家由人说了算)
自动选主看的是「谁先发起 + 谁的 term 高」,和「谁该当主控」无关。家里那台 常年开机的机器被笔记本抢走主控、之后再也回不去,是很常见的困扰。所以留了两条 人工通道 —— 偏好管长期,夺权管当下。
偏好(preferred_leader)的作用范围要说清楚:它是本机配置,只影响本机的 选举延迟 —— 被偏好的节点延迟为 0,其它节点让出一个选举窗口。所以想让 0.88 稳定当主控,就在 0.88 上配(或 start --leader)。它只在「当前没有主控」时生效: 主控还活着的时候,偏好不会把它顶下去。
夺权(promote)做了什么,按顺序:
- 先请现任主控
resign主动下台 —— 它自己降级,比等它被更高任期压下去干净, 中间不会出现两边都以为自己是主控的窗口;
- 把本机 term 追到集群最高(
/api/cluster/health免 token 且带 term)。 这一步容易被忽略但很关键:重启过的节点 term 往往落后一整截, 直接term+1仍然比在位主控小,投票会被stale_term拒掉, 现象是「敲了 promote 什么也没发生」;
term+1发起选举,投票请求带force: true。
force 放开了什么:原本 handle_vote 里有一道闸 —— 候选人的 store 落后于我就拒绝投票(防止选出数据落后的主节点)。人工夺权时这道闸会挡路, 因为重启节点的 store 几乎必然落后。放开它的依据是:hive 的 store 只是缓存, git 才是事实账,落后会在当上主控后由 StoreReconciler 从卡面补回来。 放行时会写一条 leader_forced 事件,注明落后多少条 —— 事后能分辨这次任期切换 是「按规矩选的」还是「人硬点的」。
不想放开的话:HIVE_CLUSTER_ALLOW_FORCE_PROMOTE=0(或 config.json 里 allow_forced_promote: false),语义变成「宁可没主控,也不接受落后节点」。
注意 promote 会打断当前主控:它有几百毫秒的不应期,正在跑的
orchestrate那一轮可能落空(下一轮会重来)。别在关键任务收口时切。
4.3.2 主控优先级(OSPF 式,值大者优先)
preferred_leader 有个别扭的地方:它是本机配置,想让 0.88 当主控, 就得在 0.88 上单独配一条,换一台机器又要重配,而且别的节点看不见这个偏好。
所以照 OSPF 的 DR 选举补了一层优先级:每台节点在自己配置里声明「我该排第几」, 这个值随节点自述(_self_node()["priority"])广播给全集群,谁都能看到。 不用再在每台机器上互相约定「谁是首选」。
默认 1 —— 不改的话行为和以前完全一致(谁先发起谁当)。
四条语义,全部照抄 OSPF 的 DR 选举:
| 规则 | 行为 | 为什么这么定 |
| 值大者优先 | 优先级高的选举延迟更短,先发起、先当选 | 这才是「设定优先级」的本意 |
| 同值按 node_id 定序 | 相当于 OSPF 用 Router ID 打平手 | 保证顺序确定,避免同轮打平后反复重选 |
| 不抢占在位主控 | 高优先级节点后加入,不会把现任主控挤下来 | 否则设了 2 的机器每次重启都抢一次主控,而重启往往正发生在排障时 |
| 0 = 不参选 | 这台可以当从属、可以被投票,但自己永不主动发起选举 | OSPF 里 priority 0 的接口永远不会成为 DR;给「只读副本 / 弱机器」用 |
全集群都是 0 时会退化成人人 1。不这么兜底的话没有任何节点愿意发起选举, 集群永远没有主控 —— 而「把优先级理解成越小越优先、全填 0」这种事一定会发生, 死锁比不理解更难查。
优先级只在当前没有主控时才起作用。想立刻换人,还是用 promote:
三者分工:priority 是默认机制(谁都能声明)、preferred_leader 是人工硬指定 (压过一切优先级,delay=0)、promote 是立刻动手。优先级排在 preferred 之后, 即被 preferred 指定的那台仍然最先。
配置持久化在 ~/.hive/cluster/config.json 的 leader_priority,也可以用环境变量 HIVE_CLUSTER_PRIORITY 或启动参数 --priority N 给。
4.4 主节点退出
- 优雅停止(
hive cluster stop/ Ctrl-C):先向同伴发resign,同伴立即进入选举, 通常 1~2 秒内出新主。
- 崩溃/断电:同伴靠
leader_lease超时发现,通常 5~8 秒内出新主。
- 新主上任会发
leader_elected事件,hive cluster watch/ 看板徽标能看到。
4.5 租约恢复宽限期
新主节点上任后,会把已知的、未释放的租约标记 grace_until = now + 120s (--lease-grace 可调)。宽限期内:
- 集群不会因为「换主了」就建议重派;
- 原 worker 若还在跑,继续续租即可;
- 宽限期过后租约才按正常 TTL 判过期。
真正的回收仍然由 hive watchdog 根据 git 里的 claim 决定,集群只提供更早的提示。
5. 集群里有什么
GET /api/cluster/state(带 token)返回:
| 字段 | 含义 |
|
| 当前任期、本机角色、主节点记录 |
|
| 已知节点:id/name/host/port/url/incarnation/last_seen/status(role) |
|
| 在线数、已知数、当前多数门槛 |
|
| 最近 500 条事件(join/leave/elected/progress/lease/repo.updated…) |
|
| 每张卡的实时进度(state/progress/step/message/updated_at/term/gen) |
|
| 建议性租约(agent/lease_until/term/gen/grace_until/released) |
|
| 单调递增的状态代数,供长轮询判断「有没有新东西」 |
5.1 HTTP API
| 方法 | 路径 | 作用 |
| GET |
| 探活(免 token) |
| GET |
| 完整快照 |
| GET |
| 只取 progress + leases |
| GET |
| 事件增量 |
| GET |
| 长轮询,状态变了就返回 |
| GET |
| 本机 SyncAgent 游标/最近同步状态 |
| POST |
| 注册节点 |
| POST |
| 心跳 / 状态交换 |
| POST |
| 拉票 |
| POST |
| 主节点主动让位 |
| POST |
| 节点退出 |
| POST |
| 上报任务进度 |
| POST |
| 上报/续租建议性租约 |
| POST |
| 释放租约 |
| POST |
| 动态加 seed 并尝试注册 |
| POST |
| 立即触发一轮发现 |
| POST |
| 控制面事件(P0: |
| GET |
| 本机 store 快照(tasks/claims/results) |
| GET |
| store 全量快照(压缩后追平用) |
| POST |
| leader CAS 领取(P2a) |
| POST |
| 续租 |
| POST |
| 释放/退回 |
| POST |
| 结果 accepted |
| POST |
| 指派 |
| POST |
| 关闭并清 claim |
| POST |
| leader→follower 的 op prepare(内部) |
| POST |
| leader 确认多数后提交 op(内部) |
| POST |
| 新主截断未提交尾部(内部) |
| GET |
| 已提交 op 追平(内部) |
5.2 与 worker 的接线
Worker.claim抢到 git 锁后,向本机集群发lease;
_refresh_lease续租时同步刷新集群租约;
run_task结束(成功/失败/异常)撤销集群租约;
- agent 每次
push_status:先试HIVE_PUSH_URL(老看板直推),再退到本机集群/api/cluster/push;
- P0 起
status/<tid>默认不再 force push:HIVE_STATUS_GIT=auto(默认)只在 里程碑且集群不可用时兜底;always恢复旧行为;never永不写 Git;
- 看板
/api/status会把集群进度合并进任务行(push_source=cluster);
hive cluster status/peers/events/watch给人和脚本用;
- orchestrate leader gate:有集群时只有 leader 跑 watchdog/dispatch/land; follower/no-leader 跳过写动作;
--no-elect的节点只做 observer。
更多推荐



所有评论(0)