iii 架构四基石:理解 Workers、Triggers、Functions 与 Engine
2026/9/15 0:07:37 网站建设 项目流程

iii 架构四基石:理解 Workers、Triggers、Functions 与 Engine

【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii

本文以 iii 官方 Quickstart 教程 为贯穿案例,系统讲解 iii 中构成一切系统的四个核心概念:Worker(承载工作)、Function(工作本身)、Trigger(触发工作的原因)与 Engine(在它们之间路由的协调者)。读完本文你将掌握 iii 的运行时拓扑、跨语言调用原理、Trigger 生命周期与 Actions、service::name函数命名规范,以及 Engine 实时注册表与 Worker 故障隔离机制——一旦建立了这四块的思维模型,iii 中的一切其他能力(队列、调度器、Agent、沙箱、前端等)都只是同一主题的变体。

为什么是四个概念

Unix 给了进程一个统一接口,React 给了组件一个统一接口,而 iii 试图给每一类软件——队列、调度器、Agent、前端、沙箱、业务逻辑等——一个统一接口:

  • Workers承载工作(host work);
  • Functions就是工作本身(the work);
  • Triggers是让工作运行起来的原因(what causes the work to run);
  • Engine在它们之间路由(routes between them)。

docs/understanding-iii/index.mdx文档给出的精确定义如下表所示:

概念定义关键点
Worker任何连接 Engine 并向其注册 Triggers 和 Functions 的东西可运行在任何位置(笔记本、容器、浏览器标签页、microVM),任意语言,只要能与 Engine 建立 WebSocket 连接
Trigger使 Function 运行的原因包含类型(HTTP、cron、队列消息、状态变更、另一个 Function 调用trigger)、配置(哪个路径、哪个调度、哪个队列)以及它调用的 function ID
FunctionWorker 内部的具名处理器接收 payload、返回 result;标识符遵循service::name约定,跨 Worker 重启与语言边界保持稳定
Engine协调者接受 Worker 连接、维护可用 Functions 与 Triggers 的实时注册表,把调用路由到当前提供目标 Function 的任意 Worker

贯穿全文的工作示例:Quickstart

整个understanding-iii文档都以 Quickstart 教程为工作示例。Quickstart 最终产出一个运行中的系统:两个 Worker 连接到同一个 Engine

  1. math-worker:Python Worker,注册函数math::add
  2. caller-worker:TypeScript Worker,注册函数math::add_two_numbers,它会经由 Engine 调用math::add

教程结束时,系统中还包括statehttp两个 Worker、一个把math::add_two_numbers暴露在POST /math/add-two-numbers的 HTTP Trigger,以及一个名为math、持有running_total键的键值作用域。

运行时拓扑如下(文档原图):

图中每一条箭头都是 Worker 与 Engine 之间的一条 WebSocket 连接,不存在直接的 Worker 到 Worker 流量。caller-worker调用math::add时,调用先到达 Engine,Engine 在自己的注册表中查找math::add当前所在位置,再把调用路由给math-worker。快速上手完整操作步骤见 Quickstart 教程,核心命令包括:

iii project init quickstart --template quickstart # 创建项目(含 math-worker 与 caller-worker) iii --config config.yaml # 启动 Engine,监听 ws://localhost:49134 iii worker add ./workers/math-worker # 启动 Python Worker(注册 math::add) iii trigger math::add a=2 b=3 # 直接调用函数 iii worker add ./workers/caller-worker # 启动 TS Worker(注册 math::add_two_numbers) iii trigger math::add_two_numbers a=10 b=20 # 跨语言调用,返回 { "c": 30 } iii worker add state # 增量加入 state Worker(键值存储) iii worker add http # 增量加入 http Worker(REST 端点) curl -X POST http://localhost:3111/math/add-two-numbers \ -H 'Content-Type: application/json' -d '{"a": 100, "b": 200}' # 返回 { "c": 300, ... }

Workers:做工作的进程

在 iii 系统中,每一类能力都被实现为一个 Worker:队列、调度、沙箱、可观测性、Agent、业务逻辑、设备,甚至是运行在浏览器中的代码。

具体而言,Worker 是一个通过 WebSocket 连接 Engine、并宣告自己可以运行的一组 Functions 与要注册的 Triggers的进程。连接成功后,这些 Functions 就可以被系统中任何位置调用,这些 Triggers 也会响应各自的事件,调用方与 Worker 之间无需编写任何成对的集成代码。

Worker 的边界是有意收窄的

