news 2026/9/17 20:19:26

Rivet RunnersApi 完全指南:使用 Rust SDK 列出与管理 Runner 状态

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Rivet RunnersApi 完全指南:使用 Rust SDK 列出与管理 Runner 状态

Rivet RunnersApi 完全指南:使用 Rust SDK 列出与管理 Runner 状态

【免费下载链接】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 仓库中 Rust SDK 自动生成的 RunnersApi 客户端文档展开,系统讲解runners_list(GET /runners)与runners_list_names(GET /runners/names)两个端点的完整使用方式。你将掌握如何通过 Rust SDK 查询命名空间下的 Runner(计算实例)列表、按名称或 ID 过滤、包含已停止实例、基于游标分页,以及理解从 API 网关到 pegboard 底层键值存储的完整调用链路。

什么是 Runner 与 RunnersApi

在 Rivet Actors 体系中,Runner 是承载 Actor 实例运行的计算载体。一个 Runner 拥有固定的资源槽位(slot),每个 Actor 实例会占据其中若干槽位;remaining_slotstotal_slots之间的差值即反映了该 Runner 当前的负载状况。RunnersApi 提供了对 Runner 进行只读查询的两个端点,用于运维观测、调度分析和集群管理。

RunnersApi 的全部方法如下(所有 URI 均相对于http://localhost):

方法HTTP 请求说明
runners_listGET/runners列出命名空间下的 Runner 列表
runners_list_namesGET/runners/names列出命名空间下所有 Runner 的名称(去重聚合)

从仓库结构看,这两个端点存在两层 APIapi-public(engine/packages/api-public/src/runners.rs)负责对客户端开放、认证并跨数据中心聚合;api-peer(engine/packages/api-peer/src/runners.rs)则是单数据中心内部的 peer 接口。Rust SDK 客户端直连的是 public 层。

在 Cargo 工程中接入 SDK

RunnersApi 属于rivet-api-full这个 OpenAPI 生成的 Rust 客户端包(API 版本 2.3.14),源码位于 engine/sdks/rust/api-full/rust。接入方式是在Cargo.toml[dependencies]中加入本地路径依赖:

[dependencies] rivet-api-full = { path = "./rivet-api-full" } tokio = { version = "1", features = ["full"] }

初始化客户端需要构造Configuration。该结构体定义在 engine/sdks/rust/api-full/rust/src/apis/configuration.rs,包含以下关键字段:

字段类型用途
base_pathStringAPI 服务地址,默认http://localhost
user_agentOption<String>附加到请求的 User-Agent 头
clientreqwest::ClientHTTP 客户端
bearer_access_tokenOption<String>Bearer 认证令牌

示例:

use rivet_api_full::apis::{configuration::Configuration, runners_api}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let configuration = Configuration { base_path: "https://api.example.com".to_string(), user_agent: Some("my-rivet-client/1.0".to_string()), client: reqwest::Client::new(), bearer_access_token: Some("YOUR_TOKEN".to_string()), ..Default::default() }; let response = runners_api::runners_list(&configuration, "my-namespace", None, None, None, None, Some(10), None).await?; println!("{:?}", response.runners); Ok(()) }

认证方式

两个端点都要求bearer_auth认证(见文档中的 Authorization 一节)。SDK 在发起请求时会自动读取configuration.bearer_access_token并附加Authorization: Bearer <token>头,见 engine/sdks/rust/api-full/rust/src/apis/runners_api.rs:

if let Some(ref token) = configuration.bearer_access_token { req_builder = req_builder.bearer_auth(token.to_owned()); };

服务端在 engine/packages/api-public/src/runners.rs 中通过ctx.auth().await?校验令牌。

请求头约定

  • Content-Type:未定义(GET 请求无请求体)
  • Acceptapplication/json

SDK 按Accept: application/json发送请求,收到响应后根据 content-type 反序列化:application/json解析为对应模型;text/plain或不支持的类型会返回显式错误(见 runners_api.rs 中的错误处理逻辑)。

runners_list:列出 Runner

函数签名(SDK 侧):

pub async fn runners_list( configuration: &configuration::Configuration, namespace: &str, name: Option<&str>, runner_ids: Option<&str>, runner_id: Option<Vec<String>>, include_stopped: Option<bool>, limit: Option<i32>, cursor: Option<&str>, ) -> Result<models::RunnersListResponse, Error<RunnersListError>>

参数说明

服务端查询参数定义于 engine/packages/api-types/src/runners/list.rs 的ListQuery结构:

