SpacetimeDB 入门指南:读懂“既是数据库又是服务器“的实时应用架构
2026/9/13 18:13:36 网站建设 项目流程

SpacetimeDB 入门指南:读懂"既是数据库又是服务器"的实时应用架构

【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB

本篇技术指南以 SpacetimeDB 官方文档 What is SpacetimeDB? 为核心,系统讲解这款开源实时数据库的核心定位、应用工作流、状态镜像(State Mirroring)机制及其与订阅、RLS、Reducers 等概念的协作方式。读完本文,你将掌握 SpacetimeDB 的整体架构心智模型,理解客户端本地缓存、订阅推送、服务端过滤与事务型 Reducer 之间的数据流关系,并能据此规划自己的实时游戏、聊天或协作类应用的数据库方案。

一、定位:一个"既是数据库又是服务器"的运行时

SpacetimeDB 的核心理念可以浓缩为一句话:它是一个完整的数据库,同时也是一个应用服务器。它是一款功能完备的关系型数据库系统,允许你把应用逻辑直接运行在数据库内部,从而不再需要单独部署 Web 服务器或游戏服务器。

这意味着:

  • 应用逻辑(授权、业务规则、数据校验)写在数据库里,而不是写在独立的服务进程里;
  • 整个应用可以用一种语言编写,并作为单个二进制文件部署;
  • 传统的微服务、容器、Kubernetes、Docker、虚拟机、DevOps、基础设施运维等一层层中间件,在 SpacetimeDB 的应用形态下都被省去了。

从官方文档的表述看,这种"单二进制部署"模型,正是 SpacetimeDB 与"数据库 + 独立后端服务"传统组合的关键差异。官方将其 MMORPG BitCraft Online 的整个后端(聊天消息、物品、资源、地形、玩家位置)实现为一个单独的 SpacetimeDB 数据库,用来佐证这套模型承载真实大型实时应用的能力。

服务端模块(Module):Schema 与业务逻辑的统一载体

官方文档在 The Database Module 中明确了模块(Module)与数据库(Database)的区分:

  • 模块(Module):你编写的代码,包含 Schema(表)与业务逻辑(Reducers、Procedures、Views),编译后部署到 SpacetimeDB;
  • 数据库(Database):模块的运行实例,拥有模块的 Schema 与逻辑,外加实际存储的数据和活跃连接。

同一个模块可以部署到多个数据库(例如测试、预发布、生产环境),每个数据库各自独立。更新模块代码并重新发布时,SpacetimeDB 会尝试自动迁移 Schema,已有数据得以保留(复杂 Schema 变更仍需谨慎处理迁移,参见 Automatic Migrations)。

支持的语言:服务端模块与客户端 SDK

根据 Language Support,模块(服务端逻辑)支持三种语言:

语言运行方式适用人群
Rust编译为 WebAssembly追求高性能
C#编译为 WebAssemblyUnity 开发者
TypeScript运行于 V8Web 开发者

客户端 SDK 方面,spacetimeCLI 工具可以自动为你的数据库生成类型安全的客户端代码,支持 Rust、C#、TypeScript,以及面向 Unreal Engine 的 C++/Blueprint 支持。官方文档还特别指出,SpacetimeDB 最初就是为多人 Unity 游戏后端设计的,C# SDK 与 Unity 项目可以无缝集成。

性能取向:内存态 + 提交日志持久化

SpacetimeDB 为极速与最低延迟而优化,而非批处理或分析型负载,适合游戏、聊天、协作工具等实时应用。其速度来源是架构性的:

  • 所有应用状态保存在内存中,读写直接命中内存;
  • 数据同时持久化到提交日志(commit log),用于系统重启或崩溃后恢复数据。

仓库中 commitlog crate 就是该持久化机制的底层实现。而 The Zen of SpacetimeDB 进一步解释了这种设计:SpacetimeDB 持久化一切(包括行变更历史),持久化保证只会增加延迟而不会降低吞吐量——内存保证速度,磁盘保证持久性与恢复能力。

二、应用工作流:客户端视角的完整数据链路

官方文档用一张工作流预览图(workflow-preview-diagram.png)描述了使用 SpacetimeDB 时的完整数据流,共包含四个层次:

  1. 客户端本地缓存的数据视图(Data View):所有客户端读取都发生在本地缓存的数据视图上,读取是即时的内存操作;
  2. 客户端订阅(Subscriptions):客户端通过订阅告诉服务器自己关心哪些数据、希望哪些数据同步进自己的数据视图。数据一旦变化,服务器就会把变更推送到客户端缓存;
  3. 服务端 RLS 过滤:在订阅被评估之前,RLS(Row Level Security,行级安全)过滤器会先在服务端限制数据视图,可用于访问控制或客户端数据范围限定;
  4. Reducers(事务型远程调用):Reducer 本质上是异步 RPC。请求发出后,如果该 Reducer 的结果修改了数据,会被直接写入数据库;这些变更若通过了上面两层(订阅匹配与 RLS 过滤),客户端查询本地缓存时就会看到结果。

