- 即时通讯
- 后端
【免费下载链接】WuKongIM
More than just IM 不只是即时通讯(IM)
在 WuKongIM 的动态节点生命周期体系中,Stage 9 解决的是“功能可用”与“生产可运维”之间的最后一道鸿沟:为动态加入/移除的数据节点引入持久化、低频、可计算新鲜度的健康上报回路,让 Slot/Channel 放置与缩容移除在健康证据陈旧或缺失时 fail closed(默认失败),并通过 Prometheus 指标与 manager JSON 字段向运维者解释每一次拒绝的原因,最后用一条真实 WKProto 流量的生命周期冒烟证明整套流程可以在业务不断流的前提下完成“加入—激活—onboard—排空—移除”。读完本文,你可以掌握健康新鲜度模型(fresh/stale/missing)、放置谓词NodeSchedulableForPlacement的判定条件、缩容健康阻断字段的完整清单、低基数指标契约,以及四条分阶段门禁(Gate 9A–9D)的验证命令。
1. 背景:为什么需要“持久的健康新鲜度回路”
Stage 9 之前的 Stage 1–8 已经让动态数据节点生命周期“功能完备且经过故障演练”:seed join、激活、Slot onboarding、scale-in 准备、安全移除、真实 e2e 覆盖,以及基于 gofail 的 join/onboarding/scale-in 故障恢复均已覆盖(见 Stage 9 设计文档)。动态节点设计从一开始就把**成员关系(membership)与健康(health)**刻意分离:
- 成员关系回答“节点是否属于集群”:
joining -> active -> leaving -> removed; - 健康回答“节点当前是否可用、且该证据是否新鲜”:
- 上报状态:
alive | suspect | down - 新鲜度:
fresh | stale | missing - 有效健康:
alive | suspect | down | unknown(unknown是派生状态而非直接上报:无报告、报告超过 TTL、报告属于旧 epoch、或报告对当前操作不可信时,节点被视为unknown)
- 上报状态:
问题在于,Stage 9 之前的ReportNode/ReportSlots只是 best-effort(尽力而为),没有定义持久的新鲜度契约:一个节点可以是持久化的active成员,但它的运行时状态可能已经陈旧、未知或不可达。因此:
- 新增
Slot/Channel放置无法安全地消费健康证据(没有新鲜度、TTL 与 fail-closed 语义); - manager 与 Prometheus 缺少完整的低基数视图来展示动态节点就绪度、健康新鲜度、生命周期尝试次数、任务阻断与移除阻断原因;
- 没有一条组合了“动态加入、onboarding、缩容、移除 + 客户端持续连接发送”的真实流量就绪演练。
Stage 9 的目标是让这个区分显式化且可观测:新鲜健康可以解锁放置与安全校验;陈旧或未知健康一律 fail closed。
2. Stage 9 总体架构:四个有序子阶段
Stage 9 主实施计划 把整个阶段拆成四个有序子计划,使每一块都可以独立测试、独立评审:
| 顺序 | 子阶段 | 计划文档 | 解锁的能力 |
|---|---|---|---|
| 9A | Health Report Model | Stage 9A 计划 | 持久化低频健康报告、新鲜度计算、控制快照字段 |
| 9B | Health-Gated Placement And Remove | Stage 9B 计划 | 新放置排除陈旧/未知节点;scale-in/remove 报告健康阻断 |
| 9C | Observability And Manager Evidence | Stage 9C 计划 | Prometheus 与 manager 暴露生命周期就绪度、健康年龄与阻断证据 |
| 9D | Real Traffic Production Smoke | Stage 9D 计划 | 在公共面证明 join、onboarding、scale-in、remove 在流量持续时可用 |
主计划同时给出总控文档链路:Stage 9 设计文档、动态节点生命周期总计划。
路径对应说明:计划文档写作时使用的包名为
pkg/controllerv2、pkg/clusterv2、internalv2、test/e2ev2、cmd/wukongimv2。从源码结构看,当前仓库中对应实现已分别位于 pkg/controller、pkg/cluster、internal/access/manager、test/e2e/cluster 与 cmd/wukongim 下,本文引用当前仓库中实际存在的文件路径。
3. 跨阶段不变量(Cross-Stage Invariants)
主计划为四个子阶段设定了必须全程保持的不变量,它们是 Stage 9 正确性的“宪法”,任何子阶段的实现都不能违反:
- 健康报告是持久的、低频的,但绝不允许让放置/生命周期的 compare-and-set 操作与心跳抖动(heartbeat churn)竞争;
- Controller(当前仓库中即
pkg/controller)的成员关系是唯一权威(authoritative)。Manager 与 metrics只读健康证据,不拥有持久化健康事实; - 一个节点可调度当且仅当:data 角色 +
active成员 + 新鲜的alive健康 +runtime_ready=true; joining、leaving、removed、suspect、down、stale、missing-health 的节点,对新的 Slot/Channel 放置必须 fail closed;- scale-in/remove 必须用显式字段与有界原因解释健康陈旧或缺失;
- 指标标签保持低基数:禁止UID、channel ID、task ID、session ID、地址标签;
test/e2e(原 e2ev2)下的测试保持黑盒,只使用公共 manager HTTP、公共 metrics、WKProto 与进程句柄。
4. Stage 9A:持久化健康报告模型
4.1 每节点一份有界健康报告
Stage 9A 的核心是在集群状态中为每个节点存储一份紧凑的NodeHealthReport,并带领导者侧报告时间戳与有界载荷字段。当前仓库中的实际定义(pkg/controller/state/types.go):
// NodeHealthReport stores one bounded low-frequency runtime health report. type NodeHealthReport struct { // NodeID is the reporting node identity. NodeID uint64 `json:"node_id"` // Status is the reported runtime health status. Status NodeStatus `json:"status"` // RuntimeReady reports whether the node can serve foreground cluster traffic. RuntimeReady bool `json:"runtime_ready"` // ObservedControlRevision is the latest logical Controller revision observed by the node. ObservedControlRevision uint64 `json:"observed_control_revision"` // ObservedSlotRevision is the latest local Slot runtime revision observed by the node. ObservedSlotRevision uint64 `json:"observed_slot_revision,omitempty"` // ReportSeq is a node-local sequence used for diagnostics. ReportSeq uint64 `json:"report_seq"` // ReportedAtUnixMilli is filled by the Controller leader when the report is proposed. ReportedAtUnixMilli int64 `json:"reported_at_unix_milli"` // AppliedRaftIndex is the Controller Raft index that stored this report. AppliedRaftIndex uint64 `json:"applied_raft_index,omitempty"` // ErrorCode is a bounded machine-readable runtime reason. ErrorCode string `json:"error_code,omitempty"` }存储约束(见 设计文档第 5 节):记录必须有界,不得包含 per-channel、per-UID、per-session 细节;ErrorCode长度上限 128 字符;ClusterState上以NodeHealthReports []NodeHealthReport字段保存每节点一份报告(types.go)。
4.2 关键设计:健康写不推进逻辑 Revision
这是 9A 最重要的机制决策。健康报告不得走会递增ClusterState.Revision的普通变更路径,而是:
- 更新
ClusterState.NodeHealthReports; - 更新
AppliedRaftIndex与校验和(checksum); - 返回
ApplyResult{Updated: true}; - 保持
ClusterState.Revision不变; - 通过校验和变化发布快照。
这样既让健康报告持久化、可见,又保证既有生命周期/任务的 CAS(compare-and-set)围栏不受心跳类高频写入干扰。当前仓库源码印证了这套语义:
- 命令种类 KindReportNodeHealth = "report_node_health",命令体携带
NodeHealth *state.NodeHealthReport; - fsm.ApplyResult 明确区分三个位:
Changed(推进逻辑 revision)、Updated(“命令改变了持久化状态但没有推进逻辑集群状态 revision”)、Noop(幂等/过期); - FSM 测试(fsm_test.go)证明:第一条健康报告
Updated=true && Changed=false且 Revision 不变,重复提交相同报告则落到Noop结果——即写入幂等且单调,不推进也不会回退更新报告的ReportSeq与新鲜度。
4.3 上报节奏、TTL 与配置项
设计文档给出的节奏参数与当前实现一致:
- 默认上报间隔5 秒,默认 TTL30 秒;
- 本地就绪状态变化时实际可行情况下立即上报;
- 仅当抑制仍能在 TTL 过期前保证一次新鲜心跳时,才允许抑制未变化的周期性报告;
- 报告写入按
(node_id, report_seq)幂等单调; - 时间戳在 Controller 领导者侧评估,避免信任远端时钟做新鲜度判断。
配置模型在 pkg/cluster/config.go:
// HealthReportConfig controls low-frequency node health reporting to Controller. type HealthReportConfig struct { // Interval controls how often a node reports compact health evidence. Interval time.Duration // TTL bounds how long the control plane may trust the latest report. TTL time.Duration }默认值与校验(config.go、#L633-L635):Interval=0归一为 5s、TTL=0归一为 30s;校验规则为Interval <= 0 || TTL <= 0 || TTL < Interval时返回ErrInvalidConfig——TTL 必须不小于 Interval。
配置暴露面:cmd/wukongim 的 TOML 示例 中为cluster.node_health_report_interval = "5s"、cluster.node_health_report_ttl = "30s";环境变量名为WK_CLUSTER_NODE_HEALTH_REPORT_INTERVAL与WK_CLUSTER_NODE_HEALTH_REPORT_TTL(见 internal/config/schema.go)。
4.4 节点侧低频上报回路
每个数据节点在启动后运行一个独立的低频健康上报 goroutine,停止时先停健康回路、且有有界的单次上报超时。从源码结构看,pkg/cluster中的startHealthReportLoop/reportNodeHealth/runtimeReadyForHealthReport由一组专门的测试验证(node_health_report_test.go):
TestHealthReportLoopUsesBoundedReportContext:控制器阻塞时,Stop 必须在有界时间内让 ReportNode 的 context 以context.DeadlineExceeded结束——即单次上报有超时预算;- 成功上报会刷新
channelDataPlaneLease的lastVisibleAt,且该可见时间不晚于上报开始时间;上报失败则不刷新租约; - 节点
stopping期间runtimeReadyForHealthReport()返回 false,停止流程还会发出“最后一条 not-ready 健康报告”,并尊重 Stop 的取消。
这意味着健康回路本身遵循第 11 节性能约束:不在 SEND/append/fetch 等热路径上同步等待,也不引入绕过集群语义的单节点捷径。
5. 健康新鲜度映射与放置门禁(Stage 9B)
5.1 fresh / stale / missing 的判定
Controller 健康报告进入控制快照(control snapshot)读模型时,由BuildNodeHealth完成映射(pkg/cluster/control/snapshot.go):
- 无报告(
exists=false)→NodeHealthMissing; ReportedAtUnixMilli早于当前时间(时钟偏差保护)→NodeHealthStale;ttl <= 0 || age > ttl→NodeHealthStale;- 否则 →
NodeHealthFresh。
NodeHealth读模型字段完整保留决策所需证据:Status、Freshness、RuntimeReady、ObservedControlRevision、ObservedSlotRevision、ReportSeq、ReportedAt、ReportAge、ReportTTL、ErrorCode(snapshot.go)。
5.2 共享放置谓词NodeSchedulableForPlacement
Stage 9B 把“可调度”收敛为一个共享谓词,替代各处重复的 active-data 过滤,当前实现(snapshot.go):
// NodeSchedulableForPlacement reports whether a node can receive new data placement. // A schedulable node must be>GOWORK=off go test ./pkg/controllerv2/state ./pkg/controllerv2/fsm ./pkg/controllerv2 ./pkg/clusterv2/control ./pkg/clusterv2/observe ./pkg/clusterv2 -run 'Health|ReportNode|ControlWrite|Config' -count=1 GOWORK=off go test ./cmd/wukongimv2 -run 'Config|HealthReport' -count=1 git diff --check期望:健康报告命令持久化报告数据并推进 applied index,而周期性“仅健康”写入不改变逻辑 placement/lifecycle revision。
Gate 9B:放置与移除对陈旧/缺失健康 fail closed
GOWORK=off go test ./pkg/clusterv2 ./internalv2/usecase/management ./internalv2/access/manager -run 'Health|Placement|ScaleIn|Remove|NodeList' -count=1 git diff --check期望:没有新鲜alive健康的 active 数据节点被排除出新放置,manager scale-in 状态暴露健康阻断。
Gate 9C:运维证据可见且低基数
GOWORK=off go test ./pkg/metrics ./internalv2/usecase/management ./internalv2/access/manager ./internalv2/app -run 'Lifecycle|Health|Metrics|Manager|ScaleIn' -count=1 git diff --check期望:Prometheus 指标族与 manager JSON 字段暴露生命周期与健康新鲜度,且无高基数标签。
Gate 9D:公共真实流量生命周期冒烟通过
GOWORK=off go test -tags=e2e ./test/e2ev2/cluster/dynamic_node_readiness -run TestDynamicNodeLifecycleWithContinuousTraffic -count=1 -timeout 12m -p=1 GOWORK=off go test -tags=e2e ./test/e2ev2/cluster/dynamic_node_join ./test/e2ev2/cluster/dynamic_node_readiness -count=1 -timeout 15m -p=1 git diff --check期望:测试启动三节点集群、跑真实流量、加入并激活节点 4、onboard 一个 Slot 副本、标记 leaving、排空、移除,并证明全程无发送错误。计划中记录的证据为:主冒烟ok ... 241.625s,dynamic_node_join164.029s、dynamic_node_readiness208.090s。
10. 错误处理、性能约束与交付规则
错误处理(设计文档第 12 节):健康报告写入在发送侧是 best-effort,一旦被 ControllerV2 接受即持久化。写入失败时:本地运行时记录有界告警、metrics 记录带有限原因的失败计数、下一周期重试;若报告始终无法提交,放置与移除最终会观察到 stale/unknown 并 fail closed。Controller leader 切换不得产生重复不安全状态:报告幂等,不会回退更新报告的ReportSeq。manager API 对不安全操作返回有界冲突响应并尽量附带阻断原因,不暴露原始内部错误。
性能约束:这是生产就绪工作,必须保护热路径——SEND、append、fetch、delivery、presence 等热路径不得同步等待健康上报;健康报告是每节点(而非每 channel/session)的低频控制面写入;manager 默认列表 API 必须保持分页/轻量;scale-in 清单 API 可以扫描显式清单,但扫描必须有界或作为 detail/status 操作暴露;设计必须对 256 哈希槽、大群、高消息频率、多 channel、多在线用户规模保持有效。
交付(Handoff)规则(主计划,全部已完成):
- 按 9A → 9B → 9C → 9D 顺序执行子阶段;
- 除非用户明确要求合并,每个子阶段使用一个聚焦分支/提交;
- 修改前先阅读所触及包的
FLOW.md,行为变化时同步更新; - 保持健康报告写入有界,不引入单节点部署的 cluster-bypass 路径;
- 每个子阶段在 Gate 命令通过后才更新主计划的 Gate 状态。
推荐执行模式:Stage 9 横跨 ControllerV2 持久化、clusterv2 运行时回路、manager DTO、metrics 与 e2ev2 流量,主计划推荐使用 subagent-driven development——每个子阶段一个全新 subagent,阶段之间做评审,比把整个特性放在一个会话中批量完成更安全。
11. 小结
Stage 9 通过四个可独立评审的子阶段,把 WuKongIM 动态数据节点从“功能可用”推进到“生产可运维”:
- 9A建立每节点一份、领导者侧打时、幂等单调的持久化健康报告,并以
Updated语义隔离健康写与逻辑 revision,使 CAS 围栏不再受心跳抖动影响; - 9B用共享谓词
NodeSchedulableForPlacement让所有新放置消费 fresh+alive+runtime-ready 证据,让 scale-in/remove 以显式字段(blocked_by_health、health_report_age_ms、blocked_reasons等)解释每一次拒绝; - 9C以白名单枚举标签的指标族与 manager JSON 字段,把就绪度、健康年龄、尝试次数与阻断原因变成运维证据;
- 9D用真实进程、真实 WKProto 流量、公共 API 的黑盒冒烟,证明“加入—激活—onboard—排空—移除”可以在业务不中断下完成。
对读者而言,这套“持久化证据 + 新鲜度判定 + fail-closed 门禁 + 有界原因解释”的模式不仅适用于 WuKongIM 的动态扩缩容,也是任何需要在控制面高频心跳与低频强一致事实之间做取舍的分布式系统的一个值得参考的工程范式。
- 即时通讯
- 后端
【免费下载链接】WuKongIM
More than just IM 不只是即时通讯(IM)
相关推荐
WuKongIM 动态节点生命周期 Stage 4:MarkNodeLeaving、Fail-Closed 安全报告与有界 Slot 缩容排空实战
WuKongIM 动态节点生命周期 Stage 4:MarkNodeLeaving、Fail Closed 安全报告与有界 Slot 缩容排空实战 本篇基于 W
即时通讯后端WuKongIM v3 文档 Phase 14:JavaScript/Web 集成验收闭环与 fail-closed 生产就绪门禁
WuKongIM v3 文档 Phase 14:JavaScript/Web 集成验收闭环与 fail closed 生产就绪门禁 本篇技术指南围绕 WuKon
即时通讯后端WuKongIM 动态节点生命周期 Stage 1:持久化生命周期读取模型与 Seed-Join 配置基础实现计划解析
WuKongIM 动态节点生命周期 Stage 1:持久化生命周期读取模型与 Seed Join 配置基础实现计划解析 本文基于 WuKongIM 仓库中 in
即时通讯后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考