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

发布时间:2026/9/18 13:57:14

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),仅供参考
延伸阅读

更多相关文章

2026/9/18 13:57:14

AI+零代码重塑2026年中秋营销:完整路径解析

做了几年企业营销服务,我自己最深的感受是:节日营销这两年的打法变化,比过去十年的变化还要大。尤其是2026年中秋营销,市场人面对的不只是海报、文案和活动页,而是一整套从内容生产、用户触达到效果复盘的高频运转链路…

2026/9/18 13:52:12

llvmpipe背后的LLVM 15.0.7:软件渲染与SIMD向量宽度解析

我是在一份崩溃日志里第一次完整看到llvmpipe (LLVM 15.0.7, 256 bits)这一串描述的。日志来自一台没有独立显卡的测试服务器,程序走了软件渲染,Mesa 把 llvmpipe 的驱动信息打到了诊断输出里。当时我正好在折腾从 GitHub 拉回来的 llvm-project 15.0.7&…

2026/9/18 15:12:25

银行卡号识别银行名称:BIN号匹配与前后端实现详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 15:12:25

Isaac Gym与MuJoCo双引擎协同训练机器狗的实战指南

1. 项目概述:为什么机器狗训练必须先在“数字孪生”里跑通千次?你拆过宇树Go2或B1的SDK文档吗?里面那几行写着“支持ROS2接口”“兼容Gazebo仿真”的小字,背后藏着一个残酷现实:真机调试一次跌倒,电机过热保…

2026/9/18 15:12:25

散列冲突处理全解:链地址法与开放地址法及工程选型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 15:12:25

Java仓库管理系统设计与实现:库存流水与并发控制

简介:基于Java的仓库管理系统设计与实现文档,是一篇面向计算机科学与技术专业本科毕业设计的完整论文,针对电子商务迅猛发展下传统仓库管理依赖人工、效率低下且错误率高的痛点,提出了基于Spring Boot、Vue和MySQL的现代化系统设计…

2026/9/18 15:07:24

从MBR到GPT:手写主引导记录,掌控计算机启动第一棒

大学时第一次读到《操作系统真象还原》第二章,看到“编写MBR主引导记录,让我们开始掌权”这句话时,我整个人是有点被击中感觉的。因为在之前所有编程学习里,我们写的东西都跑在操作系统之上,你调用printf、你new一个对…

2026/9/18 14:13:01

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/18 0:01:09

Google Colab 实战:运行模型、数据加载与报错排查

1. 为什么我劝你先搞懂 Colab 的运行模型1.1 Colab 到底是什么,跟本地跑代码差在哪Google Colab 简单说就是一台跑在浏览器里的 Linux 虚拟机,你打开一个 Notebook,背后就连上了一台带 GPU 的远程机器。你在单元格里敲的每一行 Python&#x…

2026/9/18 0:01:09

C语言数据类型与表达式详解

1. C语言数据与数据类型概述在C语言编程中,数据是程序处理的核心对象。理解数据的分类和特性是掌握C语言的基础。C语言中的数据主要分为四大类:常量、变量、表达式和函数。这些数据类型构成了C语言程序的基本元素,每种类型都有其独特的特性和…

2026/9/18 0:01:09

SQL时间字段指定时间段查询:区间语义、索引与时区避坑

上周排查一个线上问题&#xff0c;用户反馈"昨天的订单一条都没查到"&#xff0c;但数据库里明明躺着两千多条。最后定位下来&#xff0c;不是数据丢了&#xff0c;也不是接口挂了&#xff0c;而是那个查询条件把时间段写成了> 2024-05-20 00:00:00 AND < 2024…

2026/9/18 14:13:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/18 14:13:02

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/18 14:13:02

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码