- 后端
- 数据库
【免费下载链接】sharedb
Realtime database backend based on Operational Transformation (OT)
ShareDB 是一个基于操作转换(Operational Transformation,OT)的实时数据库后端,而Backend类正是其**服务端单实例(server-side instance)**的化身:它负责与客户端建立连接、把读写请求派发给数据库适配器,同时承担构造期配置、中间件注册以及投影(projection)定义等工作。本文以官方 API 文档 docs/api/backend.md 为主线,逐项讲解Backend的构造选项、公开属性与全部常用方法,并结合 lib/backend.js 的源码实现与 test/backend.js 测试用例,说明每个 API 的真实调用链与注意事项。读完本文,你将能够独立完成 ShareDB 服务端的初始化、适配器挂载、中间件接入、投影配置,以及服务端直连模式下的文档读写与查询。
Backend() 构造函数
Backend通过sharedb包导出,直接new即可:
var Backend = require('sharedb') new Backend([options])在源码中(lib/backend.js),构造函数会依次完成默认值注入、内部状态初始化(projections、middleware、agentsCount等)以及错误处理器的绑定。构造函数的全部可选参数如下。
构造选项总览
db—— 数据库适配器(可选)
- 默认值:
new MemoryDB()(一个全新的内存数据库实例) - 作用:ShareDB 的数据存储层,即数据库适配器实例,负责持久化文档内容与操作(op)日志
⚠️重要警告:默认的内存适配器是非持久化的。内存数据库把所有文档与操作都存放在进程内存中(见 lib/db/memory.js 的注释:内存占用无上限增长、无法跨 Node 进程扩展、服务重启即丢数据),绝不能用于生产环境。生产环境应显式传入如sharedb-mongo、sharedb-postgres等外部适配器。
var backend = new Backend({ db: new MemoryDB() })pubsub—— 发布/订阅适配器(可选)
- 默认值:
new MemoryPubSub()(内存 Pub/Sub 实例) - 作用:用于在多个 ShareDB 实例之间广播数据变更通知的通道(见 Pub/Sub 适配器)
与数据库适配器不同,文档明确说明:内存 Pub/Sub 适配器在单机独立部署的生产环境中是允许使用的,因为 Pub/Sub 状态无需跨进程持久化。只有当需要横向扩展为多个服务器进程时,才需要换成外部的 Pub/Sub 适配器(如 Redis 类实现)。内存实现本身是完整可用的(lib/pubsub/memory.js)。
milestoneDb—— 里程碑数据库适配器(可选)
- 默认值:
null(源码中实际注入为new NoOpMilestoneDB(),见 lib/milestone-db/no-op.js) - 作用:里程碑快照(milestone snapshots)的数据存储,里程碑快照是按指定版本间隔保存的文档历史快照
如果省略该选项,里程碑快照将不启用,但文档历史仍可访问,只是可能带来性能损耗。
extraDbs—— 附加数据库集合(可选)
- 默认值:
{} - 作用:一个对象,其值可以是额外的
DB适配器实例,用于查询场景。对象的键即为查询选项db字段中可传入的名字
在源码的查询路径中(lib/backend.js),queryFetch/querySubscribe会读取options.db,并到backend.extraDbs[options.db]中查找对应的适配器;找不到时会抛出ERR_DATABASE_ADAPTER_NOT_FOUND错误。
suppressPublish—— 是否抑制发布(可选)
- 默认值:
false - 作用:设为
true时,所有已提交的变更不会通过 Pub/Sub 对外发布。适用于希望写入数据库但不通知其他订阅实例的场景
maxSubmitRetries—— 提交最大重试次数(可选)
- 默认值:
null - 作用:允许一次提交(submit)被重试的次数。省略时,请求将被无限次重试
这对应 OT 并发冲突处理的底层机制:当两个客户端同时提交基于同一版本的 op 时,后提交者需要基于新版本重新转换并重试提交(详见SubmitRequest的提交逻辑)。
presence—— 启用在线状态(可选)
- 默认值:
false - 作用:设为
true时启用 Presence(在线光标、在线用户列表等实时状态)功能。源码中对应this.presenceEnabled = !!options.presence(lib/backend.js),且Agent._handleMessage中只有在presenceEnabled为真时才会处理 presence 相关的消息类型(lib/agent.js)
doNotCommitNoOps—— 是否不提交空操作(可选)
- 默认值:
false - 作用:设为
true时,将避免把 no-op(空操作,或经转换后变成 no-op 的操作)提交到数据库。客户端提交的 no-op 会像正常提交一样被确认(ack),但文档版本不会递增。该选项对需要严格控制版本号增长的场景非常有用
errorHandler—— 非致命错误处理器(可选)
- 默认值:ShareDB 默认将错误写入日志(源码默认实现即
logger.error(error),见 lib/backend.js) - 作用:ShareDB 服务端运行中出现的非致命错误都会被传入该函数
function(error, context) { logger.error(error) }在 test/backend.js 中可以看到new Backend({errorHandler: handler})的用法,测试通过自定义 handler 捕获并断言服务端错误。当需要把服务端错误接入自己的监控/报警体系时,自定义该选项是标准做法。
属性
MIDDLEWARE_ACTIONS—— 中间件动作映射
Backend暴露一个MIDDLEWARE_ACTIONS对象,其值为可用的中间件动作名映射。在源码中(lib/backend.js),它定义了 13 个动作,完整列表如下:
| 动作 | 触发时机 |
|---|---|
afterWrite | 一个操作成功写入数据库之后 |
apply | 一个操作即将被应用到快照上、尚未提交到数据库 |
commit | 一个操作已应用到快照、即将写入数据库 |
connect | 新客户端连接到服务器 |
op | 一个操作从数据库加载出来 |
query | 一个查询即将发送到数据库 |
readSnapshots | 快照已从数据库读取、即将返回给客户端 |
receive | 收到来自客户端的消息 |
reply | 即将向客户端发送非错误回复 |
receivePresence | 服务器收到 presence 信息 |
sendPresence | 即将向客户端发送 presence 信息 |
submit | 一个操作已提交到服务器 |
此外,源码还定义了SNAPSHOT_TYPES(current、byVersion、byTimestamp),用于标识readSnapshots中间件中快照的读取类型,具体可参考 docs/middleware/actions.md。
中间件的注册与触发分别由use()与trigger()完成:trigger会把request.action、request.agent、request.backend挂到上下文上,并串行执行该动作下的所有中间件函数(lib/backend.js)。
方法
connect()
建立与 ShareDB 的连接,返回一个用于与 ShareDB 交互的Connection客户端实例。它是浏览器端new Connection(socket)的服务端等价物——也就是说,你可以在 Node 服务端直接创建并操作文档对象,而不必经过网络。
backend.connect([connection [, request]])参数说明:
connection(可选,Connection实例):要绑定到Backend的连接。默认会新建一个Connection实例。request(可选,Object,默认{}):连接上下文对象,可携带 cookie、会话数据等信息,供中间件中通过agent.custom读取。
返回值:一个Connection。
从源码看(lib/backend.js),connect()内部会创建一个StreamSocket并调用listen()生成Agent,随后把agent引用挂到connection.agent上——这为服务端代码在中间件中缓存和读取会话状态提供了便利。
listen()
将一个Stream(Node.js 流)注册到后端。当服务器收到来自客户端的新连接时应调用此方法。
backend.listen(stream [, request])stream:一个Stream(或Stream-like 对象),用于新Agent与Backend之间的通信。request(可选,Object,默认{}):连接上下文对象,同样会在中间件中以agent.custom形式暴露。
返回值:一个Agent,该Agent也会在中间件上下文中可用。
这是 WebSocket 接入 ShareDB 的标准入口。结合 getting-started 中的示例:先用@teamwork/websocket-json-stream把 WebSocket 转成Stream,再交给backend.listen(stream):
webSocketServer.on('connection', (webSocket) => { var stream = new WebSocketJSONStream(webSocket) backend.listen(stream) })在源码中(lib/backend.js),listen()会new Agent(this, stream)并触发connect中间件;若中间件返回错误,则直接关闭该Agent。
close()
断开 ShareDB 及其全部底层服务(数据库、Pub/Sub 等)。
backend.close([callback])callback(可选,Function):function(error) { ... },在所有服务停止后调用;若至少有一个服务无法停止,则以 error 形式回调。
源码实现(lib/backend.js)会依次关闭pubsub、db、milestoneDb,并遍历关闭extraDbs中所有附加数据库,全部完成后才触发回调。
use()
注册中间件。
backend.use(action, middleware)action:string | string[]——单个动作名或动作名数组,决定中间件在何时执行。可用动作见上文MIDDLEWARE_ACTIONS。middleware:function(context, next) { next(error) }——一个中间件函数。
源码支持传入动作数组,内部会递归地对每个动作注册同一中间件(lib/backend.js)。test/backend.js中可以看到典型用法,例如在afterWrite、submit等动作上挂载中间件(test/backend.js)。中间件上下文context上始终带有action、agent、backend三个属性,以及各动作特有的附加属性,详见 docs/middleware/actions.md。
addProjection()
定义一个投影(projection)。
backend.addProjection(name, collection, fields)name(string):投影的名字。collection(string):被投影的目标集合。fields(Object):需要投影的字段集合,键为字段名,值必须为true:
share.addProjection('names', 'users', {name: true})⚠️不支持子字段投影。也就是说,不能定义像{profile: {name: true}}这样的嵌套字段筛选。
从源码看(lib/backend.js),addProjection()会做严格校验:投影名重复会抛出Projection already exists,字段值不是true会抛出Invalid field错误。投影被保存在this.projections映射中,后续submit、getOps、fetch等所有方法都会据此把投影名解析为真实集合名(projection.target)。
submit()
向Backend提交一个操作。
backend.submit(agent, index, id, op [, options [, callback]])agent:Agent实例,会传给中间件。index(string):集合名或投影名。id(string):文档 ID。op(Object):要提交的操作。options(可选,Object,默认{}):透传给数据库适配器commit方法的选项;适配器commit支持的任何选项都可在此使用。callback(可选):function (error, ops) { ... },其中ops是从提交的 op 被提交到真正落库之间,其他客户端提交的 op 列表——这正是 OT 变换后需要回放给客户端的那部分操作。
在源码中(lib/backend.js),submit()会先做ot.checkOp(op)合法性检查,然后依次触发submit中间件 →SubmitRequest.submit()(内部完成 OT 变换与数据库commit)→afterWrite中间件 → 对 ops 做投影清洗(_sanitizeOps)。在客户端提交路径中,Agent._submit会在成功后将ops通过_sendOps发送给客户端,让客户端补齐错过的操作(lib/agent.js)。
getOps()
获取某个文档在指定版本区间内的操作记录,其中from包含、to不包含。
backend.getOps(agent, index, id, from, to [, options [, callback]])agent:Agent实例,传给中间件。index(string):集合名或投影名。id(string):文档 ID。from(number):要获取的起始 op 版本;设为null则从最早版本开始获取。to(number):结束版本,不会被获取(即to不包含);设为null则一直获取到最新版本。options(可选,Object,默认{}):options.opsOptions(可选,默认{}):直接透传给数据库驱动的getOps,例如请求 op 元数据:
{ opsOptions: { metadata: true, }, }callback:function (error, ops) { ... },成功时返回请求到的 ops。
从源码看(lib/backend.js),getOps()会把投影解析为目标集合,把agent.custom注入opsOptions,然后调用db.getOps(),最后对每个 op 执行_sanitizeOps(投影过滤 +op中间件)。因此返回给调用方的 ops 已经过投影与中间件处理。
getOpsBulk()
批量获取一个集合中多个文档在指定版本区间内的操作记录,语义与getOps一致(from包含、to不包含)。
backend.getOpsBulk(agent, index, fromMap, toMap [, options [, callback]])agent:Agent实例。index(string):集合名或投影名。fromMap(Object):键为文档 ID,值为该文档请求的起始版本(包含)。例如{abc: 3}表示获取文档abc从版本3起的 ops。toMap(Object):键为文档 ID,值为该文档请求的结束版本(不包含)。例如{abc: 3}表示获取文档abc截至版本3(不含)的 ops。options(可选,Object,默认{}):options.opsOptions直接透传给数据库驱动的getOpsBulk,用法同getOps。callback:function (error, opsMap) { ... },返回文档 ID 到 ops 的映射,如{abc: []}。
fetch()
获取一个文档的当前快照。
backend.fetch(agent, index, id, [, options [, callback]])agent:Agent实例。index(string):集合名或投影名。id(string):文档 ID。options(可选,Object,默认{}):options.opsOptions直接透传给数据库驱动的fetch,用法同上。callback:function (error, snapshot) { ... },成功时返回请求的快照。
在源码中(lib/backend.js),fetch()调用db.getSnapshot()拿到快照后,会经_sanitizeSnapshots处理:若有投影则先做快照投影裁剪,再触发readSnapshots中间件(快照类型为SNAPSHOT_TYPES.current)。如果中间件通过request.rejectSnapshotRead(snapshot, error)拒绝了该快照的读取,fetch也会以错误回调(见_sanitizeSnapshots对"部分拒绝"的处理,lib/backend.js)。
fetchBulk()
从集合中批量获取多个文档快照。
backend.fetchBulk(agent, index, ids, [, options [, callback]])agent:Agent实例。index(string):集合名或投影名。ids(string[]):文档 ID 数组。options(可选,Object,默认{}):options.opsOptions直接透传给数据库驱动的fetchBulk。callback:function (error, snapshots) { ... },成功时返回文档 ID 到快照的映射。
源码中(lib/backend.js),fetchBulk通过db.getSnapshotBulk()批量读取,并支持"部分拒绝":当readSnapshots中间件拒绝了其中部分文档的快照读取时,被拒绝的文档在返回的snapshotMap中会以{error: ...}对象形式标记,其余文档正常返回,整体不会因单个文档被拒而失败。
queryFetch()
获取匹配查询条件的快照。在大多数情况下,直接查询底层数据库更合适;但queryFetch可以在避免Doc实例开销的前提下应用中间件链路。
backend.queryFetch(agent, index, query, [, options [, callback]])agent:Agent实例。index(string):集合名或投影名。query(Object):查询对象,其格式取决于所用的数据库适配器(例如sharedb-mongo使用 MongoDB 查询语法,而内置 MemoryDB 默认无查询支持,直接返回集合全部文档,见 lib/db/memory.js)。options(可选,Object,默认{}):options.db(可选,string):指定在哪个数据库上执行查询。这些附加数据库通过构造选项extraDbs挂载。
callback:function (error, snapshot) { ... },成功时返回请求的快照。
源码路径(lib/backend.js 与 lib/backend.js)显示:queryFetch先触发query中间件(中间件可以改写查询、频道甚至切换数据库),随后从options.db或extraDbs解析出目标数据库,调用其query()方法,最后对结果快照执行投影裁剪与readSnapshots中间件。
源码中的其他实用方法(补充)
除文档列出的 API 外,lib/backend.js 中还实现了一批供客户端协议路径使用的方法,了解它们有助于理解 Backend 的全貌:
subscribe(agent, index, id, version, ...)(lib/backend.js):订阅文档变更流,配合pubsub.subscribe(channel)使用。version为null时同时抓取当前快照,否则只抓取指定版本之后的 ops。subscribeBulk(agent, index, versions, callback)(lib/backend.js):批量订阅版本映射。querySubscribe(agent, index, query, options, callback)(lib/backend.js):订阅查询结果并随数据变化推送 diff,内部使用QueryEmitter轮询数据库。fetchSnapshot(agent, index, id, version, callback)与fetchSnapshotByTimestamp(agent, index, id, timestamp, callback)(lib/backend.js):按版本号或时间戳获取历史快照,需要milestoneDb配合,并会结合getOps从里程碑快照重建到目标版本的文档状态。getChannels(collection, id)(lib/backend.js):返回文档订阅涉及的频道(集合频道collection与文档频道collection.id),是 Pub/Sub 频道命名的核心逻辑。
一个完整的服务端使用示例
综合以上 API,一个典型的生产级 ShareDB 服务端初始化如下:
var Backend = require('sharedb') var backend = new Backend({ db: dbAdapter, // 例如 sharedb-mongo 实例,勿用默认 MemoryDB 上生产 pubsub: pubsubAdapter, // 多实例部署时可换成外部 Pub/Sub 适配器 milestoneDb: milestoneAdapter, // 可选,启用里程碑快照 presence: true, // 启用在线状态功能 doNotCommitNoOps: true, // 可选:跳过 no-op 提交 suppressPublish: false, maxSubmitRetries: 5, // 限制提交重试次数 errorHandler: function(error, context) { myMonitoring.report(error) } }) // 中间件与投影 backend.use(backend.MIDDLEWARE_ACTIONS.submit, function(context, next) { // 提交前校验或改写 next() }) backend.addProjection('names', 'users', {firstName: true, lastName: true}) // WebSocket 接入 webSocketServer.on('connection', (webSocket) => { var stream = new WebSocketJSONStream(webSocket) backend.listen(stream, {cookies: req.cookies}) // req 可在 connect 中间件中通过 agent.custom 读取 }) // 服务端直连读写(等价于服务端版本的客户端 Connection) var connection = backend.connect() var doc = connection.get('users', '123') doc.fetch(function(error) { // ... }) process.on('SIGTERM', function() { backend.close(function() { process.exit(0) }) })小结
Backend是 ShareDB 服务端的枢纽对象,它把四类职责收敛在一个类中:构造期配置(数据库、Pub/Sub、里程碑库、附加库及各类行为开关)、连接管理(listen/connect/close)、扩展点(use注册中间件、addProjection定义投影),以及数据读写(submit、getOps、getOpsBulk、fetch、fetchBulk、queryFetch)。理解每个选项在 lib/backend.js 中的落点,以及各方法如何串联"中间件 → OT 变换 → 适配器调用 → 投影清洗"这条链路,是安全、高效地把 ShareDB 集成进生产系统的前提。更深入的中间件动作定义、数据库与 Pub/Sub 适配器接口、投影与文档历史机制,可分别查阅 docs/middleware/actions.md、docs/adapters/database.md、docs/adapters/pub-sub.md、docs/projections.md 与 docs/document-history.md。
- 后端
- 数据库
【免费下载链接】sharedb
Realtime database backend based on Operational Transformation (OT)
相关推荐
揭秘支付宝签名底层原理:alipay_sdk_cj中RSA2与SHA256实现深度解析
揭秘支付宝签名底层原理:alipay_sdk_cj中RSA2与SHA256实现深度解析 alipay_sdk_cj 是一款面向 仓颉语言 的 支付宝支付后端 S
后端数据库Sails HTTP 核心钩子(Core Hook)深入解析:HTTP 服务器启动、中间件栈绑定与配置
Sails HTTP 核心钩子(Core Hook)深入解析:HTTP 服务器启动、中间件栈绑定与配置 Sails 是一个面向 Node.js 的实时(Real
后端Qiskit QuantumCircuit 类完全指南:量子电路的核心数据结构与 API 详解
Qiskit QuantumCircuit 类完全指南:量子电路的核心数据结构与 API 详解 QuantumCircuit 是 Qiskit 中表示量子电路的
科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考