ARTICLE DETAIL

资讯详情

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

LokiJS Changes API 实战指南:集合级变更追踪与远程同步的完整实现解析

LokiJS Changes API 实战指南:集合级变更追踪与远程同步的完整实现解析 数据库后端【免费下载链接】LokiJSjavascript embeddable / in-memory database项目地址https://gitcode.com/gh_mirrors/lo/LokiJS点击查看免费下载LokiJS 自 1.1 版本起引入 Changes API变更追踪 API允许开发者记录每个集合自某一时间点通常是工作会话开始也可以是自定义时间点以来发生的全部插入、更新与删除操作为远程同步、增量备份与离线-在线数据合并提供可靠的数据基础。读完本文你将掌握 Changes API 的启用方式、变更记录的序列化与清理方法、变更对象的结构与顺序语义并深入理解其在 src/lokijs.js 中的底层实现原理与 Delta 差分模式的高级用法。一、Changes API 是什么为远程同步而生的变更追踪机制LokiJS 是一款 JavaScript 可嵌入内存数据库javascript embeddable / in-memory database数据完全驻留在内存中并通过持久化适配器保存到本地存储。当应用需要在多个客户端之间同步数据时一个核心难题是如何高效地知道从上次同步之后本地数据到底发生了什么变化。Changes API 正是为解决这一问题而设计。它的核心能力是追踪每个集合自某个时间点以来的所有变更插入、更新、删除将这些变更按发生顺序保存为结构化记录通过serializeChanges()方法生成变更记录的字符串表示便于网络传输供远程同步使用。与全量比对方案相比Changes API 让同步双方只需交换变化的部分而非全部数据显著降低同步成本。这正是官方文档将其定位为particularly useful for remote synchronization尤其适合远程同步的原因。二、核心设计集合级、可选开启、默认关闭2.1 集合级collection-level特性Changes API 是一个**集合级collection-level**功能而非全局数据库功能。这意味着开发者可以为每个集合单独决定是否开启变更追踪某些集合只存放易失数据volatile data例如临时缓存、会话状态无需追踪某些集合存放需要跨端同步的关键业务数据必须开启追踪。这种精细粒度让开发者能够在同步开销与数据一致性之间取得平衡只为自己真正需要同步的集合付出追踪成本。2.2 默认关闭LokiJS 的性能原则文档明确指出LokiJS 总是默认采用性能最优的设置will always set the fastest performing setting as default因此Changes API 默认是关闭disabled的。在源码中这一点得到了印证src/lokijs.js 的 Collection 构造函数对disableChangesApi的默认值处理为// disable track changes this.disableChangesApi options.hasOwnProperty(disableChangesApi) ? options.disableChangesApi : true;也就是说只要调用方未显式传入disableChangesApi该值一律为true即 Changes API 关闭。只有在显式传入disableChangesApi: false或调用setChangesApi(true)时才开启。2.3 两种开启方式开启 Changes API 有两种等价途径方式一在集合构造时通过配置参数开启var db new loki(db.json); var users db.addCollection(users, { disableChangesApi: false });方式二在运行时通过setChangesApi(isEnabled)动态开启/关闭users.setChangesApi(true); // 开启变更追踪 users.setChangesApi(false); // 关闭变更追踪setChangesApi的实现位于 src/lokijs.jsthis.setChangesApi function (enabled) { self.disableChangesApi !enabled; if (!enabled) { self.disableDeltaChangesApi false; } };值得注意的是当setChangesApi(false)关闭 API 时源码会同步将disableDeltaChangesApi置为false即同时关闭 Delta 差分模式关于 Delta 模式详见后文第六节。开启 API 后集合内部的changes数组src/lokijs.js 中this.changes []即开始累积变更记录。提示disableChangesApi的命名是双否定语义——传入false表示不禁用即启用传入true表示禁用。这是使用配置参数时最容易踩的坑。三、触发时机与内部实现插入、更新、删除三大事件文档规定有三类操作会触发 Changes API 记录插入inserts、更新updates、删除deletes。当任一事件发生在已开启 Changes API 的集合上时该集合会存储相关对象的快照snapshot并关联该操作类型与集合名称。3.1 底层实现createChange所有变更记录最终都汇聚到Collection.prototype.createChange实现位于 src/lokijs.jsCollection.prototype.createChange function (name, op, obj, old) { this.changes.push({ name: name, operation: op, obj: op U !this.disableDeltaChangesApi ? this.getChangeDelta(obj, old) : JSON.parse(JSON.stringify(obj)) }); };可以看到每条变更记录包含三个字段name集合名、operation操作类型字符、obj对象快照。在非 Delta 模式下快照通过JSON.parse(JSON.stringify(obj))深拷贝生成确保变更记录与后续业务对象修改互不影响。3.2 插入操作的调用链插入操作在Collection.prototype.insert内部src/lokijs.js根据是否开启 API 分流if (this.disableChangesApi) { this.insertMeta(obj); } else { this.insertMetaWithChange(obj); }开启时调用insertMetaWithChangesrc/lokijs.jsCollection.prototype.insertMetaWithChange function (obj) { this.insertMeta(obj); this.createInsertChange(obj); };其中createInsertChangesrc/lokijs.js最终以操作符IInsert调用createChange。值得注意的是即使 Changes API 关闭insertMeta依然会执行——它会为文档维护meta元信息如created、updated时间戳与revision版本号这是 Changes API 与文档元数据机制的协作点。3.3 更新操作的调用链更新操作在Collection.prototype.update内部src/lokijs.js同样分流if (this.disableChangesApi) { newInternal this.updateMeta(newInternal); } else { newInternal this.updateMetaWithChange(newInternal, oldInternal); }开启时调用updateMetaWithChangesrc/lokijs.jsCollection.prototype.updateMetaWithChange function (obj, old, objFrozen) { obj this.updateMeta(obj, objFrozen); this.createUpdateChange(obj, old); return obj; };createUpdateChangesrc/lokijs.js以操作符UUpdate调用createChange并传入更新前的old对象——这是 Delta 差分模式的关键输入。3.4 删除操作的调用链删除操作通过事件系统实现。Collection 构造函数中注册了delete事件监听器src/lokijs.jsthis.on(delete, function deleteCallback(obj) { if (!self.disableChangesApi) { self.createChange(self.name, R, obj); } });即在删除发生时以操作符RRemove记录被删除对象的快照。对应的触发点在Collection.prototype.removesrc/lokijs.js源码中还有一处性能优化只有当 Changes API 开启或存在其他 delete 监听器时才触发emit(delete, ...)避免无监听时的无效事件开销。从源码结构看插入与更新是在insert/update方法内同步、内联完成变更记录的而删除则走EventEmitter 事件机制两者路径不同但最终都汇聚到createChange。四、使用指南启用、序列化与清理4.1 完整的最小示例// 1. 创建数据库与集合开启 Changes API var db new loki(example.db); var users db.addCollection(users, { disableChangesApi: false }); // 2. 执行一些写操作 users.insert({ name: joe }); // 触发 I 记录 var u users.findOne({ name: joe }); u.name jack; users.update(u); // 触发 U 记录 users.remove(u); // 触发 R 记录 // 3. 序列化所有变更 var changes db.serializeChanges(); console.log(changes);4.2db.serializeChanges()生成变更的字符串表示serializeChanges是数据库Loki 实例级别的方法用于生成全部已启用 Changes API 的集合的变更记录字符串专为网络传输synchronization设计。其实现位于 src/lokijs.jsLoki.prototype.serializeChanges function (collectionNamesArray) { return JSON.stringify(this.generateChangesNotification(collectionNamesArray)); };它本质上是对generateChangesNotificationsrc/lokijs.js结果的JSON.stringify。后者会遍历数据库所有集合将每个集合changes数组中的记录汇总到一个统一数组中Loki.prototype.generateChangesNotification function (arrayOfCollectionNames) { var changes [], selectedCollections arrayOfCollectionNames || this.collections.map(getCollName); this.collections.forEach(function (coll) { if (selectedCollections.indexOf(getCollName(coll)) ! -1) { // 将该集合的全部 changes 记录追加到总数组 } }); return changes; };4.3 按集合子集序列化如果只关心部分集合的变更可以传入集合名称数组// 仅生成 users 集合的变更 var userChanges db.serializeChanges([users]); // 仅生成 users 与 orders 两个集合的变更 var someChanges db.serializeChanges([users, orders]);不传参数时db.serializeChanges()则汇总所有开启追踪的集合。4.4 清理变更db.clearChanges()与coll.flushChanges()变更记录会持续累积因此同步完成后必须清理否则同一批变更会被重复同步。清理有两个层级数据库层级——db.clearChanges()清空所有集合的变更记录实现位于 src/lokijs.jsLoki.prototype.clearChanges function () { this.collections.forEach(function (coll) { if (coll.flushChanges) { coll.flushChanges(); } }); };它遍历所有集合对每个集合调用flushChanges()。集合层级——coll.flushChanges()只清空单个集合的变更实现位于 src/lokijs.jsfunction flushChanges() { self.changes []; }4.5 典型同步工作流文档给出了标准的配套用法在成功同步的回调中调用db.clearChanges()function syncToServer(db) { // 1. 取变更并发送到远端 var changes db.serializeChanges(); sendToRemoteServer(changes, function (err) { if (!err) { // 2. 同步成功后清空本地变更记录防止重复同步 db.clearChanges(); } }); }如果需要只清理某个特定集合例如某集合同步失败、需要重试则对该集合单独调用users.flushChanges()其余集合的变更保留待下次处理。补充说明Loki 构造函数中还通过this.on(init, this.clearChanges)src/lokijs.js把init事件与clearChanges绑定——数据库初始化/加载时会自动清空变更记录这与变更追踪从当前会话起点开始的语义保持一致。五、变更记录的结构与顺序保证5.1 变更对象的三字段结构每条变更是一个包含三个属性的对象实现在 src/lokijs.js字段含义取值说明name集合名称字符串如usersobj对象快照插入/删除时为对象深拷贝更新时非 Delta 模式为更新后的完整对象operation操作类型单个字符IInsert 插入、UUpdate 更新、RRemove 删除文档给出的示例向 users 集合插入{ name: joe }将生成{ name: users, obj: { name: joe }, operation: I }注实际运行时obj中还会包含 LokiJS 自动附加的$loki主键与meta元信息字段测试 spec/generic/changesApi.spec.js 对此有明确断言。文档示例为便于理解而省略了这些内部字段。5.2 顺序保证同步正确的基石文档特别强调变更按发生顺序保存Changes are kept in order of how they happened因此第三方应用可以按正确顺序执行插入、更新与删除操作保证远端数据最终收敛到与本地一致的状态。这一顺序性源自底层实现每个集合的changes是一个普通数组src/lokijs.js变更通过push按序追加generateChangesNotification汇总时按集合遍历顺序、集合内按changes数组原有顺序拼接最终serializeChanges输出的 JSON 数组天然保持时间顺序。对更新操作而言按序重放至关重要——若远端按乱序执行两个对同一文档的U更新最终结果可能错误。六、进阶Delta Changes API——差分模式在标准模式下每次更新都会记录更新后对象的完整快照。当文档很大而每次只改一个字段时完整快照会浪费大量同步带宽。为此 LokiJS 提供了 Delta Changes API差分变更 API通过disableDeltaChangesApi: false开启。Delta 模式的核心实现是getObjectDeltasrc/lokijs.js它递归对比旧对象与新对象只保留发生变化新增、修改的字段未变化的字段不出现在差分结果中function getObjectDelta(oldObject, newObject) { var propertyNames newObject ! null typeof newObject object ? Object.keys(newObject) : null; if (propertyNames propertyNames.length [string, boolean, number].indexOf(typeof (newObject)) 0) { var delta {}; for (var i 0; i propertyNames.length; i) { var propertyName propertyNames[i]; if (newObject.hasOwnProperty(propertyName)) { if (!oldObject.hasOwnProperty(propertyName) || self.uniqueNames.indexOf(propertyName) 0 || propertyName $loki || propertyName meta) { delta[propertyName] newObject[propertyName]; } else { var propertyDelta getObjectDelta(oldObject[propertyName], newObject[propertyName]); if (typeof propertyDelta ! undefined propertyDelta ! {}) { delta[propertyName] propertyDelta; } } } } return delta; } else { return oldObject newObject ? undefined : newObject; } }从源码可以提炼出 Delta 模式的几条关键语义递归深度比较嵌套对象逐层对比只有发生变化的叶子字段被保留恒保留字段$loki主键与meta元信息始终包含在差分结果中远端据此定位文档并更新元数据唯一字段始终保留配置了唯一索引uniqueNames的属性即使未变化也会出现在差分中新增字段直接保留旧对象中没有的新属性直接进入差分完全未变时返回undefined该字段不进入差分对象进一步压缩体积。6.1 开启方式与条件var db new loki(); var items db.addCollection(items, { disableChangesApi: false, // 必须先开启 Changes API disableDeltaChangesApi: false // 再开启 Delta 差分模式 });源码 src/lokijs.js 有一条强制依赖if (this.disableChangesApi) { this.disableDeltaChangesApi true; }——即Delta 模式强依赖 Changes API 已开启若 Changes API 关闭则 Delta 模式被自动强制关闭。6.2 Delta 模式的实测效果spec/generic/changesApi.spec.js 的works with delta mode测试用例演示了差分效果对文档{ name: tyrfing, owner: Svafrlami, maker: { name: dwarves, count: 1 } }执行两次更新var tyrfing items.findOne({name: tyrfing}); tyrfing.owner arngrim; items.update(tyrfing); // 第一次更新只改了 owner tyrfing.maker.count 4; items.update(tyrfing); // 第二次更新只改了嵌套的 maker.count序列化后得到的第二条更新记录的差分对象为{ operation: U, obj: { owner: arngrim } } // 只含发生变化的 owner { operation: U, obj: { maker: { count: 4 } } } // 只含嵌套变化的 maker.count测试断言firstUpdate.obj.name为undefined未变化字段不出现、secondUpdate.obj.owner为undefined证实差分模式只携带变化字段。同一测试文件中的batch operations work with delta modespec/generic/changesApi.spec.js进一步验证了链式批量更新items.chain().update(...)在 Delta 模式下也能正确逐条生成差分记录。七、边界条件、协作关系与注意事项7.1 与disableMeta的冲突校验disableMeta选项禁用文档meta元信息与 Changes API 存在硬性冲突addCollection在 src/lokijs.js 做了显式校验if (options.disableMeta true) { if (options.disableChangesApi false) { throw new Error(disableMeta option cannot be passed as true when disableChangesApi is passed as false); } if (options.disableDeltaChangesApi false) { throw new Error(disableMeta option cannot be passed as true when disableDeltaChangesApi is passed as false); } ... }原因从源码可以推断Changes API 在插入/更新时都会调用insertMeta/updateMeta维护文档meta$loki、meta也是 Delta 差分中恒保留的定位字段禁用meta后无法建立完整的变更追踪链。因此开启 Changes API 的集合必须保留meta。7.2 集合配置选项总览在 src/lokijs.js 与 src/lokijs.js 的 JSDoc 中与变更追踪相关的集合配置项完整定义如下选项默认值说明disableChangesApitrue设为false启用 Changes API注意双否定语义disableDeltaChangesApitrue设为false启用 Delta 差分模式要求 Changes API 已启用且会强制克隆其余常用集合选项如clone、cloneMethod、unique、indices、autoupdate等不影响 Changes API 的启用但clone: true配合 Delta 模式时克隆行为与差分记录的组合效果建议在实际场景中结合 src/lokijs.js 的选项说明验证。7.3 性能与存储开销提示由于 Changes API 默认关闭正是为了最快性能开启后必须意识到以下开销内存开销每条变更都保存对象快照非 Delta 模式下是完整深拷贝高频写入场景下changes数组会快速增长应配合flushChanges/clearChanges及时清理CPU 开销快照生成依赖JSON.parse(JSON.stringify(obj))深拷贝src/lokijs.jsDelta 模式还额外进行递归对象对比实践建议仅在需要同步的集合上开启同步成功后立即clearChanges()对大文档优先考虑 Delta 差分模式以压缩同步体积。八、测试验证与可运行示例仓库为 Changes API 提供了完整的测试套件 spec/generic/changesApi.spec.js覆盖了本教程的核心行为可作为学习与验证的参照基础行为does what it says on the tin第 4-56 行验证addCollection(users, { disableChangesApi: false })开启后插入/更新产生对应数量的变更generateChangesNotification([users])按集合过滤db.serializeChanges([users])与 JSON 序列化结果一致setChangesApi(false)后disableChangesApi变为true且不再记录新变更db.clearChanges()与users.flushChanges()均能清空记录通过users.getChanges().length断言。Delta 差分works with delta mode第 58-95 行验证差分记录只含变化字段。批量操作batch operations work with delta mode第 97-148 行验证chain().update()批量更新逐条生成U差分记录且每条差分包含$loki、count、meta字段。测试通过require(../../src/lokijs.js)spec/generic/changesApi.spec.js直接引用 src/lokijs.js可在 Node.js 环境中运行测试配置位于 spec/support/jasmine.json。九、总结一条完整的远程同步链路综合文档与源码一套基于 Changes API 的远程同步方案可以归纳为以下闭环启用追踪db.addCollection(users, { disableChangesApi: false })或users.setChangesApi(true)按需为每个集合单独开启业务写入insert/update/remove自动按序在集合changes数组中累积{ name, operation, obj }快照I/U/R提取变更db.serializeChanges()全量或db.serializeChanges([users])按集合子集生成 JSON 字符串发送给远端远端重放远端按数组顺序执行插入、更新、删除保证最终一致性清理记录同步成功回调中调用db.clearChanges()或单集合users.flushChanges()为下一轮同步清零起点。对于带宽敏感的场景可在第 1 步同时传入disableDeltaChangesApi: false启用差分模式让更新记录只携带真正变化的字段进一步压缩同步数据量。这套机制让 LokiJS 得以在保持内存数据库高性能特性的同时为多端数据同步提供了一套轻量、有序、可扩展的官方解决方案。相关资源文档原文tutorials/Changes API.md核心实现src/lokijs.js数据库级序列化/清理、src/lokijs.js集合级变更记录、src/lokijs.jsDelta 差分测试用例spec/generic/changesApi.spec.js赞分享数据库后端【免费下载链接】LokiJSjavascript embeddable / in-memory database项目地址https://gitcode.com/gh_mirrors/lo/LokiJS点击查看免费下载相关推荐Metrics.NET数据上报实战对接Graphite、ElasticSearch与InfluxDB的3种方案Metrics.NET数据上报实战对接Graphite、ElasticSearch与InfluxDB的3种方案 Metrics.NET 是一个面向 .NET运维NgRx Data 中的实体集合EntityCollection集合状态结构、变更追踪与源码实现解析NgRx Data 中的实体集合EntityCollection集合状态结构、变更追踪与源码实现解析 在 NgRx Platform 的 ngrx/da前端状态管理浏览器资源嗅探终极指南用猫抓插件轻松捕获网页媒体资源浏览器资源嗅探终极指南用猫抓插件轻松捕获网页媒体资源 还在为无法保存网页上的精彩视频而烦恼吗猫抓插件为你提供了完美的浏览器资源嗅探解决方案。这款强大的浏览器音视频上一篇Windows窗口置顶终极指南免费开源工具让重要内容永远在最前面下一篇QQ音乐格式转换终极指南3步解锁qmcdump工具完整使用教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表