2015 年 11 月 28 日,架构与运维的年度大趴——又拍云架构与运维大会·北京站圆满落幕了。近 1500 名来自全国各地的 IT 小伙伴们让现场热火朝天,而 23 位享誉互联网技术业界的大神们的登场,Grouk CTO 王渊命在会上作了题为《移动时代的多终端同步协议设计与探索》,以下是分享实录:
我们 Grouk 是一个创业团队,面向团队通讯的主打产品应用刚开始公测,因此我们也需要实现类似微信的多端数据同步功能,下面我主要从技术和产品的结合场景进行一些心得分享,感觉我们在这方面的探索还是值得和大家探讨的,这种需求业界也没有非常成熟的公开解决方案。
一、移动互联网时代多终端数据同步面临的挑战
首先要讲的是,多终端同步的含义及应用场景。
多终端同步是指用户在多个终端切换时可获得一致性体验,不丢失上下文,同时隐含的一个含义是,如果用户多个终端同时在线要能做到实时同步。
举几个应用的例子:
1、 Trello 看板应用
用户打开后,其他人操作看板,要能实时变化,不依赖用户刷新页面。如果用户在多个终端操作,也需要做到实时变化。通过测试,我发现Trello在移动端和PC同步的时候还是有Bug的。例如,PC设置离线,手机上操作card。PC连网,不刷新页面,数据经常无法同步。
2、 Quip这样的多人协作编辑
某人的编辑结果,其他人要能实时看到。同时还支持离线编辑、冲突合并。例如,Evernote多人协作是文档锁定模式的,冲突很难自动合并,体验上就差些。
这几种典型的应用场景是移动互联网爆发以来,应用富客户端化带来的一种趋势性变化。
移动端上是独立应用,PC端是基于JavaScript的应用,和原来PC互联网时代刷页面的交互体验完全不一样。这个当然原来也有,但应用没现在这么广泛。
移动客户端的爆发和应用富客户端化带来以下几个挑战:
- 服务器端的输出由HTML变成结构化数据(json等)。原来基于浏览器和HTTP协议的缓存规则机制失效,客户端需要针对具体的业务场景设计缓存。没有缓存的话,每次全量拉取很浪费流量。
- 用户习惯从多个终端进行操作,对跨屏操作的体验要求比较高了。
- 多人协作场景下,数据需要实时同步到不同用户的不同设备上。
二、多终端数据同步与传统消息投递协议的差异
接下来说一下多终端数据同步和IM的关系
虽然数据同步机制和IM消息投递是两个问题,但如果实现了实时同步,基本上就实现了一种特殊的IM。所以先说一下传统的IM投递机制。
传统的IM投递协议,大家应该都比较熟悉,5月的时候群里沈剑分享过一次。
这里借用其中的一句话:
消息可达性即消息的可靠投递,有一个著名的定理:SMC定理,Single-Message Communication,Published in :Communications, IEEE Transactions on (Volume:24 , Issue: 2 ) ,很短的一个论文。文章的结论是:任何端到端的消息传递协议,消息既不丢失,也不重复是不可能的。
也就是说传统的IM消息投递要么接受消息丢失,要么接受消息确认重试导致的重复问题。当然可以通过应用层面的排重机制来解决一问题。
虽然传统的IM投递机制+历史记录,也可以实现多终端同步:
- 所有设备都在线的情况下,直接投递。
- 离线→在线时,拉取历史记录补充缺失记录。
但这样做比较困难的地方在于:
- 变更如何同步?我们的消息不像传统IM,是不可变对象,我们的消息是可变的。同时,群组列表,联系人列表,这些都是可变的,如何同步?
- 投递确认机制的缺陷会导致一致性不好控制,如果出现多个终端不一致的情况,客户端无法自修复。只能提供特殊的刷新机制,由用户自己刷新。
比如,我前面提到的Trello的情况。当然,Trello的具体实现没分析过,这里只是推测。于是,我们考虑使用同步协议。说到同步机制当然要说一下微信,今天说分享的时候有人就提到了微信的SYNC。
微信的SYNC协议没有详细的公开分享,按照公开说明,是参考Activesyec实现的。据我的观察,当然,以下微信协议的说明不保证正确性,群里也有微信的同学,可以纠正。
- 同步机制是通过服务器通知,客户端拉取机制实现的。IM协议投递的是新消息的通知,拉取是根据版本号增量同步,将消息投递转换为基于状态同步的协议。(这个有公开说明)
- 每个Folder的版本号是严格有序递增的,Folder不是按照会话划分的。
- 微信投递消息和邮件类似,是将消息投递到每个人的收件箱中,每投递一个消息增加一个版本号。
以上只是我个人的简单分析,不确定微信的服务器端是如何存储的。也不确定微信是如何处理变更的,比如,通讯录的同步。所以我们还是得自己设计一个同步协议。
三、Grouk在多终端数据同步协议上的探索实践
到这里,先总结下我们设计的该同步协议的目标 :
- 解决接口数据做本地缓存需要根据具体接口单独设计的问题,设计一种统一的客户端缓存数据机制。这个问题应该是所有App类应用都会遇到的问题。
- 实现消息的多终端增量同步,然后通过同步机制确保不丢消息。同步机制必须避免流量浪费,所以需要做增量。
- 消息同步和联系人/群组等同步使用同一套机制。这个也为以后的业务数据类型扩充做准备。
- 客户端数据能自修复达到最终一致性。
- 不解决冲突合并问题。因为我们的消息比较轻量,不需要像文档一样考虑冲突问题,降低复杂性。
有了目标后,我们首先想到了Git等版本管理系统。因为二者要解决的问题是类似的,区别在于实时性上,还有Git的Server和Client是对等的,而我们这里的Client只是Server的子集。
因为时间关系,关于Git等版本管理系统的机制这里不细说了。直接说一下我们的抽象和解决方案 。
数据结构图,如下:
- 每个需要同步的数据集抽象成一个Folder,Folder可能是多人共享的,也可能是某人专用的。这里的Folder相当于一个索引表,引用的是对象ID。
- 每个Folder维护一个变更集(ChangeSet),增量同步通过变更实现,变更的版本号有序递增。变更是每次操作生成的,每一次Folder索引或者Folder引用对象的操作都生成一个变更。
- 变更(Change)有对应的操作(OP)。如:新增、更新、删除等。包括索引变更和索引引用对象的变更。携带变更数据。客户端根据操作要在本地实现重放逻辑。
- 每个Folder中的索引对象会被分配一个该Folder中的有序递增ID。每个索引对象也可以拥有自定义属性。
- 所有的数据对象都统一定义,有更新时间,等基本字段。抽象出通用的操作接口(ObjectStore)。
- 客户端会通过Change将服务器的Folder及对象库同步下去,不过同步的只是服务器上的一个子集,并不是全量。
客户端可以通过对象的更新时间来确定本地缓存的有效性。
下图是我们利用这套机制的流程:
用户发消息、修改消息、修改个人信息等操作,都触发一次相关Folder的变更,存到变更集中,实时投递到在线客户端。- 在线客户端收到变更后,检查本地的版本号和当前版本号是否连续。如果不连续说明有消息丢失,从服务器拉取二者之间丢失的变更。然后客户端根据操作定义将变更应用(apply change)到本地的Folder和对象库。
- 离线客户端上线后,带上本地的Folder的版本号,发起sync request,去服务器端同步变更。同步后需要进行的操作同上。
- 所有的对象通过统一的接口获取。支持类似于HTTP的ETag,变更更新模式,不过是针对每个对象的版本,以增强本地缓存机制。
可以说,相当于实现一个服务器和客户端实时同步的轻型数据库。
以下是我们这样设计的优缺点。
优点:
- 用户在线的情况下,大多数情况变更是直接投递下去的。比通知→拉取模式的和服务器的交互少,更省资源。
- 离线缓存比较容易实现,离线浏览的体验会比较好。
- 能保证终端和服务器的数据一致性。
- 相对比较通用,可以适用于多个业务场景。
缺点:
- 本地客户端的实现逻辑比较重。微信的思想是轻客户端,重服务器。我估计我们在这里还得踩些坑。
- 只能保证同一个Folder的最终一致性。
基本协议设计就讲这里了,再说一下我们的技术栈。
主要还是基于Java+Netty研发。我们撸了一个简单的前端框架,主要是为了实现用同一套逻辑,服务多个接入层。
我们的接入层支持的协议有HTTP,自定义TCP,WebSocket。通过接入层转换后,变为内部的request/response,后面共享同一套逻辑。也就是说同一个请求,可以通过HTTP发送,也可以通过长连接(TCP/WebSocket)发送。
数据对象上,我们接口支持JSON/Protobuf两种。根据客户端的accept自动适配。接口输出格式统一定义对象,客户端可以和服务器端共用。
总结下当前应用,尤其是工具应用的一种趋势。
IM已经变得不像IM,不是IM的要变成IM。前半句是说,当前的IM已经逐渐不像传统的IM了,无论是微信,还是Slack,还是我们的Grouk,和传统的IM区别越来越大。后半句是说,不是IM的应用因为要做多终端实时同步,协议越来越靠近IM机制了。
另外个人感觉这种趋势不一定仅局限在工具类。哪怕是电商网站,如果能同步用户的购物车到多个终端,用户的体验也会更进一步。
我们应用使用同步协议已实现的效果 :
- 多终端数据保持一致,用户切换后不会丢失上下文(比如QQ的消息只投递到一个终端)。
- 允许多个终端登录,比如,多个手机、多个Web。
- 历史记录可以在任何一个端获取,也可以通过搜索从任何一条历史消息开始上下回溯。
- 未读数/收藏实时同步。
- 联系人信息/群组信息实时同步。
- 更多欢迎注册体验 https://grouk.com(顺便打个广告)。
最后,再说一个题外话,就是创业公司做技术类的创新是否值得?
我们也讨论过,假设当初直接拿现成的XMPP来做,估计我们的推出时间也可以早几个月。我们在这套机制上花费的时间也不少。但我们还是觉得当前IM这么多,用户的体验已经被QQ、微信等工具教育的情况下,如果体验不能更进一步,估计用户连尝试的愿望都没有。但到底花多少时间,估计要做个平衡。