参数类型必填说明
namespaceString命名空间名称(注意是全局可读的名称,而非 ID)
nameOption<String>按 Runner 名称精确过滤
runner_idsOption<String>已废弃。逗号分隔的 Runner ID 列表
runner_idOption<Vec<String>>按 Runner ID 过滤,支持多个;SDK 以multi风格重复runner_id查询参数发送
include_stoppedOption<bool>是否包含已停止的 Runner,默认false
limitOption<i32>返回条数上限,服务端默认100
cursorOption<String>分页游标,取自上一页响应的pagination.cursor

注意 SDK 实现细节:runner_id参数使用 "multi" 收集方式,每个 ID 单独作为一个runner_id查询参数发送(runners_api.rs):

req_builder = match "multi" { "multi" => req_builder.query(&param_value.into_iter() .map(|p| ("runner_id".to_owned(), p.to_string())) .collect::<Vec<_>>()), _ => /* join with comma */, };

两条查询路径

从服务端实现(engine/packages/api-peer/src/runners.rs)可以清楚看到,runners_list内部会根据是否提供了 ID 走两条完全不同的路径:

路径 A:按 ID 精确查询

当传入runner_id(或已废弃的runner_ids)时,服务端直接把 ID 交给pegboard::ops::runner::get逐条拉取 Runner,返回的pagination.cursorNone(无分页语义)。

路径 B:按条件列出

否则调用pegboard::ops::runner::list_for_ns,携带namespace_idnameinclude_stoppedcreated_before(由cursor解析而来)与limit,并基于最后一个 Runner 的create_ts生成下一页游标:

let cursor = list_res.runners.last().map(|x| x.create_ts.to_string());

使用示例