与关键概念的对应关系

工作流中的每一层,都可以在版本化文档的 Core Concepts 中找到展开说明:

  • 订阅(Subscriptions):详见 Subscription Reference。订阅把数据库行实时复制到客户端:注册 SQL 查询 → 立即收到全部匹配行 → 行变化时持续收到实时更新 → 通过onInsert/onDelete/onUpdate行回调响应变更。客户端缓存由本地内存维护,读取零网络开销。
  • RLS 过滤:详见 Row Level Security。需要说明的是,该文档在 1.12.0 版本中明确标注 RLS 为实验性不稳定功能(Rust 需开启unstablefeature,C# 需#pragma warning disable STDB_UNSTABLE),官方建议优先使用 Views 做细粒度访问控制,RLS 仅用于 Views 无法覆盖的场景。
  • Reducers:详见 Reducers 与 Key Architecture。每个 Reducer 都在独立且原子的数据库事务中执行:成功则提交全部变更,返回错误或抛异常则整体回滚——不存在"保留一半变更"的中间状态。

三、状态镜像(State Mirroring):免轮询的实时同步

状态镜像是 SpacetimeDB 客户端体验的核心能力。其工作方式如下:

  1. 你用 SQL 查询描述客户端感兴趣的数据(例如玩家角色附近的地形物品);
  2. SpacetimeDB 为相关表在你的客户端语言中生成类型
  3. 数据库状态每次变化时,客户端都会收到一条实时更新流

官方文档特别强调:这是一个只读镜像。修改数据库的唯一途径是提交请求(Reducers),且这些请求会在服务端被校验。

从源码看状态镜像的实现支撑

从当前仓库的源码结构看,状态镜像能力由多个 crate 协同实现:

  • crates/subscription/src:客户端订阅的求值逻辑所在;
  • crates/client-api 与 crates/client-api-messages:定义客户端与主机之间的消息协议;
  • sdks/typescript/src、sdks/rust/src、sdks/csharp/src:各语言客户端 SDK 中SubscriptionBuilder/SubscriptionHandle的落地实现。

以 TypeScript 客户端为例,官方订阅文档给出了完整的订阅与响应代码:

import { DbConnection, User, Message } from './module_bindings'; // 连接到数据库 const conn = DbConnection.builder() .withUri('wss://maincloud.spacetimedb.com') .withModuleName('my_module') .onConnect((ctx) => { // 订阅 users 与 messages ctx.subscriptionBuilder() .onApplied(() => { console.log('Subscription ready!'); // 初始数据此刻已在客户端缓存中 for (const user of ctx.db.user.iter()) { console.log(`User: ${user.name}`); } }) .subscribe(['SELECT * FROM user', 'SELECT * FROM message']); }) .build(); // 响应新行插入 conn.db.user.onInsert((ctx, user) => { console.log(`New user joined: ${user.name}`); });

订阅 API:Builder 与 Handle

订阅 API 由两个核心接口构成:

  • SubscriptionBuilder:注册订阅查询的入口,支持onApplied(订阅应用成功回调)、onError(订阅失败回调)、subscribe(querySqls)(订阅一组 SQL 查询,立即返回)、subscribeToAllTables()(订阅全部表的所有行,仅适合内存与带宽充裕的应用);
  • SubscriptionHandle:管理单个订阅的生命周期,提供isEnded()isActive()unsubscribe()unsubscribeThen(onEnded)。每个订阅生命周期独立,客户端可以按需动态订阅/退订不同的数据子集——例如游戏客户端在玩家达到 6 级后订阅新商店数据、退订旧的 5 级订阅,其他订阅不受影响。

订阅性能最佳实践

官方订阅文档针对"降低服务端计算与序列化开销"给出了四条实践建议,直接关系到大规模实时应用的资源成本:

  1. 编写高效的 SQL 查询:优先利用索引,参考 SQL 参考文档中的性能与扩展性最佳实践;
  2. 将生命周期相同的订阅分组:全局数据(如公告、徽章)与阶段性数据(如按等级解锁的商店物品)分成两个独立订阅,避免反复退订/重订全局数据;
  3. 先订阅、再退订:SpacetimeDB 的订阅是零拷贝的,同一查询被多次订阅不会带来额外处理或序列化开销;更新订阅集时先建立新订阅再退订旧订阅,可避免数据复制间隙;
  4. 避免重叠查询SELECT * FROM UserSELECT * FROM User WHERE id = 5这类可走索引的重叠开销很小;而SELECT * FROM UserSELECT * FROM User WHERE id != 5会让服务器逐行处理两次并重复序列化几乎整张表,应尽量避免。

四、在数据库内"编程":Reducers、Procedures 与 Views

应用逻辑运行在数据库内部,具体表现为三类服务端函数(详见 Key Architecture):

  • Reducer:事务型的数据库修改函数。ReducerContext是其唯一强制参数,携带调用者的 Identity,可用于鉴权。每个 Reducer 在独立原子事务中执行,成功才提交,失败整体回滚;
  • Procedure:可以执行 Reducer 无法执行的外部操作(如发起 HTTP 请求),但不会自动运行在数据库事务中,需要手动开启并提交事务。1.12.0 中 Procedures 仍处于 beta;
  • View:只读的计算查询函数,必须声明为public,可返回单行或多行;与表一样可被订阅,底层数据变化时自动更新。官方在 RLS 文档中推荐用 Views 替代实验性的 RLS 做细粒度访问控制。

模块示例:声明表与 Reducer

以 Rust 模块为例(Key Architecture):

#[spacetimedb::table(name = players, public)] pub struct Player { #[primary_key] id: u64, name: String, age: u32, user: Identity, }
#[spacetimedb::reducer] pub fn set_player_name(ctx: &spacetimedb::ReducerContext, id: u64, name: String) -> Result<(), String> { // ... }

表标记为public后即可被客户端读取;客户端调用 Reducer 时,代码看起来像普通函数调用,底层实际是客户端通过网络发送请求、数据库处理并响应。

事务的边界与"禅意"设计

The Zen of SpacetimeDB 将这套设计归纳为五个核心原则:一切皆表(数据库即全部状态,没有独立的缓存层)、一切皆持久(默认持久化所有历史,内存读取 + 磁盘持久化兼得)、一切皆实时(客户端是服务器的副本,订阅即同步,无需轮询)、一切皆事务(Reducer 要么整体成功要么整体回滚,出错即回滚,无需清理代码)、一切皆可编程(授权与业务规则都是真实代码,具备完整编程语言能力)。

正是"一切皆表 + 一切皆持久"的组合,让 SpacetimeDB 可以在不断开客户端连接的情况下热替换服务端代码——状态都收敛在表中,替换逻辑不丢状态。这对持续迭代的实时应用而言是重要的架构红利。

五、围绕"单一数据库"的组织与运维

既然应用是一个数据库,运维操作也随之收敛到数据库粒度。官方文档(The Database Module)给出了spacetimeCLI 的核心运维命令:

操作命令说明
发布/更新数据库spacetime publish <DATABASE_NAME>创建或更新数据库;重复发布时自动迁移 Schema
删除数据库spacetime delete <DATABASE_NAME>永久删除,不可撤销;脚本中可用--yes跳过确认
执行 SQLspacetime sql <DATABASE_NAME> "SELECT * FROM user"数据库所有者可绕过表可见性限制;--anonymous以匿名客户端身份执行
查看日志spacetime logs <DATABASE_NAME>--follow实时跟随,--num-lines N限制条数
列出数据库spacetime list展示数据库名称、身份与主机

数据库名称必须匹配正则/^[a-z0-9]+(-[a-z0-9]+)*$/(小写字母与数字、以连字符分隔),如my-game-serverchat-app-production。每个数据库创建时还会获得唯一的十六进制 Identity,客户端可用名称或 Identity 连接。

六、总结:如何判断 SpacetimeDB 是否适合你的场景

综合官方文档与仓库实现,SpacetimeDB 的适用画像非常清晰:

  • 适合:实时游戏(尤其是多人游戏)、聊天应用、协作工具等以状态同步和低延迟为核心的场景;希望用单一语言、单一二进制完成端到端开发、免去后端运维的团队;
  • 不太适合:批处理、分析型(OLAP)负载——它明确优化的是"最大速度、最小延迟";
  • 架构取舍:用"内存 + 提交日志"换取实时性,用"一切皆表 + 事务型 Reducer"换取一致性保证,用"订阅 + 状态镜像"换取免轮询的客户端同步。

如果你想继续深入,官方推荐的下一步路径是:通过 Quickstarts 用 Rust/C#/TypeScript 创建第一个模块,然后依次掌握 Tables、Reducers 与 Subscriptions。仓库中的 templates 目录提供了 basic-rs、basic-cs、basic-ts、chat-react-ts 等可直接参考的项目骨架,demo/Blackholio 则是一个跨 Rust/C#/TypeScript 服务的完整演示应用。

【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询