iii 引擎(Engine)深度解析:启动流程、调用路由、断连清理与配置热重载
本文基于
docs/0-17-0/understanding-iii/engine.mdx(及仓库中最新版 docs/understanding-iii/engine.mdx)编写,并以engine/目录下的 Rust 源码、engine/config.yaml配置与相关测试用例作为实现级佐证。目标是让读者完整理解 iii 引擎在运行时做了什么、如何与 Worker 协作,以及如何通过配置热重载与发现机制驱动"任何语言、任何运行时"的统一调用面。
导读
iii 引擎是整个系统的"薄层"(thin layer):它不执行业务逻辑,而是让 Workers、Triggers 与 Functions 的价值得以发生——接受 Worker 的 WebSocket 连接、维护实时注册表、在 Worker 之间路由调用,并在 Worker 断连时自动清理现场。读完本文,你将掌握引擎的启动顺序与配置文件结构、引擎的三大运行时职责、Worker 断连时引擎的清理语义(含 invocation_stopped 取消错误码与发现事件)、config.yaml 热重载的工作方式,以及如何通过 engine::*::list 快照调用与 engine::workers-available / engine::functions-available 订阅事件观察系统拓扑。
引擎在 iii 中的定位
官方文档对引擎的定位是一句话:docs/understanding-iii/engine.mdx 的 frontmatter 描述写道:"The iii engine is the thin layer that allows the benefits of Workers, Triggers, and Functions to happen."(iii 引擎是让 Workers、Triggers、Functions 的各种好处得以发生的薄层。)
引擎本身是 Rust 实现的可执行程序(engine/ 目录,入口为 engine/src/main.rs),但"引擎"更多是一个运行时角色:任何通过 WebSocket 接入、向引擎注册 Functions 与 Triggers 并接受调用分发的进程,都处于引擎的管辖之下。引擎对外提供的是一套统一、语言无关的调用与发现面。
启动流程(Startup flow)
文档将引擎启动描述为四步序列,仓库源码可以逐一对上:
-
解析命令行参数。引擎二进制使用
clap定义 CLI(见 engine/src/main.rs 的Cli结构体)。关键的引擎级参数是--config <path>:指定配置文件路径,默认值为config.yaml。不带任何子命令直接运行iii即进入serve模式启动引擎(None => run_serve(&cli_args),见 engine/src/main.rs)。 -
加载配置文件(通常为
config.yaml)。run_serve中通过EngineConfig::config_file(config_path)加载配置,再用EngineBuilder::new().with_config(config).with_config_path(config_path).build().await构建引擎实例(见 engine/src/main.rs)。一个值得注意的细节:如果config.yaml不存在,ensure_config_file会提示"创建并启动引擎吗?";在非交互会话(容器、CI、服务管理器)中则自动创建一个仅含空workers列表的起始配置,保证引擎可以无人值守启动(见 engine/src/main.rs)。 -
应用配置中的 Worker 声明(启动每个声明的 Worker 进程)。配置里的
workers:列表声明了随引擎生命周期启动的 Worker,例如仓库根目录的 engine/config.yaml 声明了iii-stream与configuration两个引擎级 Worker:registration_namespace_grace_ms: 5000 # Only workers that are part of the engine lifecycle belong here. Project # workers such as http, state, cron, queue, pubsub, and bridge belong in # worker-compose.yaml. workers: - name: iii-stream config: port: ${STREAM_PORT:3112} host: 127.0.0.1 adapter: name: redis config: redis_url: redis://localhost:6379 - name: configuration config: adapter: name: fs config: directory: ./config ttl_seconds: 0注意:只有属于引擎生命周期的 Worker 才放在
config.yaml;项目级 Worker(http、state、cron、queue、pubsub、bridge 等)属于worker-compose.yaml,由iii compose管理(engine/config.yaml 中注释即说明这一点)。${STREAM_PORT:3112}这类占位符表示支持环境变量插值与默认值。 -
开始服务连接。
engine.serve().await启动 WebSocket 监听,接受来自各 Worker 的连接。
完成上述序列后,引擎即可接受 Worker 的 WebSocket 连接,并在它们之间路由调用。从源码结构看,这一就绪状态对应 engine/src/engine/mod.rs 中 Engine 结构体持有的一组运行时注册表:worker_registry(Worker 连接注册表)、functions(函数注册表)、trigger_registry(触发器注册表)、service_registry(服务注册表)、invocations(调用处理器)与 channel_manager(通道管理器)。
引擎的三大运行时职责
文档将引擎职责概括为三个运行时关注点,下面结合源码逐一展开。
1. 接受 Worker 连接并维护实时注册表
引擎通过 WebSocket 接受 Worker 连接,并维护"当前有哪些 Worker 在线"的实时注册表。对应实现是 WorkerConnectionRegistry(engine.worker_registry 字段),每个连接对应一个 WorkerConnection。Worker 进程本身可以部署在网络的任何可达位置——连接字符串是 Worker 与 iii 实例之间唯一的耦合。
一个从源码可以看到的实现细节是命名空间(namespace)解析:连接建立时,Worker 的命名空间并非随 WebSocket 握手到达,而是随 engine::workers::register 引擎调用到来。引擎为每个连接维护一个命名空间状态机(Pending → Draining → Resolved,见 engine/src/engine/mod.rs),期间到达的注册消息按到达顺序缓冲,避免注册乱序;若 Worker 迟迟不声明命名空间,引擎在 REGISTRATION_NAMESPACE_GRACE(默认 5 秒,可通过 registration_namespace_grace_ms 配置或 III_NAMESPACE_GRACE_MS 环境变量调整,见 engine/config.yaml 与 engine/src/engine/mod.rs)之后将其归入 default 命名空间。
2. 跟踪每个 Worker 注册的 Functions 与 Triggers,暴露统一系统级表面
每个已连接 Worker 会注册它提供的 Functions 与 Triggers:RegisterFunction、RegisterTrigger、RegisterTriggerType、RegisterService 等消息由引擎写入全局注册表(FunctionsRegistry、TriggerRegistry、ServicesRegistry),形成跨所有 Worker 的统一、系统级调用表面。这也解释了"任何 Worker 注册的 Function 都可用同一个 function_id 被系统内任何地方调用"这一性质。
注册的所有权(ownership)被精细管理:function_owners 表以 (namespace, function_id) 为键记录当前持有该函数的 WS Worker 与注册类型(普通调用注册或 HTTP 调用注册),保证快速重启、断连清理等并发场景下注册不会被误删(见 engine/src/engine/mod.rs)。
3. 路由调用:找到提供目标 Function 的 Worker 并分发
当 Trigger 触发或 Function 被调用时,引擎负责解析目标并分发调用。核心链路在 engine/src/engine/mod.rs 中:
resolve_function(约 L652-L692):在恰好一个命名空间中解析function_id——Some(ns)只在ns中解析,None只在default中解析;刻意不做跨命名空间搜索。文档与源码都强调:命名空间是调用方显式寻址的路由维度,而不是环境性作用域——否则"调用的目标取决于谁在问",这正是命名空间要消除的歧义。解析失败返回function_not_found错误,并附带该 id 实际存在的命名空间列表,方便排障。remember_invocation+spawn_invoke_function(约 L762-L891):以后台任务的方式走完"记住调用 → 通过InvocationHandler处理调用 → 把InvocationResult回传给调用方"的完整流程;同时把调用方的traceparent/baggage上下文透传给下游,让调用链 Span 直接嵌套在调用方 Span 之下。
引擎内部也通过 EngineTrait 暴露 call / call_with_metadata_ns 等接口(见 engine/src/engine/mod.rs),供内置 Worker、钩子与中间件以进程内方式发起调用。
Worker 断连清理(Worker disconnect cleanup)
当一个 Worker 断开连接时,引擎会自动清理该 Worker 在实时注册表中的痕迹:其 Functions 与 Triggers 被移除,针对这些 Function 的进行中(in-flight)调用被取消,系统其余部分继续对外服务。
对应实现是 cleanup_worker(见 engine/src/engine/mod.rs),其清理步骤按序执行:
- 只清理一次:通过
cleanup_claimed原子标记保证并发退出路径上只有一个清理方执行完整 teardown,其余调用方等待完成,避免重复的worker_disconnected事件与重复的死亡指标。 - 终止命名空间排空:
abort_namespace_resolution防止正在排空的注册队列继续给已断开的 Worker 注册函数。 - 释放函数所有权:对普通函数走
release_function_if_owner(CAS 释放,若所有权已被另一个仍存活的 Worker 接管则跳过——这是"快速重启竞态"的保护);对外部(HTTP 调用)函数则先快照所有权、经HttpFunctionsWorker.unregister_http_function注销后再 CAS 释放,防止清理中途被并发注册抢占导致误删新主人的状态。 - 取消进行中的调用:遍历该 Worker 的 in-flight invocation,调用
invocations.halt_invocation(invocation_id)终止它们。 - 注销注册表条目:
trigger_registry.unregister_worker、channel_manager.remove_channels_by_worker、释放 worker-name 租约(release_worker_name_if_owner,同样 CAS,避免误伤已重连的同名 Worker)、最后worker_registry.unregister_worker。
进行中的调用:invocation_stopped 错误
文档明确指出:断连时进行中的请求会收到 invocation_stopped 错误,应将其视为取消;在拥有该 Function 的 Worker 重连之前,重试都会失败。完整语义与三语言示例见 Workers 文档「Handling Worker disconnects」,这里给出一个最小化的 Node 示例:
import { IIIInvocationError } from "iii-sdk";
try {
const result = await worker.trigger({
function_id: "math::add",
payload: { a: 1, b: 2 },
});
} catch (err) {
if (err instanceof IIIInvocationError && err.code === "invocation_stopped") {
// Worker disconnected mid-invocation。订阅 engine::functions-available
// 以获知何时可以重试。
return;
}
throw err;
}
优雅关闭
SDK 的 shutdown 会干净地关闭 WebSocket:引擎移除该 Worker 的 Functions 与 Triggers、以 worker_disconnected 触发 engine::workers-available,并以 invocation_stopped 取消指向它们的 in-flight 调用。若进程被强杀,引擎在发现 socket 断开后也会达到同样的状态,只是优雅关闭更确定、更快。对一次性/短生命周期 Worker(Kubernetes Job、Serverless 容器、定时脚本)而言,shutdown() 尤其有用——它们可以像普通 Worker 一样连接、干活、干净退出(详见 Workers 文档「Shutting down a worker」)。
配置热重载(Config hot-reload)
config.yaml 在运行时被监听。文件变更后,引擎执行 解析(parse)→ 差异(diff)→ 校验(validate)→ 提交(commit) 四步:
- 未在差异中变化的 Worker 保持运行,只有新增、删除或变更的 Worker 被重启。这意味着调整一个 Worker 的声明不会影响其他 Worker 的在线状态。
- 无效配置(解析错误或校验失败)导致引擎退出,而不是进入不确定状态(indeterminate state)。这是文档明确强调的失败语义:宁可退出,也不要在半应用的状态下继续服务。
源码侧的证据:EngineBuilder::build 在配置了文件路径时设置 config file watcher(见 engine/src/workers/config.rs,watcher 观察父目录以兼容编辑器的原子写入);纯内存配置(无文件路径)则关闭文件监听并记录 "reload: no config file to watch"。reload 的差异、作用域与管理逻辑集中在 engine/src/workers/reload/ 模块,仓库还带有 reload_diff_unit.rs、reload_manager_unit.rs、reload_scope_unit.rs 等单测(engine/tests/),以及配置重载相关的 e2e 测试 config_reload_e2e.rs。
Worker settings 是独立分层(较新版本的补充)
在仓库最新版文档 docs/understanding-iii/engine.mdx 中,热重载章节还补充了一层 Worker settings:注册了配置 schema 的 Worker,在首次启动时读取一次自己的 config: 块,用于引导其在 configuration Worker 中的条目;此后其 settings 由该 Worker 动态管理并更新,无需引擎 reload。settings 变更会依据 Worker 的 schema 校验:无效变更被拒绝并保留旧值,因此一次错误的 settings 编辑永远不会把引擎搞挂。这与 config.yaml 的"无效即退出"形成对照:引擎级配置变更走"重载并严格校验"路径,Worker 级 settings 变更走"独立、可回滚"路径。
架构无关路由(Architecture-agnostic routing)
路由与语言、运行时、位置无关。引擎对以下场景应用同一条路由路径:
- 笔记本上运行的 Python Agent 托管的 Function;
- 浏览器标签页里运行的 TypeScript Worker 托管的 Function;
- 微虚拟机(microVM)中运行的 Rust 二进制 托管的 Function;
- Kubernetes 上运行的 OCI 镜像 托管的 Function。
这就是文档所说的:"任何语言、任何运行时"(any language, any runtime)是 iii 的一个具体属性(concrete property),而非愿景。
这一性质的根基在于 Worker 与引擎之间唯一的耦合就是连接字符串。Worker 通过 III_URL 环境变量约定引擎地址(也可显式传给 register_worker),例如 Workers 文档「Connecting to the engine」 中的三语言写法:
import { registerWorker } from "iii-sdk";
const url = process.env.III_URL;
if (!url) throw new Error("III_URL must be set");
const worker = registerWorker(url, {
workerName: "my-worker",
});
import os
from iii import register_worker, InitOptions
worker = register_worker(
os.environ.get("III_URL"),
InitOptions(worker_name="my-worker"),
)
use iii_sdk::{InitOptions, WorkerMetadata, register_worker};
let url = std::env::var("III_URL").expect("III_URL must be set");
let worker = register_worker(
&url,
InitOptions {
metadata: Some(WorkerMetadata {
name: "my-worker".into(),
..Default::default()
}),
..Default::default()
},
);
Worker 连接后经历的状态机为 connecting → connected → available / busy → disconnected(详见 Workers 文档「Worker lifecycle」),引擎跟踪这些迁移并通过发现函数(discovery functions)向其他 Worker 与工具暴露,使系统可以响应拓扑变化。
发现与实时注册表(Discovery and the live registry)
引擎维护的注册表包含三类信息:每个已连接 Worker、每个 Worker 注册的 Functions、绑定到这些 Functions 的 Triggers。其他 Worker 与工具既可以按需读取注册表的当前快照,也可以订阅其演化过程。
快照调用:engine::*::list
按需读取注册表快照的方式是调用以下 engine::*::list 引擎函数(见 Workers 文档「Inspecting the live registry」):
| 函数 | 返回内容 |
|---|---|
engine::workers::list |
每个已连接 Worker 及其指标(可传 worker_id 查询单个 Worker) |
engine::functions::list |
每个已注册 Function,可通过 include_internal 过滤内置函数 |
engine::triggers::list |
每个已注册 Trigger,可通过 include_internal 过滤 |
engine::trigger-types::list |
每个已通告的 Trigger 类型及其配置与调用 schema |
一个 Node 示例:
const { workers } = await worker.trigger({
function_id: "engine::workers::list",
payload: {},
});
const { functions } = await worker.trigger({
function_id: "engine::functions::list",
payload: { include_internal: false },
});
订阅事件:engine::workers-available / engine::functions-available
要实时响应拓扑变化,可以把 Trigger 绑定到引擎的发现事件:
| Trigger | 触发时机 |
|---|---|
engine::workers-available |
有 Worker 连接或断开 |
engine::functions-available |
有 Function 被注册或注销 |
这对"等 Worker 重新上线后继续工作"尤其有用。以 Node 为例,注册一个针对 engine::workers-available 的处理器:
worker.registerFunction(
"discovery::on-workers",
async (data: { event: string; worker_id: string }) => {
if (data.event === "worker_connected") {
// 一个 Worker 刚加入注册表,它的 Functions 现在可调用了。
}
},
);
worker.registerTrigger({
type: "engine::workers-available",
function_id: "discovery::on-workers",
config: {},
});
可观测性
查询 traces、logs 与 metrics 的能力由 iii-observability Worker 提供(关联文档末尾的 Note 明确说明这一点)。换言之:注册表/拓扑相关的发现由引擎自身暴露,而时序数据类可观测性则委托给独立的 observability Worker,引擎保持"薄层"定位。
小结
iii 引擎的运行时心智模型可以浓缩为四句话:
- 启动:解析参数 → 加载
config.yaml→ 按声明启动引擎级 Worker → 开始服务 WebSocket 连接; - 职责:维护在线 Worker 注册表、聚合所有 Worker 的 Functions/Triggers 成统一表面、按命名空间精确解析并路由每次调用;
- 容错:Worker 断连时自动清理注册、取消 in-flight 调用(
invocation_stopped);配置变更走"解析-差异-校验-提交"热重载,无效配置宁可退出也不半应用;Worker settings 走独立可回滚路径; - 开放:路由与语言、运行时、位置无关,任何通过
III_URL接入的进程都获得同等的一等公民地位;系统拓扑通过engine::*::list与发现事件对任何 Worker/工具开放。
如果你想进一步深入,推荐继续阅读:
- docs/0-17-0/creating-workers/workers.mdx:Worker 的连接、生命周期、断连处理与优雅关闭;
- engine/src/engine/mod.rs:引擎核心实现(连接处理、注册、路由、清理);
- engine/src/main.rs:引擎 CLI 入口与启动序列;
- engine/config.yaml:引擎级配置的真实示例;
- engine/src/workers/config.rs 与 engine/tests/config_reload_e2e.rs:配置热重载的实现与端到端验证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.24 K638- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python650
SlideSCIPPT插件,支持素材库、AI助手、一键添加图片标题,复制粘贴位置、一键图片对齐、一键插入Markdown(加粗、超链接等行内样式、代码块、LaTeX等块级样式)、便捷导出图片!C#180
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python52774
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go22545
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java36351