知识卡片

Grouk同步协议的Folder+ChangeSet数据结构

结构图卡

内容

[[Grouk同步协议的五个设计目标]]的落地,借鉴了Git等版本管理系统的思路——两者要解决的问题类似,区别在于实时性要求不同,而且Git的Server和Client是对等的,Grouk这里的Client只是Server的一个子集(客户端不需要完整复刻服务器全部数据)。核心数据结构围绕Folder和ChangeSet展开:每个需要同步的数据集被抽象成一个Folder(可能多人共享、也可能某人专用),Folder相当于一个索引表,引用的是对象ID;每个Folder维护一个变更集(ChangeSet),增量同步就是通过一条条变更实现的,变更的版本号严格有序递增,每一次Folder索引或Folder引用对象的操作都会生成一条变更;每条变更(Change)携带对应的操作(OP,如新增/更新/删除)和变更数据,客户端根据操作类型在本地重放这条变更来还原状态;Folder中的索引对象会被分配一个该Folder内有序递增的ID,索引对象还可以拥有自定义属性;所有数据对象统一定义、带更新时间等基本字段,抽象出通用操作接口(ObjectStore);客户端通过接收Change把服务器的Folder及对象库同步下来,但只同步服务器上的一个子集,不是全量,客户端可以用对象的更新时间来判断本地缓存是否还有效。整套机制运转起来相当于实现了一个服务器和客户端实时同步的轻型数据库:用户每次发消息、改消息、改个人信息,都会触发一次相关Folder的变更,存入变更集并实时推给在线客户端;在线客户端收到变更后检查本地版本号和当前版本号是否连续,不连续说明有消息丢失,就去服务器拉取中间缺失的变更,再按操作定义把变更应用到本地Folder和对象库;离线客户端上线后,带上本地Folder的版本号发起sync request向服务器同步变更,后续处理流程相同。

结构图

flowchart TD
    A["用户操作\n发消息/改消息/改个人信息"] --> B["触发相关Folder的变更(Change)\n写入ChangeSet,版本号有序递增"]
    B --> C{"客户端在线?"}
    C -->|是| D["实时推送变更到客户端"]
    D --> E{"本地版本号与\n当前版本号是否连续?"}
    E -->|连续| F["按OP重放变更\n应用到本地Folder和对象库"]
    E -->|不连续,有丢失| G["向服务器拉取中间缺失的变更"]
    G --> F
    C -->|否,离线| H["客户端上线后携带本地版本号\n发起sync request"]
    H --> G

参考来源

- 位置:《高可用架构(第1卷)》第1章《高可用架构案例精选》"1.3 如何设计类似微信的多终端数据同步协议:Grouk实践分享"节,"1.3.3 Grouk在多终端数据同步协议上的探索实践"(源文件:_epub-src/OEBPS/Text/Chapter1_3_4.xhtml) - 结论依据:原文说明"每个Folder维护一个变更集(ChangeSet),增量同步通过变更实现,变更的版本号有序递增……在线客户端收到变更后,检查本地的版本号和当前版本号是否连续。不连续则说明有消息丢失,需要从服务器拉取二者之间丢失的变更",直接支撑本卡片结论与结构图。 - 原始内容:每个Folder维护一个变更集(ChangeSet),增量同步通过变更实现,变更的版本号有序递增……客户端会通过Change将服务器的Folder及对象库同步下去,不过同步的只是服务器上的一个子集,并不是全量。