| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 5184 人关注过本帖
标题:如何设计类似微信的多终端数据同步协议 | Grouk实践分享
只看楼主 加入收藏
醒山
Rank: 11Rank: 11Rank: 11Rank: 11
等 级:小飞侠
威 望:9
帖 子:463
专家分:2071
注 册:2015-5-25
结帖率:88.89%
收藏
 问题点数:0 回复次数:0 
如何设计类似微信的多终端数据同步协议 | Grouk实践分享
如何设计类似微信的多终端数据同步协议 | Grouk实践分享

原创 2015-09-18 王渊命 高可用架构
本文由王渊命在高可用架构群所做的分享整理而来,转载请注明高可用架构公众号:ArchNotes。

王渊命,团队协作IM服务Grouk联合创始人及CTO,技术极客,曾任新浪微博架构师、微米技术总监。2014年作为联合创始人创立团队协作IM服务Grouk,长期关注团队协作基础工具和研发环境建设,Docker深度实践者。


我们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系统)。
 
虽然传统的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://(顺便打个广告)。
 
最后,再说一个题外话,就是创业公司做技术类的创新是否值得?
 
我们也讨论过,假设当初直接拿现成的XMPP来做,估计我们的推出时间也可以早几个月。我们在这套机制上花费的时间也不少。但我们还是觉得当前IM这么多,用户的体验已经被QQ、微信等工具教育的情况下,如果体验不能更进一步,估计用户连尝试的愿望都没有。但到底花多少时间,估计要做个平衡。
 
Q&A
 
Q1:为什么不采用XMPP协议呢?多人协作时后端出现用户不在一台服务器上如何同步?

XMPP由于众所周知的原因,XML不太适合移动使用。一般移动上使用都要做压缩,比如WhatsApp。另外就是前面描述的,做变更同步比较麻烦。
 
Q2:客户端所有请求都是通过和服务器的长连接过来吗?没有走比如短连接的HTTP协议之类的?

不是所有,有走短连接的。我们采用一种动态机制,长连接优先。消息上我们没有采用长轮询的方式。客户端是TCP长连接,Web版本是WebSocket。
 
Q3:这个版本号必须是有序的吗?是否可以跟Git一样用随机字符+链表的方式做?

我们这个方案里版本号必须是有序严格递增的,因为要靠这个判断是否丢失消息。Git的方案是因为需要离线写操作,我们当前没这个需求,写都是通过服务器中心写的。
 
Q4:QQ的消息只投递到一个终端?这是多年前了吧?
 
QQ是对移动端做了写优化,离线登陆后会补充投递一部分消息,但做不到全终端一致同步。
 
Q5:如何选择客户端服务器之间心跳的时长?有哪些选择的因素考虑,怎么权衡?

这个说实话我们也在摸索。没有太多数据支撑的经验。移动端其实心跳已经不是很重要了,大家的使用习惯基本上是查看消息回复,然后就沉后台了。我们做了点优化就是所有的消息都视为一种心跳。心跳其实是服务器端判断客户端是否在线的一种方式。移动客户端网络变化能收到通知,一般是几分钟一次。 Web版本没有这种功能,所以要靠心跳来判断网络,一般是几秒钟一次。
 
Q6:发现消息版本不连续后,是全量拉取吗?还是可以判断拉去到哪?

发现消息不连续后,由于版本号是有序递增的,可以计算出中间的差距,直接拉缺失的即可。当然服务器的版本是有限的,如果发现客户端的本地数据太旧,是需要重新全量拉取的。全量拉取的机制不同,Folder的规则不一样。
 
想和群内专家继续交流有关高可用架构的问题,请关注公众号后,回复arch,申请进群。
 
本文策划庆丰@微博,内容由王杰@益体康编辑,庆丰@微博校对与发布,其他多位志愿者对本文亦有贡献。更多关于架构方面的内容,读者可以通过搜索"ArchNotes"或点击页首的蓝字,关注"高可用架构"公众号,查看更多架构方面内容,获取通往架构师之路的宝贵经验。转载请注明来自"高可用架构 (ArchNotes)"微信公众号。
搜索更多相关主题的帖子: 新浪微博 创业团队 技术总监 团队协作 创始人 
2015-10-05 10:00
快速回复:如何设计类似微信的多终端数据同步协议 | Grouk实践分享
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.042689 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved