ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Rivet Actors 状态管理实战:从本地开发到 Render 云端部署(state-render 示例全解析)

Rivet Actors 状态管理实战:从本地开发到 Render 云端部署(state-render 示例全解析) Rivet Actors 状态管理实战从本地开发到 Render 云端部署state-render 示例全解析【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actorsRivet Actors 是面向 AI Agent、协同应用与持久化执行场景的有状态工作负载原语。本指南以仓库中的examples/state-render示例为核心讲解 RivetKit 中 Actor 状态的自动持久化与恢复机制、类型安全的state/actions/events声明方式以及如何通过 Hono 生产级 HTTP 服务器、Vite 构建和render.yamlBlueprint 将同一个示例一键部署到 Render。读完本文你将掌握本地开发 → 云端联调 → 自动化部署的完整链路并能直接复用这套模式到自己的项目。背景state-render与state示例的关系examples/state-render是 examples/state 示例的 Render 优化版本。它的定位差异在 README 开头的注释中写得很明确增加了生产级 HTTP 服务器基于 Hono hono/node-server引入Vite 构建流程把 React 前端编译为静态资源提供render.yamlBlueprint用于一键部署到 Render 平台。而examples/state侧重于本地开发与测试它通过registry.start()直接在进程内启动 Actor 运行时并配有 examples/state/tests/chat.test.ts 的 Vitest 测试套件。两个示例共享同一套核心业务代码——chatRoom聊天室 Actor区别仅在于运行与交付方式。注意原 README 中的cd rivet/examples/state路径在本文所属仓库中对应的是examples/state-render目录本文以下命令均以该目录为准。核心概念Actor 状态如何做到自动保存与恢复在 Rivet Actors 中状态管理遵循声明式 自动持久化的模型核心要点如下持久化状态Persistent stateActor 的state会跨重启自动保存与恢复。服务器重启、Actor 休眠再唤醒后消息列表依然完整存在。类型安全Typed state状态对象是强类型的 TypeScript 结构编译期即可发现字段拼写错误或类型不匹配。状态初始化State initialization通过state属性或createState工厂定义初始值首次创建 Actor 时生效。自动序列化Automatic serialization对c.state的任何修改都会自动持久化无需手动调用save()之类的 API。这一零手动保存的设计在 examples/state-render/src/actors.ts 的注释中有直接体现// State changes are automatically persisted对应源文件中的sendMessage实现即c.state.messages.push(message)之后框架自动落盘。深入源码chatRoomActor 的完整定义examples/state-render/src/actors.ts是整个示例的业务核心。它用actor({...})声明了一个聊天室 Actor包含三类声明式构件import { actor, event, setup } from rivetkit; export type Message { id: string; sender: string; text: string; timestamp: number; }; export const chatRoom actor({ state: { messages: [] as Message[], }, events: { newMessage: eventMessage(), messagesCleared: event[](), }, actions: { sendMessage: (c, sender: string, text: string) { const message: Message { id: crypto.randomUUID(), sender, text, timestamp: Date.now(), }; c.state.messages.push(message); c.broadcast(newMessage, message); return message; }, getMessages: (c) c.state.messages, clearMessages: (c) { c.state.messages []; c.broadcast(messagesCleared); return { success: true }; }, }, }); export const registry setup({ use: { chatRoom }, });逐段拆解这段代码state初始状态为{ messages: [] }messages是Message[]数组。Message类型包含idUUID、sender发送者、text文本、timestamp毫秒时间戳。events声明两个可广播的事件——newMessage携带Message负载与messagesCleared空负载。客户端通过useEvent订阅它们实现实时刷新。actions暴露给客户端的可调用方法sendMessage(c, sender, text)生成消息对象push进c.state.messages自动持久化随后c.broadcast(newMessage, message)推送给所有已连接客户端最后把消息返回给调用方getMessages(c)返回全部消息供客户端首屏加载历史clearMessages(c)清空状态数组并广播messagesCleared事件。registry setup({ use: { chatRoom } })把 Actor 注册进运行时供服务器路由与客户端 SDK 使用。从源码结构看setup返回的registry同时承载了handlerHTTP 处理函数与start()进程内启动两条路径分别对应云端部署与本地开发两种模式。事件广播与连接管理c.broadcast的客户端视角c.broadcast(newMessage, message)会把事件推送给所有订阅了该 Actor 实例的连接。在 examples/state-render/frontend/app/App.tsx 中前端通过chatRoom.useEvent(...)订阅chatRoom.useEvent(newMessage, (msg: Message) { setMessages((prev) [...prev, msg]); }); chatRoom.useEvent(messagesCleared, () setMessages([]));结合useActor({ name: chatRoom, key: [lobby] })可以看出调用链前端以固定的实例 Key[lobby]获取/创建chatRoomActor 实例所有连接到该 Key 的客户端共享同一份持久化状态与事件流。前端还通过chatRoom.connection.getMessages()在连接建立后拉取历史消息作为事件流的初始化补齐。双模式启动逻辑本地直跑与云端 HTTP 服务的切换examples/state-render/src/index.ts通过环境变量在两种模式间自动切换import ./env.ts; import { registry } from ./actors.ts; import { port, useRivetCloud } from ./env.ts; if (useRivetCloud) { const { serve } await import(hono/node-server); const { default: app } await import(./server.ts); serve({ fetch: app.fetch, port }, () { console.log(state-render listening on http://0.0.0.0:${port}); }); } else { registry.start(); }而 examples/state-render/src/env.ts 定义了切换条件export const port Number(process.env.PORT) || 6420; export const useRivetCloud process.env.NODE_ENV production Boolean(process.env.RIVET_ENDPOINT);本地开发NODE_ENV非 production走registry.start()由 RivetKit 运行时自带的管理服务器处理/actors、/metadata、/health等路由监听6420端口可用RIVET_MANAGER_PORT覆盖见 examples/state-render/vite.config.ts 中的RIVET_MANAGER_PORT读取逻辑。云端部署NODE_ENV production且设置了RIVET_ENDPOINT则启动 Hono 服务器将/api/rivet/*代理给registry.handler并托管 Vite 构建出的静态前端。生产 HTTP 服务器Hono 路由与静态资源托管examples/state-render/src/server.ts 是云端模式下的入口服务器import { serveStatic } from hono/node-server/serve-static; import { Hono } from hono; import { registry } from ./actors.ts; const app new Hono(); app.all(/api/rivet/*, (c) registry.handler(c.req.raw)); app.get(/health, (c) c.json({ status: ok })); app.use(/*, serveStatic({ root: ./public })); app.get(*, serveStatic({ root: ./public, path: /index.html })); export default app;各路由职责/api/rivet/*把 RivetKit 的 HTTP/WebSocket 请求原样转交给registry.handler这是前端 SDK 与 Actor 运行时通信的桥梁/actors等 RPC 端点都经由此处/health健康检查端点返回{ status: ok }对应 Render Blueprint 中的healthCheckPath: /health静态资源优先按路径匹配./public下的文件未命中的路径回退到index.htmlSPA 路由兜底。./public目录由 Vite 构建产物输出。在 examples/state-render/vite.config.ts 中可以看到关键配置build.outDir: public且emptyOutDir: true构建时清空并重写该目录通过define将import.meta.env.VITE_RIVET_PUBLIC_ENDPOINT编译期注入为VITE_RIVET_PUBLIC_ENDPOINT || RIVET_PUBLIC_ENDPOINT的值manualChunks将 react、rivetkit、render-dds 拆分为独立 vendor chunk优化首屏加载。前端客户端连接地址的智能推导examples/state-render/frontend/app/rivet-client.ts 负责推导 SDK 的连接基地址export function rivetClientBase(): string { if (import.meta.env.DEV) return http://localhost:6420; const fromBuild import.meta.env.VITE_RIVET_PUBLIC_ENDPOINT as | string | undefined; if (fromBuild fromBuild.length 0) { return fromBuild.replace(/\/$/, ); } return window.location.origin; }开发模式import.meta.env.DEV直连本地http://localhost:6420配合 Vite dev server 的代理见 examples/state-render/vite.config.ts 中/actors、/metadata、/health的proxy配置与ws: true的 WebSocket 透传生产模式优先使用构建期注入的VITE_RIVET_PUBLIC_ENDPOINT源自环境变量RIVET_PUBLIC_ENDPOINT去掉末尾/后作为基地址未设置时回退到window.location.origin即前端与 Rivet 服务同域部署的场景。测试先行用setupTest验证持久化行为虽然state-render面向部署但其业务逻辑在 examples/state/tests/chat.test.ts 中有完整的 Vitest 测试佐证可作为状态自动持久化这一特性的可验证依据。核心测试用例包括发送与接收两个客户端getOrCreate([room1])连接到同一房间client1.sendMessage(Alice, Hello!)后client2.getMessages()能取到该消息且消息结构符合Message类型id、sender、text、timestamp持久化向[persistent-room]依次发送 3 条消息再从另一个客户端实例getOrCreate([persistent-room])读取仍能拿到全部 3 条——这正是状态跨实例、跨重启保留的验证消息排序连续发送 5 条消息后getMessages()严格保持发送顺序且timestamp单调不减清空clearMessages()返回{ success: true }之后getMessages()返回空数组多房间隔离room1与room2的状态互不可见每个实例 Key 对应独立的状态空间。测试统一使用setupTest(ctx, registry)建立隔离环境说明该状态模型是可单测、可复现的——这也是把自动持久化当作工程事实而非黑盒魔法的关键证据。一键部署render.yaml Blueprint 全解examples/state-render/render.yaml 是 Render 的 Blueprint 声明文件遵循 Render Blueprint 规范完整内容如下services: - type: web name: state-render runtime: node plan: free region: oregon buildCommand: npm ci --includedev npm run build startCommand: npm start healthCheckPath: /health envVars: - key: NODE_VERSION value: 22.12.0 - key: NODE_ENV value: production - key: RIVETKIT_STORAGE_PATH value: /tmp/rivetkit - key: RIVET_ENDPOINT sync: false - key: RIVET_PUBLIC_ENDPOINT sync: false逐项解读字段值说明typeweb常驻 Web 服务runtimenodeNode.js 运行时planfree免费套餐即可运行regionoregon部署区域buildCommandnpm ci --includedev npm run build安装含 devDependencies 的依赖构建需要 Vite/tsx再执行vite buildstartCommandnpm start对应package.json中的tsx src/index.tshealthCheckPath/health对应server.ts中的健康检查路由NODE_VERSION22.12.0与 package.json 中engines.node 22.0.0匹配NODE_ENVproduction触发useRivetCloud的云端模式分支RIVETKIT_STORAGE_PATH/tmp/rivetkit本地 Actor 状态存储路径Render 免费套餐的可写临时目录RIVET_ENDPOINT/RIVET_PUBLIC_ENDPOINTsync: false由用户在 Render 控制台手动填写不自动生成部署步骤如下将仓库推送到 GitHub/GitLab/Bitbucket在 Render 控制台Blueprints New Blueprint Instance选择该仓库并 Apply若从 monorepo 部署将Root Directory设置为examples/state-render在 Render 服务中填写两个环境变量来自 Rivet Cloud 项目的 Rivet Cloud 后台变量描述RIVET_ENDPOINTRivet Cloud 项目的后端端点 URLRIVET_PUBLIC_ENDPOINTRivet Cloud 项目的公网端点 URL在 Rivet 控制台中将Connect your backend指向 Render 服务的 HTTPS 地址完成双向联调。RIVET_ENVOY_VERSION 的自动派生机制README 特别强调RIVET_ENVOY_VERSION会从 Render 的RENDER_GIT_COMMIT自动派生每次部署无需手动 bump。其实现位于 examples/state-render/src/env.tsfunction ensureRivetEnvoyVersion(): void { if (process.env.RIVET_ENVOY_VERSION) return; if (process.env.RIVET_RUNNER_VERSION) { process.env.RIVET_ENVOY_VERSION process.env.RIVET_RUNNER_VERSION; return; } const sha process.env.RENDER_GIT_COMMIT; if (sha /^[0-9a-f]{7,40}$/i.test(sha)) { const n Number.parseInt(sha.slice(0, 8), 16); process.env.RIVET_ENVOY_VERSION String(n 0 ? n : 1); } }逻辑分三层显式设置过RIVET_ENVOY_VERSION则直接沿用优先级最高其次复用RIVET_RUNNER_VERSION最后把RENDER_GIT_COMMIT的 SHA 前 8 位当作十六进制整数解析转换为数字版本号非正数时兜底为1。这样每次 Git 提交对应的部署都会产生唯一版本保证 Envoy 侧能区分新旧部署。如需覆盖显式设置该环境变量即可。本地开发运行指南前置条件Node.js ≥ 22见 package.json 的engines字段render.yaml中亦指定NODE_VERSION22.12.0npm ≥ 10使用npm ci需要锁文件。启动开发服务器git clone https://github.com/rivet-dev/rivet.git cd rivet/examples/state-render npm install npm run devnpm run dev由concurrently并行启动两个进程见 package.json scriptstsx --watch src/index.ts以 watch 模式运行 Actor 运行时监听6420端口vite启动前端 dev server并将/actors、/metadata、/health代理到127.0.0.1:6420WebSocket 也通过ws: true透传。生产构建与本地预览npm run build # vite build → 输出到 public/ npm start # tsx src/index.ts以云端模式运行需设置 NODE_ENVproduction 与 RIVET_ENDPOINT运行测试# 在 examples/state 目录下测试套件所在处 npm install npm run test测试基于rivetkit/test的setupTest与 Vitest覆盖消息收发、持久化、排序、清空与多房间隔离见 examples/state/tests/chat.test.ts。可复用的工程模式总结从state-render中可以提炼出一套可直接复用的模板业务与交付分离把 Actor 定义actor/setup放在src/actors.ts本地直跑与云端 HTTP 两种模式由env.ts的环境变量开关决定业务代码零改动前端连接地址分层rivetClientBase()按dev 直连 → 构建期注入 → 同域回退三级推导适配本地联调与生产部署Blueprint 即基础设施render.yaml把构建命令、启动命令、健康检查与环境变量声明化配合RIVET_ENVOY_VERSION的自动派生实现提交即部署、部署即版本化状态行为可测试用setupTest Vitest 在无网络环境下验证持久化、排序与隔离语义让自动保存这一特性有据可查。更进一步RivetKit 还提供状态管理之外的能力如 actions、events、lifecycle hooks相关文档位于仓库 docs-internal 目录与各示例项目中例如 examples/state-render/frontend/app/App.tsx 中使用的useActor/useEvent模式在 examples/chat-room 等示例中有更多变体可作为后续深入方向的参考。【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表