文档明确强调:Worker 不是微服务、不是任务执行器、也不是 sidecar,它只是 Engine 实时注册表中的一个参与者,贡献 Functions 和 Triggers。无论 Worker 是每秒服务数千次调用的长生命周期进程,还是「连接 → 注册 → 运行一次 → 关闭」的短生命周期进程,Engine 对它的对待方式完全相同。

Worker 隔离:崩溃不扩散

Worker 被设计为相互独立的进程,一个 Worker 崩溃不影响其他 Worker:

  • Engine 与每个 Worker 通过独立的 WebSocket连接;
  • Engine 只把调用路由给当前已连接的 Worker;
  • 一个 Worker 崩溃、重启或网络分区,不会传导给其他 Worker;
  • 崩溃 Worker 的 Functions 与 Triggers 会在断开连接时自动从路由表中摘除,其余 Worker 继续提供服务。

这一行为在源码层有直接佐证。Engine 内部维护了function_owners(记录每个(namespace, function_id)当前由哪个 WS Worker 拥有)与worker_name_owners(记录每个存活 Worker 名称由哪个连接持有),在 engine/src/engine/mod.rs 中可见其数据结构与注释:连接断开时会走cleanup_worker/remove_worker_registrations路径,原子地释放对应注册。Worker 重启时还能通过「占位被接管」的机制重新认领自己的名称。

在 Quickstart 中的体现

Quickstart 中的两个 Worker 履行着同一份契约:打开一条到 Engine 的 WebSocket 连接。连接后可注册 Functions、注册 Triggers,以及trigger()其他 Functions。Worker 通常会做其中至少一件事,但最终并不强制必须做任何一件。

Python 与 TypeScript 两个 Worker 是不同语言、不同运行时、甚至可能位于不同机器上的独立进程,彼此都不知道对方的执行上下文,它们只与 Engine 通信,其余全部由 Engine 处理——这就是「any language, any runtime」在实践中的含义:Worker 契约足够小,任何能用 WebSocket 和 JSON 的语言都能实现它

Worker 的日常管理

关于 Worker 的具体运维,可参考 使用 iii / Workers:

iii worker add <name> # 从 registry / OCI / 本地目录安装并启动 iii worker add state@1.2.0 # 固定 semver 版本 iii worker list # 列出 config.yaml 中声明的所有 Worker 及状态 iii worker start <name> # 启动一个 Worker iii worker stop -y <name> # 停止一个 Worker(-y 跳过确认) iii worker restart <name> # 重启 iii worker status <name> # 查看配置、沙箱状态与近期日志 iii worker logs <name> -f # 跟踪日志 iii worker exec <name> -- <command> # 在 Worker 沙箱内执行命令 iii worker update <name> # 更新锁定版本并写回 iii.lock iii worker remove -y <name> # 从 config.yaml 移除并停掉进程 iii worker sync --frozen # CI 形态:校验 lockfile 而不改动文件

Triggers:告诉 iii 何时调用 Function

Trigger 是一个绑定(binding),告诉 iii何时调用某个 Function。一个 Trigger 声明三部分:type(触发它的事件类型)、config(该类型的具体细节,如 HTTP 路径或 cron 表达式)、function_id(它调用的 Function)。当对应事件发生时,Trigger 触发,Engine 把调用路由到提供该 Function 的 Worker。HTTP 请求、cron 调度、队列消息、状态变更、日志事件、流事件,最终都通过 Trigger 变成一次 Function 调用。

Trigger 类型来自已连接的 Worker

Trigger 类型不是 Engine 内置的固定集合,而是由已连接的 Worker 提供。能产生事件的 Worker 会声明一个或多个 trigger type 及对应的配置 schema:

  • httpWorker 提供httptrigger type;
  • cron Worker 提供crontrigger type;
  • stateWorker 提供statetrigger type。

因此,只有当一个宣告了某类型的 Worker 处于连接状态时,才能注册该类型的 Trigger——因为正是这个 Worker 在产生触发它的事件。在引擎实现中,TriggerType结构体(见 engine/src/trigger.rs)携带idnamespace、三种 format(trigger request / call request / call response)与registrator;注册结果分为Registered(类型可用、绑定生效)与Deferred(类型暂不可用,绑定意图被暂存,待类型重新注册时自动激活)两种。

Trigger 的组成部分

一个 Trigger 有三部分:typeconfigfunction_id,合起来告诉 iii监听什么事件、如何监听、事件发生时调用什么。此外还可以指定可选的condition_function_id,在处理器运行之前执行:

  • Trigger 触发时,Engine 会用与处理器相同的 payload 调用条件函数;
  • 条件返回真值 → 执行处理器;返回假值 → 跳过本次调用;
  • 由于 Trigger 关心「何时做」、Function 关心「做什么」,条件函数保持了这种分离:Function 专注于自身工作,而不是积累一堆按 Trigger 区分的守卫逻辑。

