iii 核心概念解析:Worker、Function、Trigger 与 Engine 四要素如何构成跨语言事件驱动系统
iii 核心概念解析Worker、Function、Trigger 与 Engine 四要素如何构成跨语言事件驱动系统【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii在 iii 中所有软件能力都被归约为同一个接口Worker 承载运行、Function 是具体的工作、Trigger 决定工作何时执行、Engine 负责在它们之间路由。读完本文你将掌握这套心智模型的完整细节——包括service::name函数标识规范、Trigger 的三要素与条件函数、同步/fire-and-forget 调用模式、Worker 断连时的注册表清理语义并能结合 Quickstart 教程 在仓库源码中逐一验证这些机制。四要素总览Unix 给进程提供了单一接口React 给组件提供了单一接口。iii 给每一类软件队列、调度器、Agent、前端、沙箱、业务逻辑等提供了同一个接口。一旦建立这四个部件的心智模型iii 中其余一切都只是主题的变化要素职责一句话定义Worker承载运行任何连接到 Engine 并注册 Trigger 与 Function 的进程可运行在笔记本、容器、浏览器标签页或 microVM 上语言不限前提是能向 Engine 打开 WebSocketTrigger触发时机使 Function 运行的东西。有类型HTTP、cron、队列消息、状态变更、另一个 Function 调用trigger、配置哪个路径、哪个计划、哪个队列以及被调用的 function IDFunction具体工作Worker 内部的命名 handler接收 payload 并返回结果标识符遵循service::name约定在 Worker 重启与语言边界间保持稳定Engine路由中枢协调者。接受 Worker 连接、维护 Function 与 Trigger 的实时注册表、将调用路由到当前提供目标 Function 的 Worker以 Quickstart 为例的运行拓扑本文以 Quickstart 教程 为贯穿示例。该教程最终产出两个连接到同一 Engine 的 Workermath-workerPython Worker注册math::addcaller-workerTypeScript Worker注册math::add_two_numbers它通过 Engine 调用math::add。教程进行到最后系统还包含state与httpWorker、一个把math::add_two_numbers暴露在POST /math/add-two-numbers上的 HTTP Trigger以及一个持有running_total的名为math的键值作用域。运行时拓扑如下每一条箭头都是 Worker 与 Engine 之间的 WebSocket 连接系统内不存在 worker 到 worker 的直接流量。当caller-worker调用math::add时调用经过 EngineEngine 在注册表中查找math::add的当前位置把调用路由到math-worker。Worker独立进程与语言无关的参与方Worker 是 iii 系统中真正执行工作的部分。每一种能力类别都构建为 Worker队列、调度、沙箱、可观测性、Agent、业务逻辑、设备甚至浏览器中执行的代码。具体而言Worker 是一个通过 WebSocket 连接 Engine 的进程它声明自己能运行的一组 Function 和要注册的 Trigger。连接建立后这些 Function 可以从系统任何位置调用这些 Trigger 会响应各自的事件——调用方与 Worker 之间不需要任何成对集成代码。Worker 概念被刻意保持狭窄它不是微服务、任务运行器或 sidecar而是 Engine 实时注册表中的一个参与方贡献 Function 与 Trigger。无论该 Worker 是承载每秒数千次调用的长驻进程还是连接、注册、执行一次就退出的短命进程Engine 的对待方式完全一致。Worker 隔离性Worker 被设计为相互独立的进程。一个 Worker 崩溃不影响其他 WorkerEngine 通过独立 WebSocket 连接每个 Worker只把调用路由到当前已连接的 Worker。某个 Worker 的崩溃、重启或网络分区不会传播到其他 Worker——崩溃 Worker 的 Function 和 Trigger 在断连时从路由表中移除其余 Worker 继续服务。语言无关在实际中意味着什么Quickstart 中的 Python 与 TypeScript Worker 是不同语言、不同运行时、可能在不同机器上的独立进程彼此不知道对方的执行上下文。它们只与 Engine 对话其余由 Engine 处理。这就是任意语言、任意运行时的实际含义Worker 契约小到任何能使用 WebSocket 和 JSON 的语言都可以实现且 Engine 无论 Worker 如何构建、运行在哪里都一视同仁。从源码看Worker 与 Engine 之间的通信契约定义在 协议定义 中Engine 侧的 Worker 连接管理位于 worker_connections 模块 与 引擎主模块。Trigger决定 Function 何时运行的绑定Trigger 是一个告诉 iii 何时调用 Function 的绑定由三部分组成type事件类型如http或cronconfig该类型的细节如 HTTP 路径或 cron 表达式function_id要调用的 Function。三者合起来告诉 iii监听什么事件、如何监听、事件发生时调用什么。HTTP 请求、cron 计划、队列消息、状态变更、日志事件、流事件全部通过 Trigger 变成 Function 调用。Trigger 类型来自已连接的 WorkerTrigger 类型由已连接的 Worker 提供。能产生事件的 Worker 会连同配置 schema 一起声明一个或多个 trigger 类型http Worker 提供http类型cron Worker 提供cron类型state Worker 提供state类型。某类型的 Trigger 只能在该类型的提供者 Worker 处于连接状态时注册——因为正是那个 Worker 产生触发它的事件。Engine 内置了一张已知 trigger 类型到提供者 Worker 包的映射表trigger.rs可确认仓库当前内置的类型及其提供者Trigger 类型提供者 Worker 包httphttpcroncronsubscribepubsubstatestatedurable:subscriberqueuestream/stream:join/stream:leaveiii-streamlog/traceiii-observabilityconfigurationconfiguration值得注意的实现细节从源码结构看trigger 类型注册使用了(namespace, id)二元组作为键trigger.rs 中的TypeKey。注释说明了原因——早期仅用 id 作键时两个项目提供相同 id 会互相覆盖且败者的绑定会被静默带入胜者的提供者引入 namespace 后该问题被消除。条件函数在 handler 之前的一道门槛Trigger 还可以指定可选的condition_function_id它在 handler 之前运行。Trigger 触发时Engine 用 handler 将收到的同一份 payload 调用条件函数返回真值则 handler 执行否则调用被跳过。由于 Trigger 关心何时做、Function 关心做什么条件函数保持了这一分离——Function 专注于自身工作不必累积逐 Trigger 的守卫逻辑。Engine 侧的条件求值逻辑实现在 condition.rs 的check_condition中比文档表述更精确条件函数返回结果且不是显式false→ 放行result.as_bool() ! Some(false)条件函数没有返回任何结果→ 记录警告后仍放行条件函数显式返回false→ 跳过 handler调用本身失败 → 以错误返回不静默吞掉。同时该函数在触发器目标 Function 所在的namespace内解析条件函数确保命名空间 Worker 的条件检查命中自己的条件函数而不是default中同名的函数。Trigger 流水线与动作Trigger 触发时Engine 在实时注册表中查找其function_id找到当前提供该 Function 的 Worker 并派发调用。函数 handler 只看到 payload 本身——它永远不知道触发来源或触发事件类型。Function 调用可通过Trigger Action控制默认同步模式阻塞直到 Function 返回结果或配置的超时触发。调用方需要 Function 返回值时使用。TriggerAction.Voidfire-and-forget立即返回调度 Function 运行而不等待结果。适用于调用方无需等待的副作用工作。这一点可以在协议层得到确认protocol.rs 中TriggerAction枚举定义了Enqueue { queue: String }与Void两个变体Void与Enqueue的具体处理路径分别位于 engine/mod.rs 与 engine/mod.rs。此外Worker 也可以定义自己的TriggerAction。queue Worker 就提供TriggerAction.Enqueue({queue})把调用路由到具名队列并带重试这是把触发器与重试/持久化语义组合起来的扩展点。Trigger 生命周期Trigger 经历四个状态registeredTrigger 已向 Engine 声明activeTrigger 正在监听其事件invoked事件已触发该 TriggerunregisteredTrigger 已被移除。当拥有 Trigger 的 Worker 断连时它的所有 Trigger 会连同其 Function 一起被自动注销。Quickstart 中的三种触发方式Quickstart 演示了以三种不同方式触发 FunctionCLIiii trigger math::add a2 b3是一个由 CLI 自身触发的 TriggerEngine 把调用路由到提供math::add的 WorkerSDK 调用worker.trigger({ function_id: math::add, ... })是同一思路的另一个版本——一个 Worker 内的 Function 触发一个调用另一个 Function 的 Trigger经由 Engine 路由与 CLI 版本一致显式 HTTP Trigger由httpWorker 通过worker.registerTrigger()添加是响应式实现 Trigger 的常见方式。前两条路径针对任何已注册的 Function 都有效无需注册显式 Trigger每次registerFunction()都天然获得一个可通过这两种方式调用的 Trigger。以 Quickstart 第 7 步的 HTTP Trigger 为例quickstart.mdxworker.registerFunction( http::add_two_numbers, async (payload: { body: { a: number; b: number } }) { const result await worker.trigger{ a: number; b: number }, { c: number; running_total: number }({ function_id: math::add_two_numbers, payload: payload.body, }); return { status_code: 200, body: { c: result.c, running_total: result.running_total }, headers: { Content-Type: application/json }, }; }, ); worker.registerTrigger({ type: http, function_id: http::add_two_numbers, config: { api_path: /math/add-two-numbers, http_method: POST }, });httpWorker 拥有 HTTP 套接字当请求到达POST /math/add-two-numbers时http查找匹配的 Trigger发出一个指向math::addFunction 的请求Engine 收到请求把调用路由到caller-worker响应沿同一条路径流回。math::addFunction 全程看不到任何 HTTP 请求——它看到的只是 payload与任何其他调用无异。Functionservice::name标识与直接调用从 iii 系统视角看Function 由其名称标识可跨越语言与位置边界寻址。调用方不知道提供该 Function 的是哪个 Worker、handler 用什么语言编写、Worker 运行在哪里。Engine 把每次调用路由到当前提供目标 Function 的 Worker。Function 除了payload 进 / result 出之外没有固定形状有的是纯计算有的执行副作用写状态、HTTP 调用、入队有的是 Agent 式的、会连锁调用其他 Function。Engine 不加区分——它们的路由方式相同。函数标识符规范函数标识符使用service::name约定service段把相关 Function 归组为命名空间、作用域或 Worker 名name段是具体的 handler。math::add、state::get、http::serve都遵循该约定。需要明确该约定是建议而非硬性规则。Engine 层面任何字符串都是合法的 function ID但service::name形式让 Function 的意图对读者一目了然并避免不同 Worker 注册的无关 Function 之间发生碰撞。Quickstart 中的math::add与math::add_two_numbers即为例math命名空间归组了相关 Function名称标识具体 handler。这种分组完全是任意的——iii 内部不强制path::to::functions结构。直接调用与多 Trigger 复用用registerFunction()注册 Function 后它即刻可以通过两种方式直接调用任意已连接 Worker 的worker.trigger()以及iii triggerCLI 命令。这两条路径不需要显式 Trigger 注册是每个已注册 Function 的基线调用面其他触发源HTTP、cron、queue、state、stream则是把显式 Trigger 绑定到同一个function_id上。单个 Function 可以是任意数量 Trigger 的目标同一个 Function 可以同时被 HTTP 请求、cron 计划、队列消息调用——注册三个共享同一function_id的独立 Trigger 即可。函数代码不变变的只是 Trigger 注册。这正是让单个业务逻辑 Function 能响应众多事件源、无需逐源变体的关键。Quickstart 中直接调用的真实输出iii trigger math::add_two_numbers a10 b20 # 输出 { c: 30 }另外Function ID 在 Worker 重启间保持稳定math-worker停止再启动后调用方无需感知——它们继续调用math::addEngine 把调用路由到当前提供该 Function 的实例。Function 以同步方式定义但由于 Trigger 与 Function 之间的解耦可以被异步调用。Engine注册表、路由与断连清理Engine 是持有所有已连接 Worker、所有已注册 Function 与 Trigger 的注册表的单一进程Worker 连接时Engine 记录它提供哪些 FunctionWorker 断连时Engine 移除其 Function、取消这些 Function 的所有在途调用并通知系统其余部分拓扑已变更——其余 Worker 继续服务。路由与语言、运行时、位置无关。Engine 不需要知道math::add运行在 Docker 里、Raspberry Pi 上还是浏览器标签页中——它只知道某个Worker 提供了它。同一份教程可以跨不同运行时重新部署而不必触碰任何函数代码。更完整的 Engine 行为——启动流程解析命令行参数 → 加载config.yaml→ 应用 config 中的 Worker 声明 → 开始接受连接、config.yaml的运行时热重载解析、diff、校验、提交未变更的 Worker 保持运行无效配置导致 Engine 退出而非进入不确定状态、以及engine::*::list快照查询与engine::workers-available/engine::functions-available订阅事件详见 Engine 文档。Engine 的默认配置可参考仓库中的 config.yaml。小结一个接口四个角色回到开头的心智模型Worker是注册表参与方——独立进程可长驻可短命任意语言任意位置断连即隔离Function是稳定的寻址单元——service::name约定、payload 进 result 出、重启后 ID 不变、天然具备 CLI 与 SDK 两条直接调用面Trigger是时机绑定——typeconfigfunction_id三要素外加可选条件函数与Void/Enqueue动作四状态生命周期随 Worker 断连自动注销Engine是唯一的路由事实来源——实时注册表 架构无关路由 断连清理。四者关系即 Quickstart 拓扑中那条所有箭头都指向 Engine的不变式无 worker 到 worker 直连一切跨语言、跨位置的协作都经由注册表完成。掌握这四块积木后iii 文档中的队列、流、可观测性、沙箱等能力都只是同一主题的变体。【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考