let response = runners_api::runners_list( &configuration, "my-namespace", // namespace 必填 Some("worker-us-1"), // 按名称过滤(可选) None, // runner_ids 已废弃,传 None Some(vec!["r_abc123".into()]), // 按 ID 过滤(可选) Some(true), // 包含已停止的 Runner Some(50), // limit None, // cursor ).await?; for runner in &response.runners { println!("{}: slots {}/{} dc={}", runner.runner_id, runner.remaining_slots, runner.total_slots, runner.datacenter); } // 下一页 if let Some(cursor) = response.pagination.cursor { let next = runners_api::runners_list( &configuration, "my-namespace", None, None, None, Some(true), Some(50), Some(&cursor) ).await?; }

返回值:RunnersListResponse

响应模型见 engine/sdks/rust/api-full/rust/docs/RunnersListResponse.md:

字段类型说明
paginationmodels::Pagination游标分页信息
runnersVec<models::Runner>Runner 列表

Pagination结构极为精简,仅包含一个可选的cursor: Option<String>(定义见 engine/packages/api-types/src/pagination.rs)。

Runner模型的字段(见 engine/sdks/rust/api-full/rust/docs/Runner.md 与 engine/packages/types/src/runners.rs):

字段类型说明
runner_idStringRunner 唯一 ID
namespace_idString所属命名空间 ID
datacenterString所在数据中心
nameStringRunner 名称
keyStringRunner 密钥
versioni32版本号
total_slots/remaining_slotsi32总槽位 / 剩余槽位
create_tsi64创建时间戳(毫秒)
drain_tsOption<i64>排空(drain)时间戳,可选
stop_tsOption<i64>停止时间戳,可选
last_ping_tsi64最近一次心跳时间戳
last_connected_tsOption<i64>最近连接时间戳,可选
last_rtti32最近一次往返时延
metadataOption<serde_json::Value>附加元数据,可选

runners_list_names:列出 Runner 名称

当只需要名称清单(例如构建一个名称选择器或做存在性检查)时,使用该端点可以显著降低数据传输量。

函数签名(SDK 侧):

pub async fn runners_list_names( configuration: &configuration::Configuration, namespace: &str, limit: Option<i32>, cursor: Option<&str>, ) -> Result<models::RunnersListNamesResponse, Error<RunnersListNamesError>>

参数说明

服务端参数定义于 engine/packages/api-types/src/runners/list_names.rs 的ListNamesQuery

参数类型必填说明
namespaceString命名空间名称
limitOption<i32>返回名称数量上限,服务端默认100
cursorOption<String>分页游标

注意与runners_list不同,该端点没有namerunner_id等过滤参数,只支持分页游标遍历。

Datacenter Round Trips:2 次往返

文档在该端点下方标注了关键的实现提示,源码同样体现在 engine/packages/api-public/src/runners.rs:

2 round trips: - GET /runners/names (fanout) - [api-peer] namespace::ops::resolve_for_name_global

这意味着一次runners_list_names调用实际包含两次数据中心往返:

  1. public 层通过fanout_to_datacenters将查询**扇出(fanout)**到所有数据中心各自的 peer 接口GET /runners/names
  2. 每个数据中心内,peer 处理器先调用namespace::ops::resolve_for_name_global将命名空间名称解析为namespace_id(见 engine/packages/api-peer/src/runners.rs),再执行名称扫描。

使用示例

let response = runners_api::runners_list_names( &configuration, "my-namespace", Some(100), // limit None, // cursor ).await?; for name in &response.names { println!("runner name: {name}"); }

返回值:RunnersListNamesResponse

响应模型见 engine/sdks/rust/api-full/rust/docs/RunnersListNamesResponse.md:

字段类型说明
namesVec<String>Runner 名称列表
paginationPagination分页游标

public 层在聚合时会先对各数据中心返回的名称做去重(IndexSet),随后排序并截断到limit,最后以最后一个名称作为下一页游标(见 api-public/src/runners.rs):

let mut all_names = fanout_to_datacenters::<_, _, _, _, _, IndexSet<String>>( &ctx, "/runners/names", query, |ctx, query| async move { rivet_api_peer::runners::list_names(ctx, (), query).await }, |_, res, agg| agg.extend(res.names), ).await?.into_iter().take(limit).collect::<IndexSet<_>>(); all_names.sort(); let cursor = all_names.last().map(|x: &String| x.to_string());

服务端与底层的完整调用链

把两个端点串联起来,一次查询在仓库中的完整调用链为:

Rust SDK (runners_api.rs) │ GET /runners 或 /runners/names(Bearer 认证) ▼ api-public 处理器 (engine/packages/api-public/src/runners.rs) │ fanout_to_datacenters(扇出到所有数据中心) ▼ api-peer 处理器 (engine/packages/api-peer/src/runners.rs) │ namespace::ops::resolve_for_name_global → namespace_id ▼ pegboard operation (engine/packages/pegboard/src/ops/runner/) ├── list_for_ns.rs —— 列表扫描 + get_inner 回填 └── list_names.rs —— 名称键范围扫描 ▼ universaldb 事务(Snapshot 隔离级别) └── 键空间:keys::ns::ActiveRunnerKey / AllRunnerKey / RunnerNameKey ...

pegboard 列表扫描实现

pegboard_runner_list_for_ns(engine/packages/pegboard/src/ops/runner/list_for_ns.rs)按四种组合分支扫描键空间,每种分支都对应独立的索引键类型:

条件使用的键类型
按名称 + 包含停止AllRunnerByNameKey
按名称 + 仅活跃ActiveRunnerByNameKey
无名称 + 包含停止AllRunnerKey
无名称 + 仅活跃ActiveRunnerKey

所有扫描都以Snapshot(快照)隔离级别执行——代码注释明确说明“列表时旧数据无关紧要,无需 Serializable”。扫描方向为reverse: true,配合created_before(由游标解析)实现基于创建时间的倒序分页;取到 runner_id 后通过super::get::get_innerbuffered(512)并发回填完整的 Runner 数据。

pegboard 名称扫描实现

pegboard_runner_list_names(engine/packages/pegboard/src/ops/runner/list_names.rs)基于RunnerNameKey正向范围扫描:若提供了after_name游标,则以end_of_key_range从该名称之后开始,StreamingMode::Exact+limit精确控制返回条数。同样使用 Snapshot 隔离级别,注释说明这是为了避免与新名称插入产生争用("not Serializable to prevent contention with inserting new names")。

键空间结构

这些索引键定义于 engine/packages/pegboard/src/keys/ns.rs:

  • ActiveRunnerKey(NAMESPACE, namespace_id, RUNNER, ACTIVE, create_ts, runner_id)—— 活跃 Runner 主索引;
  • AllRunnerKey(NAMESPACE, namespace_id, RUNNER, ALL, create_ts, runner_id)—— 全部 Runner(含已停止);
  • RunnerNameKey(NAMESPACE, namespace_id, RUNNER, NAME, name)—— 名称去重索引(值为空,仅作存在性标记)。

键的前缀 tuple 结构意味着同一命名空间、同一前缀下的记录在存储中天然按create_ts排序,这正是游标分页可以直接把create_ts字符串当作下一页起点、并用end_of_key_range截断的底层原因。

游标分页与 limit 语义

两个端点都遵循相同的分页约定:

  1. 请求时传入上一页返回的pagination.cursor
  2. 服务端把游标解析为上一页最后一个元素的排序键runners_listcreate_tsrunners_list_names用名称);
  3. 底层扫描从该键之后开始,因此游标分页不会因新数据插入而重复或遗漏(区别于 offset 分页);
  4. 首页请求不传cursor,从最新记录开始。

一个容易踩的坑:runners_list走“按 ID 查询”路径时返回的cursor恒为None,此时继续分页没有意义;只有走“列表扫描”路径才会产生有效的下一页游标。

常见错误处理

SDK 为每个方法定义了类型化错误枚举(见 runners_api.rs):

  • RunnersListError/RunnersListNamesError:均只有一个UnknownValue(serde_json::Value)变体,用于承接服务端返回的非预期错误体。

当 HTTP 状态码为 4xx/5xx 时,SDK 返回Error::ResponseError(ResponseContent { status, content, entity }),其中content是原始响应文本,entity是尝试反序列化出的错误枚举。实战中应优先检查status判断错误类别。

典型失败场景:

场景表现
未提供bearer_access_token服务端ctx.auth()校验失败,返回 401
命名空间不存在resolve_for_name_global返回None,映射为Namespace::NotFound(见 api-peer/src/runners.rs)
runner_ids中含有非法 ID解析失败,返回ApiBadRequest("invalid id inrunner_idsquery")
响应不是application/jsonSDK 返回反序列化错误

与 Actors 查询 API 的关系

RunnersApi 与仓库中同样由 OpenAPI 生成的 ActorsListApi(GET /actors)是配套关系:Actor 运行在 Runner 之上,因此常见的数据流是先用runners_list找到负载较低的 Runner,再结合actors_list观察其上的 Actor 分布;而runners_list_names适合做名称空间的全局普查。两者共享同一套Pagination游标约定与 fanout 架构,理解本文的调用链后即可举一反三。

小结

本文基于 engine/sdks/rust/api-full/rust/docs/RunnersApi.md 及其对应的服务端实现,完整梳理了 RunnersApi 两个端点的参数、返回值、分页机制与底层实现:

  • runners_list(GET /runners)返回RunnersListResponse,支持按名称、按 ID 过滤,以及include_stoppedlimitcursor参数;服务端存在“按 ID 直查”和“按索引扫描”两条路径;
  • runners_list_names(GET /runners/names)返回RunnersListNamesResponse,只含名称清单,跨数据中心扇出并去重排序,全程 2 次数据中心往返;
  • 两个端点均需 bearer_auth,分页统一采用基于create_ts/ 名称的游标机制;
  • 底层由 pegboard 的 universaldb 键扫描支撑,索引键设计(ACTIVE/ALL/NAME 前缀)直接决定了查询的排序与分页能力。

结合 engine/packages/api-public/src/runners.rs 与 engine/packages/api-peer/src/runners.rs 阅读,即可获得从 SDK 到存储的完整视角。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 20:17:24

管理后台功能需求文档模板v1.1:docx结构化编写与权限管理实践

简介&#xff1a;这是针对个人消费贷款申请管理后台的功能需求文档模板&#xff0c;面向产品经理、运营人员和开发技术人员&#xff0c;用于统一各方对后台功能与审批流程的认知&#xff0c;减少开发与沟通返工。模板以贷款申请到放款为主线&#xff0c;定义了贷款用户、业务员…

作者头像 李华
网站建设 2026/9/17 20:14:27

04. 如何自定义快捷菜单栏和工具栏? I ANSA 设计小诀窍系列

大家好。在使用ANSA进行网格划分时&#xff0c;高频操作往往分散在不同菜单下&#xff0c;频繁切换会显著拖慢工作效率。如果能将常用功能集中到自定义的选项卡和工具栏中&#xff0c;就可以大幅减少点击次数&#xff0c;让操作更顺手。本次分享将带你掌握ANSA中自定义快捷菜单…

作者头像 李华
网站建设 2026/9/17 20:14:13

Python爬取猫眼电影Top100:数据分析与可视化实战

简介&#xff1a;面向计算机专业学生与Python实战学习者的猫眼电影数据综合项目&#xff0c;覆盖网络爬虫、数据清洗、数据库存储、Web可视化等完整流程&#xff0c;适合作为毕业设计、课程设计或期末大作业。压缩包共86个文件&#xff0c;体积仅1.05MB&#xff0c;内部包含9个…

作者头像 李华