引擎侧的条件求值实现在 engine/src/condition.rs:check_condition与 Trigger 目标函数相同的 namespace中解析条件函数,条件返回false时跳过处理器,无返回值按通过处理,调用失败则返回错误。

Trigger 流水线

Trigger 触发时,Engine 在实时注册表中查找其function_id,找到当前提供该 Function 的 Worker,然后分发调用。Function 处理器只看到 payload,永远看不到触发它的来源或事件类型——这是 Trigger 与 Function 彻底解耦的关键。

Trigger Actions:控制调用的方式

Function 调用可以通过 Trigger Action 来控制:

Action行为适用场景
默认(同步)阻塞直到 Function 返回结果或达到配置的超时调用方需要 Function 的返回值
TriggerAction.Void(fire-and-forget)立即返回,调度 Function 运行而不等待结果副作用类工作,调用方无需等待

Worker 也可以定义自己的 Trigger Action。例如 queue Worker 提供TriggerAction.Enqueue({queue}),把调用经具名队列路由、带重试语义。

引擎实现中,spawn_invoke_function(见 engine/src/engine/mod.rs)正是区分这两种模式的地方:当invocation_idSome时,Function 完成后会把InvocationResult回传给调用方;为None时则是 fire-and-forget(对应Voidaction),调用被调度到后台任务执行。

Trigger 生命周期

Trigger 经历四个状态:

  • registered:已向 Engine 声明;
  • active:正在监听自己的事件;
  • invoked:某个事件已经触发了它;
  • unregistered:已被移除。

当拥有某个 Trigger 的 Worker 断开时,它名下的所有 Triggers 会随其 Functions 一起自动注销

在 Quickstart 中的三种触发方式

Quickstart 用三种不同的方式通过 Trigger 调用 Function:

  1. CLI 直接触发iii trigger math::add a=2 b=3是一个由 CLI 自身触发的 Trigger。Engine 把调用路由到任何提供math::add的 Worker。
  2. SDK 调用触发worker.trigger({ function_id: 'math::add', ... })是同一个想法的另一种形式——一个 Worker 内的一个 Function 触发调用另一个 Function,同样经由 Engine 路由。这两条路径对任何已注册的 Function 都成立,无需注册显式 Trigger:每个registerFunction()天生就附带一个可用这两种方式调用的 Trigger。
  3. HTTP Trigger(反应式):由httpWorker 通过worker.registerTrigger()完成,这也是实现 Trigger 的常见反应式方式。当请求到达POST /math/add-two-numbers时:
    1. http查找匹配的 Trigger,向math::addFunction 发起请求;
    2. Engine 收到请求并把调用路由给caller-worker
    3. 响应沿原路返回。math::addFunction永远不会看到 HTTP 请求,它看到的只是一个 payload,与任何其他调用一样。

一个 Function 可以拥有多个 Trigger:同一个 Function 完全可以同时被 cron 调度、队列消息和 CLI 直接调用触发。相关可配置项(含condition_function_id的用法)可参考 configuration Worker 的 README。

Functions:被调用的具名处理器

Function 是 Worker 内部的具名处理器:接收 payload、返回 result。从 iii 系统的角度看,Function 由其名称标识,可跨语言与位置边界寻址。调用方不知道哪个 Worker 提供该 Function、处理器用什么语言编写、Worker 运行在哪里——Engine 会把每次调用路由到当前提供目标 Function 的 Worker。

Function 除了「payload 进 / result 出」之外没有固定形态:有的是纯计算,有的产生副作用(写状态、发 HTTP、入队),有的是 agentic 的(继续调用其他 Function)。Engine 不区分这些,路由对它们完全一致。

函数标识符:service::name约定

Function 标识符遵循service::name约定:

  • service段把相关 Functions 归组为一个命名空间、作用域或 Worker 名;
  • name段是具体的处理器;
  • 例如math::addstate::gethttp::serve

该约定是建议而非硬性规则:引擎层面任何字符串都是合法的 function ID,但service::name形式让 Function 意图一目了然,也避免了不同 Worker 注册的不相关 Functions 之间产生冲突。文档也推荐使用更结构化的path::to::functions形式,但 iii 对此不做任何强制。

直接调用(Direct invocation)

registerFunction()注册一个 Function 后,它就能通过以下两条路径被直接调用:

  • 任何已连接 Worker 的worker.trigger()
  • iii triggerCLI 命令。

