尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

iii 架构四基石:理解 Workers、Triggers、Functions 与 Engine 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 workFunctions就是工作本身the workTriggers是让工作运行起来的原因what causes the work to runEngine在它们之间路由routes between them。docs/understanding-iii/index.mdx文档给出的精确定义如下表所示概念定义关键点Worker任何连接 Engine 并向其注册 Triggers 和 Functions 的东西可运行在任何位置笔记本、容器、浏览器标签页、microVM任意语言只要能与 Engine 建立 WebSocket 连接Trigger使 Function 运行的原因包含类型HTTP、cron、队列消息、状态变更、另一个 Function 调用trigger、配置哪个路径、哪个调度、哪个队列以及它调用的 function IDFunctionWorker 内部的具名处理器接收 payload、返回 result标识符遵循service::name约定跨 Worker 重启与语言边界保持稳定Engine协调者接受 Worker 连接、维护可用 Functions 与 Triggers 的实时注册表把调用路由到当前提供目标 Function 的任意 Worker贯穿全文的工作示例Quickstart整个understanding-iii文档都以 Quickstart 教程为工作示例。Quickstart 最终产出一个运行中的系统两个 Worker 连接到同一个 Engine。math-workerPython Worker注册函数math::addcaller-workerTypeScript Worker注册函数math::add_two_numbers它会经由 Engine 调用math::add。教程结束时系统中还包括state与http两个 Worker、一个把math::add_two_numbers暴露在POST /math/add-two-numbers的 HTTP Trigger以及一个名为math、持有running_total键的键值作用域。运行时拓扑如下文档原图图中每一条箭头都是 Worker 与 Engine 之间的一条 WebSocket 连接不存在直接的 Worker 到 Worker 流量。当caller-worker调用math::add时调用先到达 EngineEngine 在自己的注册表中查找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 a2 b3 # 直接调用函数 iii worker add ./workers/caller-worker # 启动 TS Worker注册 math::add_two_numbers iii trigger math::add_two_numbers a10 b20 # 跨语言调用返回 { c: 30 } iii worker add state # 增量加入 state Worker键值存储 iii worker add http # 增量加入 http WorkerREST 端点 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 崩溃不影响其他 WorkerEngine 与每个 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 / Workersiii worker add name # 从 registry / OCI / 本地目录安装并启动 iii worker add state1.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 何时调用 FunctionTrigger 是一个绑定binding告诉 iii何时调用某个 Function。一个 Trigger 声明三部分type触发它的事件类型、config该类型的具体细节如 HTTP 路径或 cron 表达式、function_id它调用的 Function。当对应事件发生时Trigger 触发Engine 把调用路由到提供该 Function 的 Worker。HTTP 请求、cron 调度、队列消息、状态变更、日志事件、流事件最终都通过 Trigger 变成一次 Function 调用。Trigger 类型来自已连接的 WorkerTrigger 类型不是 Engine 内置的固定集合而是由已连接的 Worker 提供。能产生事件的 Worker 会声明一个或多个 trigger type 及对应的配置 schemahttpWorker 提供httptrigger typecron Worker 提供crontrigger typestateWorker 提供statetrigger type。因此只有当一个宣告了某类型的 Worker 处于连接状态时才能注册该类型的 Trigger——因为正是这个 Worker 在产生触发它的事件。在引擎实现中TriggerType结构体见 engine/src/trigger.rs携带id、namespace、三种 formattrigger request / call request / call response与registrator注册结果分为Registered类型可用、绑定生效与Deferred类型暂不可用绑定意图被暂存待类型重新注册时自动激活两种。Trigger 的组成部分一个 Trigger 有三部分type、config、function_id合起来告诉 iii监听什么事件、如何监听、事件发生时调用什么。此外还可以指定可选的condition_function_id在处理器运行之前执行Trigger 触发时Engine 会用与处理器相同的 payload 调用条件函数条件返回真值 → 执行处理器返回假值 → 跳过本次调用由于 Trigger 关心「何时做」、Function 关心「做什么」条件函数保持了这种分离Function 专注于自身工作而不是积累一堆按 Trigger 区分的守卫逻辑。引擎侧的条件求值实现在 engine/src/condition.rscheck_condition在与 Trigger 目标函数相同的 namespace中解析条件函数条件返回false时跳过处理器无返回值按通过处理调用失败则返回错误。Trigger 流水线Trigger 触发时Engine 在实时注册表中查找其function_id找到当前提供该 Function 的 Worker然后分发调用。Function 处理器只看到 payload永远看不到触发它的来源或事件类型——这是 Trigger 与 Function 彻底解耦的关键。Trigger Actions控制调用的方式Function 调用可以通过 Trigger Action 来控制Action行为适用场景默认同步阻塞直到 Function 返回结果或达到配置的超时调用方需要 Function 的返回值TriggerAction.Voidfire-and-forget立即返回调度 Function 运行而不等待结果副作用类工作调用方无需等待Worker 也可以定义自己的 Trigger Action。例如 queue Worker 提供TriggerAction.Enqueue({queue})把调用经具名队列路由、带重试语义。引擎实现中spawn_invoke_function见 engine/src/engine/mod.rs正是区分这两种模式的地方当invocation_id为Some时Function 完成后会把InvocationResult回传给调用方为None时则是 fire-and-forget对应Voidaction调用被调度到后台任务执行。Trigger 生命周期Trigger 经历四个状态registered已向 Engine 声明active正在监听自己的事件invoked某个事件已经触发了它unregistered已被移除。当拥有某个 Trigger 的 Worker 断开时它名下的所有 Triggers 会随其 Functions 一起自动注销。在 Quickstart 中的三种触发方式Quickstart 用三种不同的方式通过 Trigger 调用 FunctionCLI 直接触发iii trigger math::add a2 b3是一个由 CLI 自身触发的 Trigger。Engine 把调用路由到任何提供math::add的 Worker。SDK 调用触发worker.trigger({ function_id: math::add, ... })是同一个想法的另一种形式——一个 Worker 内的一个 Function 触发调用另一个 Function同样经由 Engine 路由。这两条路径对任何已注册的 Function 都成立无需注册显式 Trigger每个registerFunction()天生就附带一个可用这两种方式调用的 Trigger。HTTP Trigger反应式由httpWorker 通过worker.registerTrigger()完成这也是实现 Trigger 的常见反应式方式。当请求到达POST /math/add-two-numbers时http查找匹配的 Trigger向math::addFunction 发起请求Engine 收到请求并把调用路由给caller-worker响应沿原路返回。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::add、state::get、http::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::add与math::add_two_numbers都是 Function标识符遵循service::namemath命名空间把相关 Functions 归组name标识具体处理器。分组是任意的iii 不强制。Function ID 跨 Worker 重启保持稳定当math-worker停止并重启时调用方无需感知——它们继续调用math::addEngine 把调用路由到当前提供该 Function 的实例。此外Functions 是同步定义的但由于 Trigger 与 Function 之间的解耦它们可以被异步调用。Engine实时注册表与路由协调者Engine 是一个单进程持有所有已连接 Worker 及所有已注册 Functions、Triggers 的注册表Worker 连接时Engine 记录它提供的 FunctionsWorker 断开时Engine 移除它的 Functions、取消这些 Function 的在途调用并通知系统其余部分拓扑已变化。路由与语言、运行时、位置无关。Engine 不需要知道math::add是运行在 Docker、Raspberry Pi 还是浏览器标签页里它只知道某个 Worker 提供了它。同一个教程可以跨不同运行时重新部署而无需改动函数代码。源码视角Engine 的数据结构在 engine/src/engine/mod.rs 中可以看到 Engine 的完整构成Engine结构体其核心字段与文档描述一一对应worker_registry已连接的 Worker 连接注册表functionsFunctionsRegistry函数实时注册表trigger_registryTriggerRegistryTrigger 注册表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_owners与worker_name_owners两张DashMap键为(namespace, 名称)正是这一机制的实现连接断开时cleanup_worker会原子地释放该 Worker 的注册而一个 Worker 快速重启时其占位会被新连接接管从而在「快重启竞态」下不会误删已被另一个存活 Worker 覆盖的注册。相关端到端行为也有对应的测试覆盖例如 namespace_routing_e2e.rs 与 invocation_integration.rs。Engine 的更多细节关于 Engine 的启动流程、配置热加载以及实时注册表与发现面文档指向 Engine 详解 继续深入。总结一套心智模型无数种变体iii 的全部能力都建立在同一个极简心智模型之上Worker连接 EngineWebSocket JSON宣告它能运行的 Functions 与要注册的 TriggersFunction是具名处理器以service::name标识跨语言、跨位置、跨重启稳定Trigger把事件源绑定到function_id决定「何时调用」条件函数进一步把「是否调用」从 Function 中剥离Engine维护实时注册表把每次调用路由到当前提供该 Function 的 Worker并在断开时自动清理。理解这四块之后队列、调度、沙箱、可观测性、Agent 等一切模块都可以还原为「某个 Worker 提供的 Trigger 类型 / Function」的组合。进一步的实战内容可参考Quickstart 教程从零搭建本文贯穿使用的双 Worker 示例使用 iii / WorkersWorker 的安装、锁定版本、日志与沙箱运维创建 Workers / WorkersWorker 连接生命周期与 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),仅供参考
返回列表