ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

iii 渐进式迁移指南:三片式策略把既有 HTTP 系统平滑接入 iii,无需大爆炸重写

2026/9/13 8:50:57 拓冰建站 浏览量
iii 渐进式迁移指南:三片式策略把既有 HTTP 系统平滑接入 iii,无需大爆炸重写 iii 渐进式迁移指南三片式策略把既有 HTTP 系统平滑接入 iii无需大爆炸重写【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii本文基于仓库docs/0-13-0教程树中的增量采用Incremental Adoption总览教程讲解如何把 iii 引擎逐步引入一个既有系统先用 iii function 把存量 HTTP 服务包装成可寻址入口再把慢速调用下沉到 iii 队列实现“立即返回 自动重试”最后把持久化状态逐片迁入 iii 的状态原语。读完本文你能掌握一套“每片独立可回滚”的迁移方法论以及每一步对应的 CLI 命令、配置参数和三语言TypeScript / Python / RustSDK 代码。教程核心思想拒绝大爆炸重写原文档开宗明义本教程的目标是“在没有 big-bang rewrite大爆炸式重写的前提下把 iii 增量地引入既有系统”。具体拆解为三个动作包装Wrap用一个 iii function 把存量 HTTP 服务包起来让它成为系统中任何位置都可以通过function_id寻址的对象。此时还没有任何真实流量迁移只是增加了一个“iii 形状”的入口。下沉Offload把长耗时调用挪到队列 worker 之后包装函数立即返回重试由队列托管。迁移持久化Migrate persistence一次只迁移一片状态到 state worker其余系统保持不动直到你准备好迁下一片。原教程的结论部分强调了一个关键工程性质每个切片slice都是独立可逆的。你可以在任意边界处暂停或回滚而不需要停掉系统的其余部分。这是整套方法论能够用于生产系统的根本原因——迁移节奏由你控制而不是被架构一次性切换的窗口期绑架。前置条件引擎、存量服务与 SDK原教程列出三项前置条件一个运行中的 iii 引擎、一个可通过 HTTP 访问的存量服务、以及与服务语言对应的 SDKTypeScript、Python 或 Rust。下面结合仓库文档把第一项“运行中的引擎”展开到可复制操作的程度。启动引擎默认配置或 config.yaml按 安装文档 安装 iii 后最省事的起步方式是使用默认配置iii --use-default-config它会用一套默认 worker 启动引擎适合首跑和 scratch 实验。一旦需要定制端口、适配器或 worker 集合就切换到正式的config.yaml通过--config path指定路径。config.yaml只有一个顶层键workers:列出引擎应加载的 worker每个条目包含nameregistry slug 或本地 worker 名和一个由该 worker 自身定义的config块。仓库文档给出的示例如下workers: - name: iii-http config: port: 3111 host: 127.0.0.1 - name: iii-state config: adapter: name: kv config: store_method: file_based file_path: ./data/state_store.db上面这段配置同时预告了本教程后两步要用的两个关键 workeriii-httpHTTP 接入与iii-state状态原语配合kv适配器的file_based存储。更多细节见 引擎配置文档。另外两点值得注意均来自引擎文档环境变量展开config.yaml支持${VAR:default}语法变量未设置时回落到默认值。用它可以在不同环境间切换端口、URL 和功能开关而不必分叉配置文件例如port: ${HTTP_PORT:3111}。worker 不要求与引擎同机worker 可以部署在任意位置只需要一条到 iii 实例的连接串把它写进config.yaml只是便捷方式。这一点对迁移场景尤其重要——你包装存量服务的 function worker 可以和存量服务放在同一个部署单元里而不必改动引擎侧拓扑。切片一用 iii function 包装既有 API原教程对该切片的一句话定义是“用 iii function 给存量 HTTP 服务做前置使它能被function_id寻址此时不搬任何流量只是增加一个 iii 形状的入口。”包装的动作本体就是在一个 worker 中注册函数。函数 id 遵循service::name形式handler 接收调用 payload 并返回结果。以 TypeScript 为例摘自 Functions 文档import { registerWorker } from iii-sdk; const worker registerWorker(process.env.III_URL); // 包装后的入口内部转发到你的存量 HTTP 服务 worker.registerFunction(legacy::process, async (payload: { id: string }) { const res await fetch(http://your-existing-service/api/process/${payload.id}); return res.json(); });Python 与 Rust 的等价写法import os from iii import register_worker, InitOptions worker register_worker( os.environ.get(III_URL), InitOptions(worker_namelegacy-wrapper), ) def process_handler(payload: dict) - dict: # 内部转发到你的存量 HTTP 服务 ... worker.register_function(legacy::process, process_handler)use iii_sdk::{InitOptions, RegisterFunction, register_worker}; let url std::env::var(III_URL).expect(III_URL must be set); let worker register_worker(url, InitOptions::default()); worker.register_function(RegisterFunction::new(legacy::process, |input: ProcessInput| { // 内部转发到你的存量 HTTP 服务 Ok(serde_json::json!({ ... })) }));这里有两条来自仓库文档的重要约束直接影响包装层的写法函数与触发器解耦同一个函数可以被多种触发器同时调用CLI 直接调用、HTTP 路由、cron 计划等handler 本身不需要为调用来源做任何分支。也就是说包装函数上线后无论调用方来自 CLI、HTTP 还是队列寻址方式都是同一个function_id——这正是“包装后不搬流量、但系统已获得 iii 形状入口”的机制基础。Schema 目前只是元数据函数可以为请求 payload 和响应携带 JSON Schema随函数一起存储供 console 和 agent 可读的 skill 使用但引擎暂不做运行时校验不会拒绝不符合 schema 的 payload 或响应。把 schema 当作面向调用方、agent 和 console 的契约文档即可。该切片的仓库教程入口为 wrap-existing-api注意在当前仓库快照中该子教程正文尚为占位状态上述包装写法以 Functions 文档为准。切片二把慢活下沉到 iii 队列原教程对该切片的定义“把长耗时调用挪到队列 worker 之后让包装函数立即返回重试由队列托管。”这正是包装层暴露出的痛点——legacy::process如果底层服务响应慢HTTP 调用方就必须干等。队列切片的解法是引入queueworkeriii worker add queuequeueworker 把生产者与消费者解耦函数向一个命名 topic 发布消息后立即返回订阅该 topic 的函数在后台处理消息且自带重试与死信队列DLQ。队列配置queue_configs 全参数队列在queueworker 的queue_configs下声明仓库文档给出的示例- name: queue config: queue_configs: legacy-jobs: max_retries: 3 concurrency: 10 type: standard每条queue_configs条目的完整参数摘自 Queues 文档字段默认值说明max_retries3消息转入 DLQ 前的投递尝试次数。concurrency10并行处理的任务数必须至少为 1schema 会拒绝0所以不能靠它暂停队列。fifo队列会强制其为1。typestandardstandard并发或fifomessage group 内有序。message_group_field无fifo必填payload 中决定排序分组的字段。backoff_ms1000重试基础延迟毫秒指数退避。poll_interval_ms100worker 轮询间隔毫秒。用 Enqueue 动作调用包装函数把切片一注册好的包装函数接入队列方式与普通worker.trigger调用完全相同唯一区别是附带TriggerAction.Enqueueimport { TriggerAction, type EnqueueResult } from iii-sdk; const { messageReceiptId } await worker.triggerunknown, EnqueueResult({ function_id: legacy::process, payload: { id: job-123 }, action: TriggerAction.Enqueue({ queue: legacy-jobs }), // 指定队列名 }); // messageReceiptId 标识这条已入队的任务from iii import TriggerAction receipt worker.trigger({ function_id: legacy::process, payload: {id: job-123}, action: TriggerAction.Enqueue(queuelegacy-jobs), }) # receipt[messageReceiptId] 标识这条已入队的任务use iii_sdk::TriggerAction; use iii_sdk::protocol::TriggerRequest; use serde_json::json; let receipt worker .trigger(TriggerRequest { function_id: legacy::process.to_string(), payload: json!({ id: job-123 }), action: Some(TriggerAction::Enqueue { queue: legacy-jobs.to_string() }), timeout_ms: None, }) .await?; // receipt[messageReceiptId] 标识这条已入队的任务返回值中的messageReceiptId是入队任务的唯一标识调用方在拿到它的瞬间就已经可以返回——“慢活异步化”的调用侧契约到此成立。消费语义返回即确认抛错即重试队列还以 pub/sub 形态工作消费者通过注册durable:subscriber类型的 Trigger 绑定到 topic引擎对每条消息执行一次被订阅的函数并把发布的data作为 payload 传入。语义规则非常简洁且可预期正常返回确认ack消息抛出异常拒绝nack消息触发重试最终进入 DLQ。TypeScript 消费者示例摘自 Queues 文档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: legacy-worker }); // 收到每条消息的 data worker.registerFunction(legacy::process, async (msg: { id: string }) { // 在这里干活抛错即 nack消息会被重试 return { done: true }; }); worker.registerTrigger({ type: durable:subscriber, function_id: legacy::process, config: { topic: legacy-jobs }, });对迁移场景的意义在于存量服务的不稳定超时、5xx不需要你手写重试循环或补偿逻辑max_retries 指数backoff_ms DLQ 构成了队列内建的故障吸收层。该切片教程入口为 offload-to-queue当前仓库快照中同样为占位状态队列参数与代码以 Queues 文档为准。切片三把持久化逐片迁入状态原语原教程对该切片的定义“一次只迁移一片状态到 state worker在你准备好迁移下一片之前其余系统保持不动。”承接前置条件中config.yaml的示例iii 的状态原语由iii-stateworker 提供通过adapter选择存储后端文档示例展示了kv适配器配合file_based存储方式- name: iii-state config: adapter: name: kv config: store_method: file_based file_path: ./data/state_store.dbfile_based意味着状态落在本地文件./data/state_store.db是迁移起步期最低成本的存储选择当规模或可用性要求提高时只需调整adapter配置切换后端而 worker 侧访问状态原语的代码路径不变。这也呼应了引擎文档的说明worker 的配置 schema 以各自 worker 的文档页为准见 Worker Registry。“逐片”的含义落在操作层面先选一个边界清晰的状态域例如某个实体表或某类计数把它读写切换到 state worker验证行为一致后再动下一片。由于前两个切片已经保证了入口寻址切片一与异步可靠性切片二都走在 iii 上状态迁移失败时的回退动作就是“把该片的读写指回原存储”不会牵连其他片。切片教程入口为 migrate-persistence。总结可逆切片构成的迁移路径按顺序走完三个子教程后系统就逐片运行在 iii 之上。把全文的技术主线压缩成一张边界图切片动作入口文档回退方式一包装注册 iii function 前置存量 HTTP 服务wrap-existing-api删掉/停用包装 worker流量路径不变二下沉queueworker TriggerAction.Enqueue异步化慢调用offload-to-queue把 trigger 改回同步直调三持久化状态逐片迁入iii-statekv适配器起步migrate-persistence该片读写指回原存储三个切片共享同一套机制底座function_id寻址让系统内所有调用点可以无感切换queueworker 的重试/DLQ 语义把可靠性从应用层代码转移到平台层iii-state的适配器设计让存储后端可替换。每个切片边界都可独立暂停或回滚这正是原教程结论所强调的——不需要任何一次性的全量切换窗口。教程总览原文见 incremental-adoption/overview配套的函数注册与触发器细节可继续查阅 Functions 与 Queues。【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考