这两条路径不需要显式注册 Trigger,是每个已注册 Function 的基线调用面。其他触发源(HTTP、cron、queue、state、stream)则是把显式 Trigger 绑定到同一个function_id上。

一个 Function 对应多个 Trigger

单个 Function 可以是任意数量 Trigger 的目标:同时注册三个共享同一function_id的 Trigger,同一个 Function 就能同时被 HTTP 请求、cron 调度和队列消息调用。函数代码完全不变,变的只是 Trigger 注册——这让一个业务逻辑 Function 可以响应多种事件源,而无需为每种来源编写变体。

在 Quickstart 中的体现

math::addmath::add_two_numbers都是 Function,标识符遵循service::namemath命名空间把相关 Functions 归组,name标识具体处理器。分组是任意的,iii 不强制。

Function ID 跨 Worker 重启保持稳定:当math-worker停止并重启时,调用方无需感知——它们继续调用math::add,Engine 把调用路由到当前提供该 Function 的实例。此外,Functions 是同步定义的,但由于 Trigger 与 Function 之间的解耦,它们可以被异步调用。

Engine:实时注册表与路由协调者

Engine 是一个单进程,持有所有已连接 Worker 及所有已注册 Functions、Triggers 的注册表:

  • Worker 连接时,Engine 记录它提供的 Functions;
  • Worker 断开时,Engine 移除它的 Functions、取消这些 Function 的在途调用,并通知系统其余部分拓扑已变化。

路由与语言、运行时、位置无关。Engine 不需要知道math::add是运行在 Docker、Raspberry Pi 还是浏览器标签页里,它只知道某个 Worker 提供了它。同一个教程可以跨不同运行时重新部署,而无需改动函数代码。

源码视角:Engine 的数据结构

在 engine/src/engine/mod.rs 中可以看到 Engine 的完整构成(Engine结构体),其核心字段与文档描述一一对应:

  • worker_registry:已连接的 Worker 连接注册表;
  • functionsFunctionsRegistry,函数实时注册表;
  • trigger_registryTriggerRegistry,Trigger 注册表;
  • service_registry:服务注册表;
  • invocationsInvocationHandler,调用处理;
  • function_owners:每个(namespace, function_id)当前由哪个 WS Worker 拥有;
  • worker_name_owners:每个存活 Worker 名称由哪个连接持有。

resolve_function展示了路由的核心:在唯一一个namespace 中解析function_id(未指定则用default),不做跨 namespace 的「最佳匹配」搜索;找不到时返回function_not_found错误,并附上该 ID 实际注册所在的 namespace 列表,便于排错。

故障与隔离:断开即清理

前文 Worker 隔离小节提到,Engine 通过独立的 WebSocket 与每个 Worker 连接、只路由给当前已连接的 Worker。引擎侧的function_ownersworker_name_owners两张DashMap(键为(namespace, 名称))正是这一机制的实现:连接断开时cleanup_worker会原子地释放该 Worker 的注册;而一个 Worker 快速重启时,其占位会被新连接接管,从而在「快重启竞态」下不会误删已被另一个存活 Worker 覆盖的注册。相关端到端行为也有对应的测试覆盖,例如 namespace_routing_e2e.rs 与 invocation_integration.rs。

Engine 的更多细节

关于 Engine 的启动流程、配置热加载以及实时注册表与发现面,文档指向 Engine 详解 继续深入。

总结:一套心智模型,无数种变体

iii 的全部能力都建立在同一个极简心智模型之上:

  1. Worker连接 Engine(WebSocket + JSON),宣告它能运行的 Functions 与要注册的 Triggers;
  2. Function是具名处理器,以service::name标识,跨语言、跨位置、跨重启稳定;
  3. Trigger把事件源绑定到function_id,决定「何时调用」,条件函数进一步把「是否调用」从 Function 中剥离;
  4. Engine维护实时注册表,把每次调用路由到当前提供该 Function 的 Worker,并在断开时自动清理。

理解这四块之后,队列、调度、沙箱、可观测性、Agent 等一切模块都可以还原为「某个 Worker 提供的 Trigger 类型 / Function」的组合。进一步的实战内容可参考:

  • Quickstart 教程:从零搭建本文贯穿使用的双 Worker 示例;
  • 使用 iii / Workers:Worker 的安装、锁定版本、日志与沙箱运维;
  • 创建 Workers / Workers:Worker 连接生命周期与 SDK 侧注册细节;
  • Engine 详解:启动流程、配置热加载与实时注册表。

【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询