)
Huly Network 高可用实战指南基于 Stateless Containers 的快速故障转移Quick Start: HA【免费下载链接】platformHuly — All-in-One Project Management Platform (alternative to Linear, Jira, Slack, Notion, Motion)项目地址: https://gitcode.com/GitHub_Trending/platform80/platform本文是 Huly Network 虚拟网络的《QUICKSTART_HA高可用快速开始》技术解读与实战手册面向希望在不引入外部协调组件如 ZooKeeper、etcd的前提下为无状态服务实现领导者选举、单实例保证与自动故障转移的开发者。读完本文你将掌握 Huly 无状态容器Stateless Containers的注册竞争机制、serveAgent与AgentImpl的核心 API、100ms 级故障转移的底层原理并能在 5 分钟内用两份可运行的代码搭建出一主一备的 HA 演示环境。先划清边界Network 服务本身不支持 HA开始动手前必须先明确 Huly Network 的 HA 能力边界。本文所描述的高可用机制仅覆盖agents代理与 containers容器这一层而作为中央协调者的Network Server网络服务是单实例运行的❌ Network 服务只能以单实例运行不允许集群部署或同时运行多个 network server❌ 不存在多 network server 间的容错或负载均衡✅ 多个 agent 可以注册同一个容器 UUID主 agent 失效后其余 agent 自动接管✅ 无需外部协调服务即可完成领导者选举与自动故障转移。这一设计约束在 network.ts 中亦有体现NetworkImpl使用进程内的Map维护全部 agent 与容器注册表_agents、_containers天然是单节点状态机。因此如果你的部署拓扑中 Network 服务本身也需要容灾需要结合部署层面的冗余手段而本文只讨论agents/containers 的 HA。5 分钟快速指南它是什么Huly Network 的无状态容器特性允许多个 agent 竞争管理同一个容器 UUID。注册结果遵循先到先得first-wins第一个注册成功的 agent 成为该容器的活跃active持有者其余 agent 成为备用standby节点当活跃容器被关闭或终止时网络会广播移除事件备用 agent 自动重新注册抢先者接管服务。整个过程对调用方透明无需人工干预。何时使用需要领导者选举leader election需要保证集群中同一时刻只有一个服务实例在运行单例服务需要自动故障转移automatic failover希望在不引入 ZooKeeper/etcd 等外部协调服务的情况下实现 HA。最小可运行示例以下示例完整继承自 QUICKSTART_HA.md演示如何用两个 agent 竞争同一个ContainerUuidimport { AgentImpl } from hcengineering/network-core import { createNetworkClient, containerOnAgentEndpointRef } from hcengineering/network-client // 1. 创建你的容器 class MyService implements Container { constructor(readonly uuid: ContainerUuid) {} async request(operation: string): Promiseany { return { status: active, uuid: this.uuid } } async terminate(): Promisevoid { console.log(Service stopped) } // ... 其他必需的接口方法ping / connect / disconnect } // 2. 用 serveAgent 创建两个 agent它们都会尝试注册同一个 UUID const sharedUUID my-service-001 as ContainerUuid // 连接到网络默认端口 3737 const client createNetworkClient(localhost:3737) await client.waitConnection() // Agent 1主节点- 通过 serveAgent 携带无状态容器 await client.serveAgent( localhost:3801, {}, // 动态容器工厂按需创建的容器 (agentEndpoint) { // 返回无状态容器 const service1 new MyService(sharedUUID) return [ { uuid: sharedUUID, kind: my-service as ContainerKind, endpoint: containerOnAgentEndpointRef(agentEndpoint, sharedUUID), container: service1 } ] } ) // Agent 2备用节点- 同样通过 serveAgent 携带无状态容器 await client.serveAgent( localhost:3802, {}, // 动态容器工厂 (agentEndpoint) { const service2 new MyService(sharedUUID) return [ { uuid: sharedUUID, kind: my-service as ContainerKind, endpoint: containerOnAgentEndpointRef(agentEndpoint, sharedUUID), container: service2 } ] } ) // 结果Agent 1 被接受Agent 2 被拒绝UUID 已被 Agent 1 持有 // 当容器被移除时故障转移自动发生注意containerOnAgentEndpointRef(agentEndpoint, sharedUUID)用于生成绑定到具体 agent 端点host:port agentId的容器端点引用。动态容器工厂参数传{}表示本例不需要按需创建容器只注册预先存在的无状态容器。核心 API 速览serveAgent生产环境推荐的入口从 client/src/index.ts 的实现可以看出serveAgent是客户端层的高层封装它替你完成了三件事创建AgentImpl→ 启动NetworkAgentServer监听指定端点 → 调用agent.addStatelessContainer()注入无状态容器并注册到网络。其类型签名如下serveAgent: ( endpointUrl: string, // 本 agent 对外监听地址如 localhost:3801 factory: RecordContainerKind, ContainerFactory, // 动态容器工厂表按 kind 区分 statelessContainers?: StatelessContainersFactory // 无状态容器工厂返回预置容器数组 ) Promisevoid其中无状态容器工厂的类型为type StatelessContainersFactory ( agentEndpoint: AgentEndpointRef ) StatelessContainerConfig[] | PromiseStatelessContainerConfig[]值得注意的细节源码级佐证见 index.tsserveAgent会先检查endpointUrl是否已被占用重复使用会抛出Agent server already running at ...statelessContainers工厂收到的是 agent 启动完成后的agentEndpoint因此可以放心调用containerOnAgentEndpointRef(agentEndpoint, uuid)若未提供endpointUrl的端口部分默认端口为3738。agent.addStatelessContainer(uuid, kind, endpoint, container)底层注入如果你不使用serveAgent而是直接操作AgentImpl仅建议用于学习或自定义流程可以调用该方法把预先存在的容器挂到 agent 上实现见 agent.tsagent.addStatelessContainer(uuid, kind, endpoint, container)参数说明参数类型含义uuidContainerUuid容器 UUID所有 HA agent 必须使用同一个值这是竞争关系成立的前提kindContainerKind容器类型标识如ha-service、leader、migrationendpointContainerEndpointRef该容器的端点引用通常用containerOnAgentEndpointRef(agentEndpoint, uuid)生成containerContainer实际的容器实例实现request/terminate/ping/connect/disconnect接口监听故障转移事件client.onUpdate// 监听容器事件跟踪故障转移 client.onUpdate(async (event) { for (const c of event.containers) { if (c.event NetworkEventKind.removed) { console.log(Container removed - failover in progress) } } })NetworkEventKind共有三种事件类型added容器注册成功、removed容器被移除可能触发接管、updated容器端点等元数据更新。onUpdate返回一个取消订阅函数用完后应调用以释放监听器。故障转移的完整时序结合 network.ts 与 client.ts 的源码一次完整的竞争—接管过程如下两个 agent 都通过serveAgent注册第一个 agent 的容器被接受NetworkEventKind.added第二个 agent 的同一 UUID 注册被拒绝。网络侧NetworkImpl.register()检测到existingContainer.agent.id ! record.agentId时会将该 UUID 加入containersToShutdown数组返回给被拒 agent并打印HA: Container ... already owned by agent ..., rejecting agent ...见 network.ts被拒的 agent收到containersToShutdown后调用agent.terminate(uuid)终止自己的容器实例。在 agent.ts 的register()中体现为接受方激活activateStatelessContainer→ 拒绝方清理terminate活跃容器失效无论是主动terminate还是 agent 心跳超时aliveTimeout默认 3 秒见 timeouts.ts网络都会把NetworkEventKind.removed事件压入eventQueue并通过 tick 广播给所有客户端备用 agent 收到移除事件NetworkClientImpl.onEvent()检查自己的statelessContainers映射若命中则启动延迟 100ms的重新注册见 client.ts第一个完成重新注册的备用节点胜出成为新的活跃实例其余备用节点继续等待下一轮。这一先到先得 事件广播 延迟重注册的设计正是 Huly 无外部协调服务即可实现 HA 的关键网络只负责裁决接管动作由各 agent 自发完成。三种典型 HA 模式1. 领导者选举Leader Election所有节点用同一个leaderId注册第一个注册者即成为领导者const leaderId cluster-${clusterId}-leader as ContainerUuid await client.serveAgent(localhost:${port}, {}, (agentEndpoint) [ { uuid: leaderId, kind: leader as ContainerKind, endpoint: containerOnAgentEndpointRef(agentEndpoint, leaderId), container: leaderService } ]) // 第一个注册成功的 agent 成为 leader2. 单例服务Singleton Services保证迁移、定时清理等任务在整个集群中只运行一份实例const singletonId migration-service as ContainerUuid await client.serveAgent(localhost:${port}, {}, (agentEndpoint) [ { uuid: singletonId, kind: migration as ContainerKind, endpoint: containerOnAgentEndpointRef(agentEndpoint, singletonId), container: migrationService } ]) // 集群中只运行一个实例3. 数据库副本Active-Standby Database Replica多个副本持有同一个UUID 构成主备关系主库挂掉后备用副本自动提升const dbId database-replica-${replicaId} as ContainerUuid await client.serveAgent(localhost:${port}, {}, (agentEndpoint) [ { uuid: dbId, kind: database as ContainerKind, endpoint: containerOnAgentEndpointRef(agentEndpoint, dbId), container: databaseReplica } ]) // 多个副本使用同一个 UUID 实现 HA在完整文档 HA_STATELESS_CONTAINERS.md 中还给出了配合容器生命周期接口的进阶写法DatabaseReplica通过onActivation()将备用副本提升为主库request()中对非活跃副本直接抛错实现只读降级可作为落地参考。关键避坑指南Gotchas从 QUICKSTART_HA.md 原文档及AgentImpl源码可以总结出以下必须遵守/规避的规则❌不要在不同的 agent 上使用不同的 UUID——它们将不会产生竞争✅要在所有 HA agent 上使用相同的 UUID❌不要手动调用agent.addStatelessContainer()绕过serveAgent的完整注册流程——请使用serveAgent的第三个参数无状态容器工厂✅要使用serveAgent的无状态容器工厂参数来声明预置容器❌不要期待瞬时故障转移——存在约100ms的延迟✅要按最终一致性eventual consistency来设计你的系统接受接管窗口内可能短暂无活跃实例。在仓库中运行与验证三步启动演示原文档给出的验证方式两条命令分属两个终端# 终端 1启动网络服务network-pod cd pods/network-pod rushx dev # 终端 2运行 HA 示例 npx ts-node examples/ha-stateless-container-example.ts仓库说明pods/network-pod用于启动中央网络服务监听 3737 端口示例脚本位于 examples/ha-stateless-container-example.ts其中main()会完整演示创建NetworkImpl与NetworkServer→ 创建两个竞争 agentPrimary/Secondary→ 查询容器状态 → 通过shutdown操作模拟主节点故障 → 等待 2 秒验证备用节点接管 → 清理退出。单元测试佐证HA 特性的行为已由核心包的测试套件覆盖文件位于 packages/core/src/test/ha-stateless.spec.ts共 6 个测试用例逐条对应本文所述机制first agent wins when multiple agents register same UUID验证先注册者被接受、后注册者列表中被剔除stateless container is activated on successful registration验证注册成功后容器从statelessContainers映射移动到活跃容器_byIdstateless container is terminated when rejected验证被拒容器的terminate()被调用stateless containers are included in list() call验证注册前无状态容器已出现在list()结果中这是网络裁决的基础getContainer works for both active and stateless containers验证激活前后都能按 UUID 取到容器实例same agent re-registering updates endpoint验证同一 agent 重复注册同一 UUID 不会被拒绝而是更新端点。运行测试cd packages/core rushx test。常见问题FAQ故障转移需要多久默认约100ms客户端onEvent中的setTimeout(..., 100)可通过调整该延迟值改变详见 client.ts。可以配置 3 个及以上备用节点吗可以任意数量的 agent 都可注册同一 UUID先完成重新注册者胜出。状态会随故障转移一起迁移吗不会——容器是无状态的备用节点接管后从自己的状态开始运行跨实例的状态同步需要由你的业务层自行实现。存在脑裂split-brain风险吗网络本身不提供脑裂保护——若发生网络分区理论上可能出现两个节点都认为自己是活跃方的情况需要在部署层通过网络冗余等手段缓解详见 HA_STATELESS_CONTAINERS.md 的 Limitations 章节。进一步深入完整实现文档HA_STATELESS_CONTAINERS.md含removeStatelessContainer、孤儿容器清理、最佳实践与故障排查可运行示例examples/ha-stateless-container-example.ts源码入口agent.tsAgentImpl 无状态容器管理、network.tsfirst-wins 裁决与事件广播、client.ts自动重注册、index.tsserveAgent封装超时与心跳参数packages/core/src/api/timeouts.tsaliveTimeout3s、unusedContainerTimeout5s、pingInterval1s容器接口定义packages/core/src/containers.ts配套文档从 docs/README.md 出发可按序阅读 CORE_CONCEPTS.md、PRODUCTION_DEPLOYMENT.md 与 MULTI_TENANT.md 构建完整知识体系。一句话总结Huly 的 HA 方案用同 UUID 竞争 先到先得裁决 移除事件广播 100ms 延迟重注册四个机制让无状态服务在不需要任何外部协调组件的情况下获得领导者选举与自动故障转移能力——代价是网络节点本身保持单实例且故障转移窗口内系统处于最终一致性状态。【免费下载链接】platformHuly — All-in-One Project Management Platform (alternative to Linear, Jira, Slack, Notion, Motion)项目地址: https://gitcode.com/GitHub_Trending/platform80/platform创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考