Rivet Actors Envoy 模型深度解析:有状态负载均衡网关的注册数据结构与生命周期

发布时间:2026/9/17 22:51:50
Rivet Actors Envoy 模型深度解析:有状态负载均衡网关的注册数据结构与生命周期 Rivet Actors Envoy 模型深度解析有状态负载均衡网关的注册数据结构与生命周期【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actorsRivet Actors 的公开 API 中Envoy模型描述了一个有状态工作负载网关Envoy在控制平面的注册信息它归属哪个命名空间与数据中心、属于哪个计算池、持有多少容量槽位、最后一次心跳与往返延迟等。本文以 Envoy.md 的属性表为核心骨架结合pegboard、pegboard-envoy等包的源码实现逐字段拆解其语义与存储原理并给出通过GET /envoys接口和 Rust 客户端使用该模型的方法。读完本文你将能准确读懂Envoy返回体中的每个字段、理解其背后的心跳/过期机制并在自己的运维与监控代码中直接使用该数据结构。Envoy 在 Rivet Actors 架构中的角色Rivet Actors 面向 AI Agent、协作应用与持久化执行等有状态工作负载。在这种架构下Actor 进程需要被调度到计算节点上持续运行客户端连接也需要被稳定地路由到正确的节点。Envoy模型对应的正是这一层网关/代理实体——它在命名空间内注册自身信息向控制平面持续上报心跳并负责把外部流量转发到其下承载的 Actor。从仓库源码可以确认这一角色定位engine/packages/pegboard-envoy/是网关实体的运行时实现包含 WebSocket 隧道tunnel_to_ws_task.rs、ws_to_tunnel_task.rs、控制通道control_task.rs与心跳任务ping_task.rsengine/packages/pegboard/src/ops/envoy/下集中了控制平面对 Envoy 注册信息的全部操作get.rs读取、list.rs列表、update_ping.rs心跳更新、expire.rs过期、drain.rs排空、evict_actors.rs驱逐 Actor相关端到端测试位于 engine/packages/engine/tests/envoy/actors_lifecycle.rs 等文件中。也就是说Envoy模型是控制平面持久化的网关注册视图任何查询集群内网关状态的需求例如排障、容量观测、负载均衡分析最终都会落到这个数据结构上。Envoy 模型属性总览原文档 Envoy.md 给出了该模型完整的属性定义SDK 生成版本对应 API 版本 2.3.14NameTypeDescriptionNotescreate_tsi64创建时间戳datacenterString数据中心名称envoy_keyStringEnvoy 唯一标识last_connected_tsOptioni64最近连接时间戳[optional]last_ping_tsi64最近心跳时间戳last_rtti32最近一次往返延迟metadataOptionserde_json::Value附加元数据[optional]namespace_idString命名空间 IDpool_nameString计算池名称slotsi64容量槽位数stop_tsOptioni64停止时间戳[optional]versioni32版本号SDK 中对应的 Rust 结构体定义在 engine/sdks/rust/api-full/rust/src/models/envoy.rs服务端一侧的真实类型定义在 engine/packages/types/src/envoys.rs。对比两者可以发现SDK 将version声明为i32、slots为i64而服务端内部使用u32/u64字段名与序列化名完全一致#[serde(rename create_ts)]等因此可以安全地直接反序列化 API 响应。字段深度解析身份与定位字段envoy_key、namespace_id、datacenter、pool_name、version这五个字段共同回答这是哪个集群里的哪个网关envoy_key网关的唯一标识字符串。它是控制平面所有 Envoy 注册键engine/packages/pegboard/src/keys/envoy.rs的核心组成部分也出现在负载均衡索引键中。网关进程内部通过conn.envoy_key在心跳、隧道等任务中引用自身。namespace_id命名空间 ID字符串形式的 UUID。命名空间是 Actor 资源的隔离边界Envoy 注册、Actor 调度都以命名空间为单位。datacenter数据中心名称。在 get.rs 组装返回体时该字段直接取自控制平面配置datacenter: dc_name.to_string()其中dc_name由ctx.config().dc_name()解析得到。这也意味着同一个 Envoy 键在不同数据中心的控制平面视角下会带上各自的数据中心名。pool_name计算池名称。计算池pool是一组具有相同资源规格的节点集合例如通用池GPU 池。Envoy 注册时将其归属的池名写入持久化存储PoolNameKey调度系统据此把工作负载分配到正确的池。versionEnvoy 运行版本。注意在 EnvoyLoadBalancerIdxKey 中版本号被反序编码存储encode_reverse_u32注释说明Stored in reverse order (higher versions are first)使较新版本的网关在索引中排在最前从而在负载均衡时优先选择新版本节点。生命周期与健康字段create_ts、stop_ts、last_ping_ts、last_connected_ts、last_rtt这一组字段刻画了网关从注册到退出的完整时间线create_ts注册创建时间戳Unix 毫秒。它同时参与两个反向索引键的排序ActiveEnvoyKeyNAMESPACE, ENVOY, ACTIVE, namespace_id, create_ts, envoy_key与ActiveEnvoyByNameKey按池名 create_ts 排序见 engine/packages/pegboard/src/keys/ns.rs 与 同文件 ActiveEnvoyByNameKey。列表接口正是基于create_ts做游标分页。stop_ts可选网关停止时间戳。StopTsKey存在与否也用于判断注册是否已进入过期流程。last_ping_ts最近一次心跳时间戳。它是负载均衡索引EnvoyLoadBalancerIdxKey的排序键之一namespace_id, pool_name, version, last_ping_ts, envoy_key即最近活跃的网关排最前。last_connected_ts可选最近一次连接建立的时间戳底层由ConnectedTsKey持久化。last_rtt最近一次往返延迟。在 LastRttKey 的实现注释中明确标注单位为毫秒// Milliseconds.类型为u32。心跳机制的实现细节可以在 update_ping.rs 中看到每个心跳事务会以Serializable隔离级别读取当前的EnvoyConnIdKey、PoolNameKey、VersionKey、LastPingTsKey并检查ExpiredTsKey是否存在然后写入新的last_ping_ts与last_rtt并在非 serverless 场景下把EnvoyLoadBalancerIdxKey从旧时间戳移动到新时间戳先删除旧键再写入新键。心跳还引入了连接围栏EnvoyConnIdKey标识当前持有注册的连接来自陈旧连接的心跳会被拒绝Outcome::StaleConnection来自已过期连接的心跳会被拒绝Outcome::Expired只有当前连接且未过期的注册才会被更新。在网关侧心跳发送循环位于 engine/packages/pegboard-envoy/src/ping_task.rs以envoy_update_ping_interval为间隔、并叠加 0~128ms 随机抖动// Jitter sleep to prevent stampeding herds防止所有网关同时心跳造成惊群同时检查距上次 pong 的时间是否超过envoy_ping_timeout阈值超时则判定网关失联并终止连接。这些行为把last_ping_ts/last_rtt变成了可观测、可触发过期驱逐的实时健康信号。容量与元数据字段slots、metadataslots该网关可承载的 Actor 槽位数量即容量上限。SlotsKey的实现比较特殊它以小端序编码注释// NOTE: Atomic ops use little endian见 engine/packages/pegboard/src/keys/envoy.rs因为槽位会参与原子增减操作分配/释放 Actor 时对剩余槽位做原子计数而 FoundationDB 的原子操作约定使用小端整数。metadata可选任意 JSON 结构由网关自行上报的附加信息例如 CPU/内存型号、标签等。底层使用MetadataKeyFormalChunkedKey存储超过单键长度限制的元数据会被自动按CHUNK_SIZE分块存储读取时再合并还原并带有PEGBOARD_RUNNER_METADATA_VERSION版本化序列化见 engine/packages/pegboard/src/keys/envoy.rs。SDK 侧对应Optionserde_json::Value。返回体是如何被组装的当 API 层收到查询请求后最终的数据组装发生在 pegboard/src/ops/envoy/get.rs 的get_inner中过程如下先检查PoolNameKey是否存在不存在则直接返回None视为未注册并行读取 9 类数据pool_name、version、slots、create_ts、connected_ts、stop_ts、last_ping_ts、last_rtt以及metadata分块除pool_name/version/slots/create_ts使用Serializable之外其余健康类字段使用Snapshot快照读代码注释说明该操作用于 API 基础信息允许读到轻微过期数据把metadata_chunks通过MetadataKey::combine合并还原空则置为None最后组装出Envoy结构体并返回。值得注意的细节是last_ping_ts与last_rtt在读取时使用unwrap_or_default()即没有历史心跳记录的网关会得到 0 值调用方在解释这两个字段时应留意这一默认行为。通过 API 查询 EnvoyGET /envoysEnvoy模型的实际获取入口是EnvoysApi::envoys_listGET /envoys其完整签名与参数定义见 docs/EnvoysApi.md响应模型为 EnvoysListResponse包含envoys列表与pagination分页信息。参数类型必填说明namespaceString是命名空间 ID过滤该命名空间下的网关nameOptionString否计算池名称过滤对应pool_nameenvoy_keyOptionVecString否按指定的 envoy_key 精确过滤limitOptioni32否单页返回数量cursorOptionString否分页游标该接口在 API 侧的类型定义位于 engine/packages/api-types/src/envoys/list.rsListQuery/ListResponse。后端实现 pegboard_envoy_list 展示了两个执行路径指定pool_name时沿ActiveEnvoyByNameKey子空间反向迭代最新的create_ts在前支持用created_before截断范围以实现游标分页未指定pool_name时沿ActiveEnvoyKey子空间做同样的反向迭代收集到envoy_key列表后以 512 路并发.buffered(512)调用get_inner组装完整对象。注释还明确指出列表读取无需可串行化stale data does not matter因此列表接口在一致性上做了取舍以换取吞吐。接口鉴权使用bearer_authAccept 头为application/json。在 Rust 客户端中使用 Envoy 模型rivet-api-full是由 OpenAPI Generator版本 7.14.0根据 OpenAPI 文档 2.3.14 生成的完整 Rust 客户端安装方式是在项目下放置rivet-api-full目录并在Cargo.toml中声明[dependencies] rivet-api-full { path ./rivet-api-full }客户端内可直接构造或反序列化Envoyuse rivet_api_full::models::Envoy; // 反序列化 API 响应 let envoy: Envoy serde_json::from_str(r#{ create_ts: 1750000000000, datacenter: iad, envoy_key: envoy-abc123, last_ping_ts: 1750000001000, last_rtt: 12, namespace_id: ns-xxxx, pool_name: general, slots: 128, version: 42 }#)?; // 手工构造new 构造器要求 9 个必填字段 let envoy Envoy::new( 1750000000000, // create_ts iad.to_string(), // datacenter envoy-abc123.to_string(), // envoy_key 1750000001000, // last_ping_ts 12, // last_rtt ns-xxxx.to_string(), // namespace_id general.to_string(), // pool_name 128, // slots 42, // version );从 构造器实现 可以看到new()会把last_connected_ts、metadata、stop_ts三个可选字段初始化为None。由于结构体派生Serialize/Deserialize且可选字段标注skip_serializing_if Option::is_none序列化时未设置的字段不会出现在 JSON 中与 API 响应格式保持一致。同时结构体还派生了PartialEq方便在测试中断言两个 Envoy 是否相等。深入阅读指引如果想继续深入仓库中还有以下与本主题直接相关的资源服务端真实类型定义engine/packages/types/src/envoys.rs存储键设计每个字段对应一类键engine/packages/pegboard/src/keys/envoy.rs 与 engine/packages/pegboard/src/keys/ns.rs读取、列表、心跳、过期、排空等操作engine/packages/pegboard/src/ops/envoy/网关侧心跳与隧道实现engine/packages/pegboard-envoy/src/心跳修复成员关系、读路径过期等测试engine/packages/pegboard/tests/envoy/heartbeat_repairs_membership.rs、engine/packages/pegboard/tests/envoy/read_path_expire.rs负载均衡设计文档docs-internal/engine/envoy-load-balancing.md综上Envoy模型并非一个静态的配置快照而是一套围绕注册—心跳—负载索引—过期生命周期的动态状态视图。理解其 11 个字段与背后的存储键、操作事务就能把 API 返回的每一行数据映射到控制平面的真实运行机制上无论是做容量观测、网关排障还是负载均衡分析都能事半功倍。【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考