RxDB 作为 RethinkDB 替代方案:离线优先的客户端响应式数据库实战指南 📅 发布时间:2026/9/20 11:57:45 👁 浏览次数: 数据库NoSQL嵌入式数据库实时数据库【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址https://gitcode.com/gh_mirrors/rx/rxdb点击查看免费下载RethinkDB 曾以 changefeed变更推送机制引领了实时数据库的风潮但作为服务端数据库它天然无法满足现代离线优先offline-first应用的需求。本文以 RxDB 仓库中的对比文档为主体系统剖析 RethinkDB 的架构短板并逐一展示 RxDB 如何在客户端实现等效的响应式编程模型——包括响应式查询、离线写入、多后端复制、多标签页同步与冲突解决。读完本文你将掌握用 RxDB 构建断网也能完整工作、联网后自动同步的实时应用的完整方案。什么是 RethinkDBRethinkDB 是 2009 年创立、2012 年公开推出的分布式文档型数据库定位是实时应用。它的标志性能力是changefeed客户端与数据库服务器之间建立持久连接插入、更新、删除等变更事件一旦发生就被实时推送给客户端无需轮询。RethinkDB 使用自研的ReQL查询语言它可链式调用并直接嵌入宿主语言JavaScript、Python、Ruby。一个带 changefeed 的基础查询如下// RethinkDB: subscribe to all changes in the messages table r.table(messages) .changes() .run(connection, (err, cursor) { cursor.each((err, change) { console.log(Old value:, change.old_val); console.log(New value:, change.new_val); }); });changefeed 的思路在当时确实新颖。在此之前构建实时应用通常需要轮询、在普通数据库之上搭建 WebSocket 基础设施或者在存储之上再叠加一层专用的 pub/sub 系统。RethinkDB 把它直接做进了查询层。RethinkDB 时间线2009—— RethinkDB Inc. 成立项目最初是面向 SSD 优化的存储引擎。2012—— RethinkDB 1.0 公开亮相成为带 ReQL 与 changefeed 的实时文档数据库。2013–2015—— 活跃开发期社区持续壮大实时应用团队兴趣浓厚。2016 年 10 月—— RethinkDB Inc. 关停。创始团队发布事后总结post-mortem承认在与 MongoDB 及云托管数据库的竞争中未能建立可持续的商业模式。2017 年 2 月—— Linux 基金会经由 CNCF接管 RethinkDB并以 Apache License 2.0 重新授权社区继续维护。2018 年至今—— 社区贡献者仅提供零星的 bug 修复与维护性版本几乎没有新的功能开发。项目功能稳定但不再积极演进。创始团队的事后总结相当坦诚数据库市场奖励的是运维简单与托管服务相比 Firebase 或后来的 Supabase 等托管方案RethinkDB 运维门槛过高。已在用 MongoDB 的团队也几乎没有理由仅为了 changefeed 而迁移——何况 MongoDB 后来也加入了自己的 change streams 功能。RethinkDB 的强项在联网环境下做服务器到客户端的实时数据流RethinkDB 的架构是干净利落的ReQL 表达力强changefeed 深度融入查询模型分布式架构支持跨节点的分片与复制。对监控传感器数据的仪表盘、假设所有用户都在线的聊天应用这类场景RethinkDB 确实优雅地解决了一个真实问题。RethinkDB 的短板没有离线能力RethinkDB 是服务端数据库应用通过向 RethinkDB 集群发送网络请求来查询数据。用户一旦断网所有读写立即失败。这并非配置问题或缺少插件而是架构上就没有客户端存储没有可离线响应查询的本地缓存网络一断所有 changefeed 全部断开驱动直接报错客户端离线期间发生的变更RethinkDB 也不会为每个客户端缓冲错过的历史事件。配套的客户端库 Horizon提供认证与订阅辅助能力自始至终没有实现离线支持2016 年提出的相关 issue 在公司关停时未获解决即被关闭。对现代 Web 与移动应用而言离线绝非边缘场景——用户在火车上、信号差的建筑里、网络时断时续的环境中打开应用是常态断网即报错的应用体验是难以接受的。changefeed 无法跨断线存活当 changefeed 客户端断开再重连时它不会自动补收离线期间发生的变更。应用必须重新建立连接、重跑查询并自行在最后已知状态与当前服务端状态之间做对账。服务器在内存中最多缓冲changefeed_queue_size默认 100,000 条事件的变更。如果客户端离线时间过长导致缓冲写满服务器会丢弃事件并向客户端返回错误。此时应用拿到的是一份不完整的状态视图只能全量重读。这套架构把大量复杂度推给了应用代码——每个用到 changefeed 的功能都要自行处理重连、回填与缓冲溢出。服务端架构意味着基础设施运维在生产环境运行 RethinkDB意味着要管理一个集群分片、复制因子、服务器拓扑都要手工配置。相比 Firebase、Supabase 这类云托管数据库RethinkDB 把运维责任全部压给了使用团队。这也是创始团队事后总结中提到的败因之一开发者更偏好托管服务因为运维复杂度被抽象掉了。公司关停后RethinkDB 也没有官方支持合同或托管服务。社区维护而非积极开发RethinkDB 由志愿者维护只收 bug 修复、不收新功能。对 Deno、Bun 等新 JavaScript 运行时以及现代生态工具链的驱动支持远不如仍在积极开发的数据库。对 2025/2026 年启动的新项目而言押注一个没有商业背书、没有托管服务、没有功能路线图的数据库是有风险的一旦发现安全漏洞或与新版 Node.js 不兼容修复只能指望没有回应义务的社区志愿者。ReQL 不可移植ReQL 是 RethinkDB 专属的。学到的 ReQL 知识无法迁移到其他数据库切换存储后端时 ReQL 查询也无法复用。相比之下MongoDB 风格的查询语法RxDB 等也实现了该语法拥有大得多的社区知识库。生态数据数据也印证了这一点截至 2026 年 7 月 30 日rethinkdb包在 npm 上最近 30 天的下载量为 65,703 次而rxdb为 270,494 次。RxDB 如何应对同样的问题RxDB 是面向客户端环境的本地优先local-firstJavaScript 数据库覆盖浏览器、React Native、Electron 与 Node.js。它与 RethinkDB 有着相同的响应式目标数据变更应自动传导到 UI但实现方式是把响应式放到客户端而不是依赖一条常驻的服务器连接。无需服务器连接的响应式查询在 RxDB 中每一个查询都是可观察的observable。订阅查询结果时你会立刻收到当前结果集此后无论底层数据发生何种变化——来自本地写入还是复制事件——observable 都会重新发射最新结果import { createRxDatabase } from rxdb/plugins/core; import { getRxStorageIndexedDB } from rxdb/plugins/storage-indexeddb; const db await createRxDatabase({ name: myapp, storage: getRxStorageIndexedDB() }); await db.addCollections({ messages: { schema: { title: message schema, version: 0, primaryKey: id, type: object, properties: { id: { type: string, maxLength: 100 }, text: { type: string }, roomId: { type: string }, createdAt: { type: number } }, required: [id, text, roomId, createdAt], indexes: [roomId, createdAt] } } }); // Subscribe to all messages in a specific room, sorted by creation time db.messages.find({ selector: { roomId: room-42 }, sort: [{ createdAt: asc }] }).$.subscribe(messages { renderChatUI(messages); // called immediately and on every change });这段代码完全离线运行。查询针对的是 IndexedDB或任意配置的存储而不是远程服务器没有需要建立的连接也没有需要处理的断线。RxDB 使用 event-reduce 算法让响应式更新保持高效一次文档写入发生后RxDB 会判断能否直接把变更应用到现有查询结果上从而跳过整条查询的重新执行。从源码看该模块对每个变更事件都会给出一个明确结论——runFullQueryAgain: false时直接产出newResults增量更新仅在无法安全增量时才退化为runFullQueryAgain: true的全量重查。这使得写密集场景下的响应式 UI 更新依然很快。真正的离线优先用户在没有网络的情况下打开 RxDB 应用所有功能照常工作写入落到本地存储查询从本地存储返回UI 渲染时没有任何加载转圈或报错状态。网络恢复后RxDB 的复制插件会在后台把本地变更同步到远端后端用户再次离线本地数据库继续工作。这正是 offline-first 架构 所描述的模式——本地数据库而非服务器成为应用所有持久状态变更的入口。RethinkDB 的架构做不到这一点数据在服务器上离线等于没有数据。想获得真正的离线优先必须额外叠加一层本地存储并自己编写对账逻辑——到那时RethinkDB 只是后端而不是实时客户端数据库。灵活的存储后端RxDB 拥有可插拔的存储层。同一份应用代码可依据运行环境切换到不同的存储引擎环境存储方案浏览器标准IndexedDB浏览器高吞吐OPFSOrigin Private File SystemReact Native / ExpoSQLiteexpo-sqlite 或 op-sqliteNode.js / ElectronSQLitebetter-sqlite3多标签页浏览器SharedWorker测试 / CIMemory切换存储只需在创建数据库时改一个参数import { getRxStorageOpfs } from rxdb/plugins/storage-opfs; const db await createRxDatabase({ name: myapp, storage: getRxStorageOpfs() // use OPFS for better browser performance });可复制到任意后端RethinkDB 同时充当存储层与实时传输层RxDB 则将两者解耦数据存在本地复制到后端是独立、可配置的插件。HTTP 复制插件 对接任意 REST/HTTP 端点GraphQL 复制插件 可连接 GraphQL API包括 AWS AppSyncWebSocket 复制插件 提供来自服务器的低延迟推送CouchDB 复制插件 走 CouchDB 的多主协议你也可以为任意私有 API 实现自定义复制处理器。import { replicateRxCollection } from rxdb/plugins/replication; const replicationState await replicateRxCollection({ collection: db.messages, replicationIdentifier: messages-http-v1, pull: { handler: async (checkpoint, batchSize) { const url /api/messages/changes?since${checkpoint?.updatedAt ?? 0} limit${batchSize}; const response await fetch(url); const data await response.json(); return { documents: data.documents, checkpoint: data.checkpoint }; } }, push: { handler: async (rows) { const response await fetch(/api/messages/push, { method: POST, body: JSON.stringify(rows), headers: { Content-Type: application/json } }); return response.json(); // returns conflicting docs or [] } }, live: true, retryTime: 5000 }); // Observable replication state replicationState.active$.subscribe(active console.log(Syncing:, active)); replicationState.error$.subscribe(err console.error(Sync error:, err));复制状态完全可观察。从复制插件的源码可以看到RxReplicationState内部维护了received、sent、error、canceled、active、conflict共六个 Subject并对外暴露received$、sent$、error$、canceled$、active$、conflict$等 observable。你随时可以精确得知复制何时处于激活状态、何时出错、发了哪些文档、收了哪些文档没有任何隐藏行为。浏览器多标签页支持RethinkDB 是服务器进程没有浏览器标签页的概念。而在客户端多个标签页各自持有独立内存状态是常见的一致性问题的根源。RxDB 用 SharedWorker 存储 解决这个问题所有标签页共享一个运行在 SharedWorker 中的数据库实例任意标签页的写入都会立即反映到其他标签页的响应式查询中import { getRxStorageSharedWorker } from rxdb/plugins/storage-shared-worker; const db await createRxDatabase({ name: myapp, storage: getRxStorageSharedWorker({ workerInput: new SharedWorker( new URL(rxdb/plugins/storage-shared-worker/worker.js, import.meta.url), { type: module } ) }) });对于只需恰好一个标签页执行后台任务比如跑复制的协调场景RxDB 还提供领导者选举插件一个标签页被选举为 leader 并执行后台任务其余标签页等待leader 标签页关闭后另一个会自动接管。该能力对应源码中的waitForLeadership()方法见 src/plugins/leader-election/index.tsimport { RxDBLeaderElectionPlugin } from rxdb/plugins/leader-election; import { addRxPlugin } from rxdb/plugins/core; addRxPlugin(RxDBLeaderElectionPlugin); // Wait until this tab is the leader before starting replication await db.waitForLeadership(); startReplication(db);可观察的变更事件RxDB 在数据库和集合两个层级都暴露了 changestream见 rx-database.md。你可以订阅所有文档变更这与 RethinkDB 的表级 changefeed 类似但事件来自本地数据库而非服务器// Subscribe to all changes in the messages collection db.messages.$.subscribe(changeEvent { console.log(Operation:, changeEvent.operation); // INSERT, UPDATE, DELETE console.log(Document ID:, changeEvent.documentId); console.log(Document data:, changeEvent.documentData); }); // Subscribe to changes on a specific document const doc await db.messages.findOne(message-001).exec(); doc.$.subscribe(updatedDoc { console.log(Document updated:, updatedDoc?.text); });这是 RethinkDB 点级 changefeed 的客户端等价物区别在于这些事件源于本地因此用户离线时依然会触发。冲突解决在实时多用户系统中两个用户可能同时编辑同一文档。RethinkDB 的冲突模型依赖服务器持有唯一权威视图——因为每次写入都立即经过服务器这种模型才能成立。RxDB 则是本地优先模型用户可以在离线时本地编辑联网后这些修改再同步。如果两个客户端在断连期间编辑了同一文档同步时两个版本必须被调和。RxDB 通过可配置的冲突处理器来处理await db.addCollections({ messages: { schema: messageSchema, conflictHandler: async ({ newDocumentState, realMasterState }) { // Keep whichever version was updated more recently if (newDocumentState.updatedAt realMasterState.updatedAt) { return { documentData: newDocumentState }; } return { documentData: realMasterState }; } } });对于合并语义至关重要的协作编辑场景比如两个用户在文本的不同位置做了编辑RxDB 支持基于 CRDT 的冲突解决。CRDT 无需中心权威即可确定性地合并并发编辑。插件通过getCRDTSchemaPart()为 schema 注入 CRDT 字段见 src/plugins/crdt/index.ts并在每次写入时把操作追加到crdtDocField.operations并重算哈希import { getCRDTSchemaPart, RxDBcrdtPlugin } from rxdb/plugins/crdt; import { addRxPlugin } from rxdb/plugins/core; addRxPlugin(RxDBcrdtPlugin); const messageSchema { version: 0, primaryKey: id, type: object, properties: { id: { type: string, maxLength: 100 }, text: { type: string }, roomId: { type: string }, crdts: getCRDTSchemaPart() }, crdt: { field: crdts } };Schema 校验与 TypeScript 支持RxDB 会在写入存储之前用 JSON Schema 校验每个文档。不符合 schema 的文档在数据库层就被拒绝防止脏数据进入本地存储try { await db.messages.insert({ id: msg-001, // text field is required but missing roomId: room-42, createdAt: Date.now() }); } catch (err) { console.error(err); // Schema validation error: missing text }RxDB 还会根据 schema 自动生成 TypeScript 类型为所有集合操作提供 IDE 自动补全与编译期类型安全。Schema 迁移应用演进时数据模型会变化。RxDB 内置了 schema 迁移系统当本地数据库以高于存量数据的 schema 版本打开时RxDB 自动执行迁移。该机制在源码中对应migrationStrategies的注册与按版本顺序执行见 src/plugins/migration-schema/index.ts 及 migration-helpers.tsawait db.addCollections({ messages: { schema: messageSchemaV2, // version: 1 migrationStrategies: { 1: (oldDoc) { // Migrate from version 0: add a roomId field with a default return { ...oldDoc, roomId: oldDoc.roomId ?? general }; } } } });迁移在每台客户端的本地数据上独立运行不需要协调的后端发布。静态加密RxDB 内置了加密插件在写入本地存储之前对单个文档字段加密适合在本地存储敏感用户数据的应用。wrappedKeyEncryptionCryptoJsStorage用密码包装存储密钥并强制密码最短长度MINIMUM_PASSWORD_LENGTH为 8见 src/plugins/encryption-crypto-js/index.tsimport { wrappedKeyEncryptionCryptoJsStorage } from rxdb/plugins/encryption-crypto-js; import { getRxStorageIndexedDB } from rxdb/plugins/storage-indexeddb; const db await createRxDatabase({ name: myapp, storage: wrappedKeyEncryptionCryptoJsStorage({ storage: getRxStorageIndexedDB() }), password: your-encryption-passphrase }); const schema { version: 0, primaryKey: id, type: object, properties: { id: { type: string, maxLength: 100 }, text: { type: string }, private: { type: string } }, encrypted: [private] // stored as ciphertext in IndexedDB };快速上手 RxDB安装 RxDB 与 RxJSnpm install rxdb rxjs创建数据库、插入文档并订阅响应式查询import { createRxDatabase, addRxPlugin } from rxdb/plugins/core; import { RxDBDevModePlugin } from rxdb/plugins/dev-mode; import { getRxStorageIndexedDB } from rxdb/plugins/storage-indexeddb; addRxPlugin(RxDBDevModePlugin); const db await createRxDatabase({ name: chatapp, storage: getRxStorageIndexedDB() }); await db.addCollections({ messages: { schema: { title: message schema, version: 0, primaryKey: id, type: object, properties: { id: { type: string, maxLength: 100 }, text: { type: string }, roomId: { type: string }, createdAt: { type: number } }, required: [id, text, roomId, createdAt], indexes: [roomId, createdAt] } } }); // Write a message await db.messages.insert({ id: msg-001, text: Hello from RxDB!, roomId: room-42, createdAt: Date.now() }); // Reactive query: always reflects the current state db.messages.find({ selector: { roomId: room-42 }, sort: [{ createdAt: asc }] }).$.subscribe(messages { console.log(Current messages:, messages.map(m m.text)); });以上全部离线可用。需要服务器同步时接上一个复制插件即可。对比总结维度RethinkDBRxDB运行位置服务端集群客户端浏览器、移动端、桌面端离线支持无必须联网完整的离线优先响应式查询服务器推送 changefeed 事件客户端 observable 查询基于 RxJS数据存放位置仅远端服务器本地存储IndexedDB、OPFS、SQLite断线处理changefeed 断开错过事件丢失本地数据库继续工作查询语言ReQLRethinkDB 专属MangoMongoDB 兼容 JSON后端依赖必须运行 RethinkDB 集群任意后端或无需后端冲突解决服务器权威最后写入获胜可配置的客户端处理器或 CRDT多标签页支持不适用服务器概念SharedWorker跨标签页共享状态Schema 校验无每次写入强制执行 JSON SchemaSchema 迁移手动内置版本化迁移策略静态加密无内置内置字段级加密插件TypeScript社区维护的类型声明从 schema 自动生成当前状态2017 年起社区维护2016 年起持续积极维护商业支持无公司 2016 年关停有高级插件与活跃开发许可证Apache 2.0Apache 2.0常见问题RxDB 能否复制到 RethinkDB 后端RxDB 没有原生的 RethinkDB 复制插件。如果你在服务器上运行 RethinkDB可以在其前面构建自定义 HTTP 或 WebSocket API再用 RxDB 的自定义复制或 WebSocket 复制插件进行同步。RxDB 的复制协议只要求后端能按给定 checkpoint 提供文档变更并接受推送的文档任何带 RethinkDB 驱动的服务端语言都能暴露这一接口。RxDB 的响应式与 RethinkDB 的 changefeed 有何区别RethinkDB 的 changefeed 从服务器向客户端推送单个变更事件旧值和新值客户端收到的是原始事件必须自行从中维护状态。而 RxDB 的响应式查询在每次相关变更后发射完整、最新的结果集当查询匹配 10 个文档且其中一个更新时订阅者收到的是全部 10 个当前文档。这直接对应 UI 渲染——你始终拥有完整状态而不是一串需要自行应用的增量。event-reduce 算法通过从变更事件直接计算结果集更新、避免对存储重跑完整查询让这一过程保持高效。RxDB 适合实时协作应用吗适合。RxDB 已用于生产环境的协作应用本地数据库保证 UI 始终即时响应复制让所有客户端保持同步。对多个用户并发编辑同一文档的场景RxDB 同时支持自定义冲突处理器和基于 CRDT 的合并SharedWorker 存储模式则处理同一会话内多个浏览器标签页共享状态、避免重复的问题。用户重新联网后 RxDB 如何处理重连RxDB 的复制插件持续运行并自动重试。网络不可用时pull 和 push 处理器会失败RxDB 等待retryTime毫秒后重试网络恢复后复制从最后一个成功的 checkpoint 自动继续。不会有任何变更丢失离线期间的写入都存储在本地连接重建后立即推送到服务器。RxDB 需要登录或用户账户才能工作吗不需要。本地数据库在无任何认证的情况下即可工作。只有复制处理器需要凭据而它们就是普通的 async 函数你在其中携带后端要求的任意 header 或 token。如果应用运行期间认证过期复制会暂停你可以重新提供凭据并继续无需重启数据库。赞分享数据库NoSQL嵌入式数据库实时数据库【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址https://gitcode.com/gh_mirrors/rx/rxdb点击查看免费下载相关推荐用 RxDB 作为 React 应用的客户端数据库离线优先、实时响应与端到端同步实战指南用 RxDB 作为 React 应用的客户端数据库离线优先、实时响应与端到端同步实战指南 本文以 react database.md https://link数据库NoSQL嵌入式数据库实时数据库RxDB 作为 AWS Amplify DataStore 替代方案后端无关的离线优先数据库实践RxDB 作为 AWS Amplify DataStore 替代方案后端无关的离线优先数据库实践 AWS Amplify DataStore 曾经提供了一个诱数据库NoSQL嵌入式数据库实时数据库Terraform Stacks 运行时内部架构解析基于隐式数据流求值的声明式语言运行时Terraform Stacks 运行时内部架构解析基于隐式数据流求值的声明式语言运行时 本文面向 Terraform 的维护者与内核研究者系统剖析 Sta数据库NoSQL嵌入式数据库实时数据库上一篇终极调试神器LLDB-QuickLook10个快速可视化调试技巧下一篇浏览器端AI抠图革命无需服务器3行代码实现专业级背景移除创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考