Rivet Actors GetOrCreate API 深度解析:跨数据中心幂等 Actor 获取与创建
【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors
导读
在 Rivet Actors 中,actors_get_or_create(HTTPPUT /actors)是构建有状态工作负载时的核心原语:它以namespace + name + key为唯一标识,实现"存在则返回、不存在则创建"的幂等语义,特别适合 AI Agent、协作类应用与持久化执行等场景。本文以 ActorsGetOrCreateApi.md 为主体,结合仓库中 api-public / api-peer / pegboard 的源码实现,完整讲解该接口的请求参数、返回结构、跨数据中心路由策略(Datacenter Round Trips)、Epoxy Key 预留机制与典型错误处理,帮助你掌握在 Rust SDK 中正确、高效地使用这一原语。
一、接口概览:GetOrCreate 与 Create 的定位差异
actors_get_or_create由 api-public 路由 以PUT /actors注册,与POST /actors(ActorsCreateApi)相比,核心差异在于:
| 维度 | POST /actors(actors_create) | PUT /actors(actors_get_or_create) |
|---|---|---|
| 语义 | 无条件创建,key 冲突会触发duplicate_key错误 | 幂等:key 已存在则直接返回既有 Actor,否则创建 |
| 响应 | ActorsCreateResponse,仅含actor | ActorsGetOrCreateResponse,含actor与created标志 |
| 适用场景 | 需要严格"新建"的语义,或客户端已确认 Actor 不存在 | 每次请求都"拿引用",如连接游戏房间、绑定会话、按业务键定位实例 |
从 SDK 生成代码看,客户端方法签名如下(见 actors_get_or_create_api.rs):
pub async fn actors_get_or_create( configuration: &configuration::Configuration, namespace: &str, actors_get_or_create_request: models::ActorsGetOrCreateRequest, ) -> Result<models::ActorsGetOrCreateResponse, Error<ActorsGetOrCreateError>>请求的 base URL 相对http://localhost,需要bearer_auth鉴权,Content-Type与Accept均为application/json。
二、请求参数与数据模型
PUT /actors的参数分两部分:query 参数namespace与 JSON bodyActorsGetOrCreateRequest。对应的 Rust 模型定义在 actors_get_or_create_request.rs。
2.1 参数总表
| 名称 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
namespace | Query | String | ✅ | 目标命名空间名称 |
name | Body | String | ✅ | Actor 的名称(与key共同构成唯一性约束) |
key | Body | String | ✅ | 业务键,最大 1024 字节,不可为空 |
runner_name_selector | Body | String | ✅ | 选择器,决定由哪个 Runner 配置池承载该 Actor |
crash_policy | Body | CrashPolicy | ✅ | 崩溃策略:restart/sleep/destroy |
datacenter | Body | Option<String> | ❌ | 目标数据中心名称;不传时自动选择 |
input | Body | Option<String> | ❌ | 创建时注入的初始数据,为任意 base64 编码的二进制数据 |
2.2 关键字段细节
- key 的约束:服务端在 api-peer 的 get_or_create 中校验:空 key 返回
EmptyKey;超过 1024 字节返回KeyTooLarge(错误定义见 errors.rs)。MAX_ACTOR_KEY_SIZE = 1024在源码中以常量形式声明。 - crash_policy 枚举:见 CrashPolicy,取值
restart、sleep、destroy,分别表示崩溃后重启、进入休眠、直接销毁。 - input:与
ActorsCreateRequest一致(见 ActorsCreateRequest),是可选的 base64 二进制数据,会在 Actor 启动时注入。
三、响应结构:actor + created
响应模型ActorsGetOrCreateResponse(见 actors_get_or_create_response.rs)包含两个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
actor | Actor | 完整的 Actor 对象 |
created | bool | true表示本次调用实际创建了新 Actor;false表示命中已存在的 Actor |
created字段是幂等语义的关键:客户端可以借此区分"首次启动"(需要初始化状态、推送初始数据)与"复用已有实例"(直接连接)。
Actor对象本身携带丰富的生命周期信息(见 Actor.md):
actor_id、namespace_id、name、key、datacenter、runner_name_selectorcreate_ts:首次创建时间;start_ts:首次可连接时间(null表示从未)sleep_ts:进入休眠状态的时间;reschedule_ts:下次尝试调度的最早时间戳connectable_ts:最后一次可连接时间(未运行时为null)destroy_ts、pending_allocation_ts、error:销毁时间、等待分配时间与启动失败的错误详情
四、核心原理:Datacenter Round Trips 与跨数据中心路由
原文档最重要的部分是Datacenter Round Trips分析,它揭示了actors_get_or_create在不同情形下的跨数据中心网络开销。综合服务端实现 get_or_create.rs,可以还原完整流程。
4.1 三种情形的往返次数
情形 A:Actor 已存在(2 次往返)
namespace::ops::resolve_for_name_global:将 namespace 名称解析为全局唯一的namespace_id;GET /actors/{id}:直接定位并读取既有 Actor。
情形 B:Actor 不存在,且在"当前"数据中心创建(2 次往返)
namespace::ops::resolve_for_name_global;pegboard::workflows::actor创建流程(包含 Epoxy Key 分配),即 Actor 创建工作流。
情形 C:Actor 不存在,且在"其他"数据中心创建(3 次往返)
namespace::ops::resolve_for_name_global;POST /actors转发到远端数据中心;- 远端执行
pegboard::workflows::actor创建流程(包含 Epoxy Key 分配)。
原文档同时强调:actor::get 永远发生在同一数据中心内——也就是说,一旦 Actor 被创建,后续对该 Actor 的所有操作(读取、连接、KV 访问)都在其所在的数据中心完成,不会产生跨 DC 的读取。
4.2 数据中心选择的实现细节
请求处理在get_or_create_inner中先解析 namespace,然后调用find_dc_for_actor_creation决定目标数据中心(见 utils.rs):
- 若请求带
datacenter参数,则通过dc_for_name解析为 datacenter label,并校验该 DC 是否启用了对应的 Runner 配置; - 若未指定,则取
list_runner_config_enabled_dcs返回的第一个可用 DC; - 若没有任何 DC 配置了该 runner 名称,返回
NoRunnerConfigConfigured错误。
选定目标 DC 后,get_or_create_inner做本地 / 转发决策(见 get_or_create.rs):
if target_dc_label == ctx.config().dc_label() { rivet_api_peer::actors::get_or_create::get_or_create(ctx.into(), (), query, body).await } else { request_remote_datacenter::<GetOrCreateResponse>( ctx.config(), target_dc_label, "/actors", axum::http::Method::PUT, Some(&query), Some(&body), ).await }即:目标 DC 就是当前 DC 时走进程内操作(api-peer),否则通过 HTTP 转发到远端 DC 的/actors端点——这正对应情形 C 中的第 2 次往返。
五、源码级实现剖析:从 Key 查找到并发兜底
api-peer中的核心逻辑(见 get_or_create.rs)由三步组成:查 key → 命中即返回 → 未命中创建。
5.1 第一步:按 Key 查询既有 Actor
通过pegboard::ops::actor::get_for_key操作完成(见 get_for_key.rs),其内部先调用get_reservation_for_key从Epoxy读取该 key 的预留(reservation)信息,输出三种结果:
| 输出 | 含义 | 处理 |
|---|---|---|
Found { actor } | key 已有 Actor 且在当前 DC | 直接返回created: false |
NotFound | 无预留记录 | 进入创建流程 |
Forward { dc_label } | key 被预留到其他 DC | 返回KeyReservedInDifferentDatacenter错误 |
5.2 第二步:创建并处理并发竞争
未命中时,代码生成新的actor_id(Id::new_v1(ctx.config().dc_label()),即 actor ID 内嵌 DC 标签),调用pegboard::ops::actor::create创建,并携带forward_request: true。
这里有一个值得注意的并发兜底:如果两个客户端同时以同一 key 调用 get_or_create,创建阶段可能抛出duplicate_key错误。api-peer 通过extract_duplicate_key_error(见 get_or_create.rs)从错误链中提取existing_actor_id,然后get这个已存在的 Actor,同样返回created: false。这样即便在竞争条件下,get_or_create 也保持幂等而非报错。该辅助函数同时支持本地RivetError与远端RawErrorResponse两种错误形态。
5.3 第三步:Epoxy Key 预留的全局语义
创建流程涉及 Epoxy 的per-key Paxos机制(详见 ACTOR_KEY_RESERVATION.md):
- Epoxy KV 中存储
Actor key -> reservation ID,reservation ID 内含 Actor 所在 DC 标签; - 全新 key 走 per-key Paxos 快速路径,1 个 RTT 完成预留;
- 解析使用
kv_get_optimistic,假设值一经写入不再变化,因此本地可缓存 Actor 所在 DC,解析一次后无需再跨节点读取; - 由于预留值不可变更,key 与数据中心绑定:Actor 只能在原预留 DC 创建,这正是
Forward与KeyReservedInDifferentDatacenter错误存在的根本原因(错误定义见 errors.rs)。
另一方面,pegboard::ops::actor::create还会订阅 Actor 工作流的CreateComplete/Failed/DestroyStarted事件并阻塞等待结果(见 create.rs),若工作流报出KeyReservedInDifferentDatacenter且允许转发,则会forward_to_datacenter把请求转到正确 DC 重试——这解释了为何默认不指定datacenter时系统能自动将 Actor 创建到正确的位置。
六、错误码速查
get_or_create 可能触发的典型错误(定义于 pegboard/src/errors.rs):
| 错误 code | 触发条件 |
|---|---|
empty_key | key为空字符串 |
key_too_large | key超过 1024 字节 |
duplicate_key | key 冲突(并发下会被 api-peer 自动兜底) |
key_reserved_in_different_datacenter | key 已预留到其他 DC,且请求限制了 DC 或无法转发 |
no_runner_config_configured | 任何 DC 都没有配置匹配runner_name_selector的 Runner |
creation_rate_limit | 同一 namespace 内创建速率超限 |
destroyed_during_creation | 创建过程中 Actor 被销毁 |
namespace_not_found | namespace 名称无法解析 |
七、实战示例:Rust SDK 中的幂等获取
结合前文,一个完整的调用示例如下(类型与函数来自 api-full Rust 客户端):
use rivet_api_full::apis::{configuration::Configuration, actors_get_or_create_api}; use rivet_api_full::models::{ActorsGetOrCreateRequest, CrashPolicy}; let config = Configuration { base_path: "http://localhost".to_string(), bearer_access_token: Some("YOUR_TOKEN".to_string()), ..Default::default() }; let request = ActorsGetOrCreateRequest::new( CrashPolicy::Restart, // 崩溃后重启 "session-42".to_string(), // key:业务唯一键 "chat-room".to_string(), // name:Actor 名称 "default".to_string(), // runner_name_selector:Runner 配置池 ); // datacenter 与 input 为可选字段,可后续设置 // request.datacenter = Some(Some("dc-1".to_string())); // request.input = Some(Some(base64::encode(initial_payload))); let resp = actors_get_or_create_api::actors_get_or_create( &config, "my-namespace", request, ).await?; if resp.created { // 首次创建:初始化状态、推送初始数据 println!("created actor {}", resp.actor.actor_id); } else { // 复用已有 Actor:直接连接 println!("reusing actor {}", resp.actor.actor_id); }要点回顾:
- 不传
datacenter时,系统依据runner_name_selector自动选择启用了对应 Runner 配置的 DC; - 相同的
namespace + name + key重复调用只会返回同一个 Actor; - 若确实需要跨 DC 创建(如按用户地域就近放置),显式传
datacenter可控制放置位置,但要留意 key 一旦预留即与 DC 绑定(见 ACTOR_KEY_RESERVATION.md)。
八、小结
actors_get_or_create是 Rivet Actors 中最实用的"拿引用"原语:它把"查 + 建"两步合并为一次幂等 PUT 请求,通过 Epoxy 的全局 key 预留保证跨数据中心的一致性,并用created标志让调用方精确区分首次创建与复用命中。理解其 Datacenter Round Trips 与底层get_for_key/create/ 转发链路的实现,有助于你在设计多数据中心 Actor 架构时做出更合理的放置与容错决策。
如需深入,可继续阅读 ActorsCreateApi.md(对比无条件创建)、Actor.md(Actor 对象全字段)以及 ACTOR_LIFECYCLE.md(Actor 从创建、运行、休眠到销毁的完整时序)。
【免费下载链接】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),仅供参考