2016.04.13 丨 壹佰案例

亿级日PV的魅族云同步的核心协议与架构实践

2016.04.13 丨 壹佰案例


沈辉煌魅族高级架构师,魅族云同步负责人。2010年加入魅族,负责云同步的核心协议设计与研发。专注在分布式服务、分布式存储、海量数据下rdb与nosql融合等方面。

本文根据msup和魅族联合举办的《第三期魅族技术开放日-架构设计与优化》录音整理原创首发,转载或节选内容前需获授权。


云同步的业务场景




这是魅族云同步的演进,第一张是M8、M9,然后到后面的是MX系统,M9再往后发展,我们的界面可以看到基本上是没有什么变化的,但本质发生了很大的变化,我们经过了一些协议优化,发展到今天的魅族云同步。

这是云服务对应的网页端,界面非常简洁,可以看到正中间我们有4个模块,提供一些传统数据的讨论,不得不提一下这边的查手机,我们通过它帮一些客户找到了他的手机,它的功能是很强大的,可以定位位置,还可以进行一些拍照。



我们的业务发展了这么多年,一个是手机端,一个是网页端,都说搞技术的是非常寂寞的。看一下我们的业务接入,只是展示了一部分,可以看到我们要跟这么庞大的业务团队进行沟通交流,所以也不是非常的寂寞,要确保每一个业务的场景,还有产品经理以及用户。可能你们比较关注我们的业务规模,这些就是我们的数据,我们主要进讲是怎么做的。

魅族云同步的核心协议

  • 应用范围及出发点

在讲同步协议之前,先讲它的应用,云同步协议的应用是非常广泛的,可以做云相册、云备份、云同步等。先说下讲协议的出发点,(可能跟大家做的业务不一样,但希望借着讲这个协议,让每个业务能在这里面找到参考的价值)。

基于数据和操作的同步,这是我们对同步协议的定义,举一个例子来讲,有一个联系人,里面有姓名和电话,我只改了一个电话,最后同步的对象是姓名、新的电话以及更新,所以是三项数据。简单来讲,不管你改的数据是大还是小,最后同步的都是一个整体

  • 数据类型


我们看看数据类型,其实很简单,结构化数据和非结构化数据在结构化数据这块,我们又细分为典型数据和小数据,为什么做这样的区分?因为我们接的业务非常多,有几十个业务,不可能全部业务一视同仁,所以我们制订了这样的小业务。举个例子,像联系人分组,用户的联系人分组几十个已经不得了了。

我们针对不同的数据类型设计不同的协议,右边是我们这部分的重点,4个协议,第一个就是我们的核心,也就是魅族的MZ-SyncML协议、半同步协议、针对小数据协议匹配的是一次同步协议,文件的话有文件同步协议和半同步协议。

  • 数据格式-流量-money


我们看一下数据格式,SyncML协议采取的是这样一个Json数据格式,变量非常长,可能有十几个字母。

我们经过优化,设计了一个精简的Json,变量只有1-2个,当整个对象变量较多的时候,这个地方就相当可观了,再往下我们引入Protobuf来对比,它是以顺序为传递的格式。我们的测试数据是,假设传统Json是100%,我们采用了精简Json可以省下50%的流量。再然后我们采用了Gzip,测试数据是可以减少40%,所以最后我们算一下,精简JSON+Gzip=传统Json×50%×60%=30%,如果我做了这样的优化,假设原来外网带宽是100M, 现在就只有30M了,这是一个非常可观的优化,所以我们说做流量就是钱。

  • MZ-SyncML协议

这是云同步的协议,我们这个协议是基于微软的SyncML协议优化过来的。所以我们来看看客户端的表,包括anchor表、mapping表、一个是业务表,业务表有自己的更新时间,也是客户端时间戳。我们来看看服务端的表结构,多了一个ServerAnchor和Updatetime,原因是如果服务端时间和客户端时间不能等同处理,等同处理是会出乱子的。

所以我们的设计是分开的,服务端的增量数据就是通过服务端的时间窗和服务端的Updatetime来获得,最后确定同步协议到底是走存量的还是走增量的,我们的表结构就是这样的。

看看我们的时序图,如果不了解SyncML,我简单科普一下,为了处理数据更加严谨,所以它做了4个交互逻辑,第一个交互逻辑就是同步类型、同步点,确认到底是什么同步类型;第二个交互逻辑是分批提交数据,第三是分批摘取数据,最好记录同步结果。它里面有一些什么本质呢?我刚才讲到全量和增量,所以我们也叫慢同步和快同步,对应全量和增量

什么是快同步和慢同步,慢同步是等于我没有做一次同步,这个时候是第一次同步,我就要做两边存量数据的交换,最终保持的结果是我这边的业务跟这边的业务是一样的,有了第一次的基础之后我们就会根据时间戳,也就是同步点,下次做增量数据同步,就可以达到快速并且是增量的交互目的。这是我们魅族MZ-SyncML协议,对比原先的SyncML有哪些特点?


这是基于SyncML协议改进的,4步交互逻辑,慢同步和快同步,我们是基于JSON,这个地方我们正在优化,适用范围非常广。它是一个分批交互,分批交互不管你的数据是多大,是一万条、几万条、几十万条,你用它都是没有问题的。网络数据包是有限的,不可能在一个数据包里面把所有的数据都提交了,另外我们支持刷新同步。简单来讲,同步冲突的时候到底是以谁为准,在协议里面会做一些定义,到底是以谁为准。最核心的协议就是这样的特点。

  • Semi-Sync协议

为什么我们有一个经典的MZ-SyncML,还要设计半同步协议呢?我买了一台新的6,要从云端数据里面取回我的一万条短信,我就要走慢同步,在同步的过程中我切换了网络或者我走开了,导致网络变化,这个时候这次同步就会失败,下一次再同步的时候,我的数据怎么办?我可能上一次同步成功了一半,难道我下次同步的时候还要把同步的那一半也同步回来吗?如果没有半同步协议,是需要把之前那此同步成功过的数据同步回来的,基于这个出发点我们设计了半同步协议,这个半同步协议是我们设计出来的,之前SyncML是没有这个东西的。

让我们来看看客户端的表,很明显多了一个半同步点,这个半同步电实际上是ClinetAnchor的复制,这里也是一样的,我把它同步过来。服务端的表也是一样的,多了一个半同步点的字段,它的设计出发点其实跟客户端是一样的,只不过它的匹配是服务端时间戳,客户端的时间和服务端的时间是永远不可能同等处理的。


我们截取的是Semi-Sync协议的四步,我们来看看蓝色部分,第二步就是往服务端写数据,这个时候我写映射关系,就把新的同步点写入新的SemiAnchor字段,服务端也是一样的,这次假设整个都跑完了,在这个过程中,我同步失败了,我已经成功同步了五千条短信,因为是同步失败,所以我们还要再次同步,再次同步的时候,我们的目的就体现在红色这部分,这部分是客户端要选取它的增量数据,就是刚才提到的一万条短信,这里面怎么排除掉成功的那五千条,通过它的SemiAnchor进行匹配,红色部分这个地方就会从一万条减成五千条,半同步就是这样一个设计。


半同步的特点:首先它是MZ-SyncML协议的补充,不是一个独立的协议,简单来讲就是断点续传,在慢同步里有大用处,快同步也有用处,分批、单批次数据都可以用,同步成功,它就没用。

  • One-Sync协议  


另外就是小数据,小数据的特点是数据量很小,这个如果采用魅族的MZ-SyncML就不太合适了,会显得很臃肿,数据量很小,却用一个庞大的协议来做数据交互。基于这个出发点我们做了一个小数据同步协议—One-Sync协议。我们来看看客户端的表:这个地方多了一个ServerAnchor,它还是要用自己的时间系统,所以还是要有一个ClinetAnchor,服务端没有了ClinetAnchor,只有一个ServerAnchor。

这里面就面临着一个问题,一次握手,其response可能是失败的,导致两边同步点不一致。  我们接受这一个协议缺点,但不会导致非常严重的后果,这里面是一个旧的同步点,下次再同步的时候,我们以传过来的同步点做同步。 简单点讲,就是不支持断点续传。


One-Sync协议的特点非常简单,它一样有慢同步和快同步,它也是同样支持刷新同步和Two-way的同步,它只有在小数据情况下才有用,同步失败有损回退,我们认为这是可以接受的,因为从快同步回退到上一次的快同步,而不是回退到慢同步,如果是回退到慢同步是我们不能接受的。

  • File-Sync协议


我们来看看最后一个文件同步协议-File-Sync协议,我们来看看它的应用场景,比如云相册,用这个同步协议是可以的,还有就是附文件同步,联系人头像、便签文件等。

其本质还是MZ_syncMl协议,关键点是同步成功后的uuid,这是唯一标识,用这唯一标识跟业务匹配,就可以实现相关的功能了。


我们来讲一个美丽又忧伤的故事,云同步的客户端是非常多的,一个帐号可以登录多个地方,这个时候如果点同步,就会触发冲突。这是其中一个场景,左边是客户端1,右边是客户端2,客户端1是后云,客户端2要自己同步,但早于客户端1提交数据。

客户端2于客户端1提交数据,慢与客户端1同步数据,首先客户端1会生成一个同步点1,相应的客户端2来了也会有一个同步点2,这非常明显服务点2肯定大于服务点1,客户端2提交的数据写入了Data2数据,再往后客户端1也会写入Data1的数据,Data2的同步时间要大于Data1。

下次同步的时候,结果就会丢掉Data1的数据,增量数据的选取是基于它的时间窗,客户端2同步成功了,在这个地方再同步的时候就会有一个Anchor3,它要再拿云端的数据,增量数据的选取就是大于Anchor2,结果就不会包括anchor1。

后面我们解决了这个问题,解决问题的办法非常简单,就是排他锁。我们只在第一步和第二步的时候加锁,第三步和第四步的时候不加锁。

这是MZ_syncML的例子,对于一次同步协议是不是也有这样的问题,当然也有这样的问题,解决办法就是类似的,即排他锁。

魅族云同步的架构设计

这是魅族云同步在我们数据中心的一个实际部署示意图,上面这三个就是我们对外的服务,首先是手机端、网页端、第三方接入平台,右边是我们依赖的一些主件,这里只是写了一部分,中间比较核心的是三个单元,每个单元是一个独立的小集群,里面自成服务、自成系统,再往里面是我们按照业务划分出来的模块,再往下就是缓存和存储。

为什么会有这样的单元设计,只有一个大的单元可不可以,也是可以的,甚至这些模块都不要,也是没有问题的。我们早期就没有这些模块,把整个云服务放在一起,最后发展成这样子,首先是从模块化的思想出发,我们把相应的业务进行集中,比如文件模块,文件模块的IO消耗比联系人模块要高,如果不分模块,都在一起,文件的服务就会影响到正常的其他服务,我们分了模块,就专项专管,

做了这样一个UNIT划分。为什么?首先它是一个子服务,这个时候你要拿走这里面的东西是非常简单的,因为一个UNIT只服务合理的用户数,比如一个单元只服务500万用户,再多就加单元,再多就加数据中心。 这个单元还有一个好处就是容灾比较灵活,这在后面会讲到。

  • 多个数据中心

刚看完了单个数据中心,现在来看看3个数据中心,就是机房,这里面的核心是一样的,跟刚才的单个数据中心是一样的部署,唯一不同的中间有一个大圈,叫GSLB也就是用户就近访问。


  • GSLB

GSLB业界里面有类似的服务,但这是魅族自己研发的域名解析系统,原理非常简单,根据用户的IP或者是用户的ID,给你分发到一个最优的数据中心。比如你用的网络是在广州,我就给你分到广州的机房,这个时候你的访问肯定是最优的,因为你的网络链路最短。

除了这样的就近访问还有什么好处呢?可以解决DNS劫持的问题,我不知道在座的有没有遇到过DNS劫持,我们遇到过非常多,特别是偏远的地区,比如新疆,各种运营商会给你搞DNS解析,如果走域名它就是访问不成功,介入了GSLB以后,可以把域名变成IP,就可以解决DNS劫持的问题,我们上线之后再也没有遇到这类问题。

  • 自路由

另外一个是流量调度,GSLB接近访问本身就是一个流量调度,假设GSLB断了,所有依赖GSLB的服务就完了?肯定不能这样,所以我们做了一个自路由,非常简单,这个路由组件是我们自己开发的,这里面集成了GSLB的工作,当用户访问联系人模块的时候就会调这个路由组件的GSLB功能,达到了GSLB一样的功能。

  • 扩展的GSLB

GSLB设计之初是为了解决移动应用的访问问题,如果我们用浏览器访问域名是不是面临同样的问题,如果我们只用这个是解决不了的,所以我们搞了一个扩展的GSLB,其本质核心还是在这里,怎么做呢?我们把一个业务分成了总域名、多个子域名,每个子域名匹配一个数据中心。这是一个非常好理解的思路,也有其他友商是这样干的。一个用户过来了,我们根据用户规则匹配对应的子域名,你就可以访问到最近的IDC,一样的效果,这就是我们的接近访问。

  • 异地容灾

UNIT还有什么好处?还有异地容灾。我们最外面有三条红色的线,是一个循环,互为一个备份,比如在数据中心1里面有文件存储系统,还有DB、数据缓存到这里,这个地方缓存是按照UNIT缓存的。这个时候就讲到了挖掘机,它的功能很强大,它把数据1的光纤挖掉了,怎么办?我们通过GSLB分流,把所有用户牵到数据中心2里面去,这个时候我们业务的高可用就得到保障了,全部用户访问到这里来,只是慢一点,数据一样可以得到有效的访问。

云同步的技术难点

  • 模块多,通信难


这是一个通用的问题,模块多,通信难我们有Zookeeper、Kiev、MQ,这都是站在巨人的肩膀上做的。

  • 长连接?短连接


长连接和短连接,我们来算算公式,首先长连,我们刚才讲到MZ-SyncML协议里面有4步的交互过程,手机发出了一个请求,这个时候与云端建立连接,并且是一个长连接,第二步是一个分批提交数据,红色部分是客户端的耗时,第三步是一个分批获取数据,从云端拿数据,红色部分依然是客户端的耗时,最后一步是最后一次握手,也就是云端写状态数据和手机写同步点,前后我们是不算到在连接里面的,因为在云端写状态数据内部,客户端已经断开了,整个长连接的耗时就是黑色部分加红色部分,这是长连接的情况。

我们再来算算短连接的情况,第一步是一样的,第二步是分批提交,一样的没有了红色耗时,这个地方是客户端的耗时,这个地方其实我们每次请求都会建立一个连接,交互完了之后,我就关掉链接。第三步是分布获取数据,一样的没有了红色部分,最后一部分是最后一次握手,也是一样的。

这里面怎么衡量呢?我们要分别测出来客户端的耗时和服务端的耗时,这个地方我直接把数据给你们,红色部分我们测了自己的云同步,也测了小米的,红色部分的耗时占了整个耗时的80%以上,这个值并不夸张,实际有可能比这个值更高,可能达到90%甚至90%多。


这是一个什么结果呢?假设我们采用长连接,我的云端资源跟你创建了一条连接,但80%的时间是空闲的,没有工作,所以长连接等于高成本的浪费,一台服务器能够承担的链接数是有限的,你给我创造的链接80%不能用,摆明了就是浪费,所以很明显我们的选择就是短连接,如果采用长连接,对于Qps的值也不是很好的。


  • 安全


安全我只讲两点,第一是假设我们的数据库或者文件系统被别人偷走了,我的数据不能马上被别人读出来;第二点是模块安全。我们刚刚提到模块非常多,相应的就是依赖的非常多,假设某一个依赖组件出现问题了,能否导致我们的业务也出问题呢?

难道我们就抓着那个依赖说是你出了问题,不是我的问题,但最后的结果是云同步出了问题,所以这里显得尤为重要,模块越多,这个地方越重要,就是人们常说的自闭环。不管你的模块是怎么样的错,要保证我的整个服务是OK的。假设依赖模块一旦出问题了,我就不提供服务,我告诉你服务端出错了,服务端不可访问了。

  • 备胎也有春天


高可用,多IDC,还有就是多线路,解决延时的问题,目前我们有专线还有VPN,我们的VPN都是采用多线路,这个地方要结合每个企业的实际情况,不一定每个人的业务前提条件都是一样的,这个地方其实就是多个单元,再往后就是多个集群。

  • 存储+路由


接下来说存储跟集成的GSLB路由组件,采用RDB+Hbase存储,高低频数据分离,我们在实际的业务场景里面怎么区分高频数据和低频数据呢?有些业务可能分不出,但总的来讲像应用中心这种也是非常容易分出来的,云服务的一些常用功能里面有一个回收站,回收站就是垃圾桶,都是被用户删掉的数据,我们又不能真的把它删掉了,我们要给它访问,所以分为是低频数据,垃圾桶里面的数据用的就是Hbase,用户正常用的数据就是RDB。我们实际实现的回收站可能更高级一点,是时光机,顾名思义,这非常好理解。

RDB多库,Userid水平拆分这是我们的根本,因为几百个库其实也蛮大的,我们的根本就是Userid水平拆分,当数据大的时候,这是一个很好的方法,包括一些用户表、文件存储,其实也是按照Userid进行拆分的。

最后一点就是我们的路由组件,是我们自己实现的,里面包含的功能有DB路由,你要访问这么多数据库,总不能去我的程序里面弄,所以DB路由、文件路由、连接池自管理,最后一点就是GSLB,它对业务是透明的。

  • 云同步的特性


我们得到了云同步的特性,也不是说云同步特有的,很多企业都在搞物联网以及O2O、手游,像九游等,他们在考虑这些业务实现的时候,可能也会遇到同样的问题。我的意思是假设大家做业务的时候可以从这些方面考虑,我觉得应该是可以少踩一些坑,是这样的出发点。

云同步的APP端可能会遇到的技术问题

  • 自升级

最后聊聊客户端的问题,我们的客户端是跟着我们的手机走的,这里面有一个很大的经验,就是自升级,这个太重要了!现在很多移动app应该都有这样的功能,但以前魅族云同步就没有,以前要修复一个bug,一定要推荐用户升级才能实现,这太痛苦了,在前一两年我们就把这个问题解决了。

  • 流量

为什么这里要讲流量呢?前面我们提到的流量是整个宏观的角度,可能是从整个厂商、企业里面的带宽考虑的,客户端面向的用户可能更关心的是自己的流量。怎么给它减少呢?

第一点,我检测它开启wifi的时候开始同步,当我检测到它打开了wifi的时候触发同步,我就把本来要跟移动网络的场景切换到了wifi网络。第二点,就是合并策略,我把多次的同步合并成一次,这里面实际具体的策略就不讲了,但它的思路就是我会把多次本来同步的数据合并到一次,这个时候请求少了,流量也就少了。

  • 功耗

虽然说现在手机的电池越来越大了,但相应的CPU、显卡、屏幕的功耗越来越高,抛开硬件不讲,我们做一个非系统应用,又不能占用用户过多的电池电量,在功耗方面我们有哪些策略优化呢?

A、像wifi一样,检测到用户充电的时候同步,这个时候不需要使用用户电池的电量。

B、合并策略,在这个地方我们也有一些优化,多次同步耗电量更高一点。

C、我们不需要常驻,早期的云同步采用的是常驻的方式,只要是手机,不管用不用,它就会挂在那里。我们在把这个地方改成了非常驻的实现方式,当他知道需要同步的时候才唤醒我的进程,这个地方是非常省电的。

D、还有就是动画,像手游,可能动画需要非常炫,我们这个云同步对界面的要求不高,所以我们没必要在动画上面浪费过多的电,我们只要按照正常展示交互弄出来就可以了。

  • 速度

我刚才讲到整个云同步的耗时客户端占了80%以上,那么多友商在做却做不好,它有一些根本的前提,但也不是说就不能优化的。搞安卓的应该都清楚现在安卓基本上都用sqlite的数据库,这里面有一个非常重要的问题,我们的数据都是批量的,批量写入到底是不是真正批量写入,这是值得我们研究的。

我们研究了batch的核心方法,一个for里面有一个insert,insert本身就是一个事务,怎么快得了呢?所以这是一个伪batch。另外就是自己的业务速度要提高,我们早期的代码里面有很多for+for,两个循环,经过我们的优化,全部替换成了for+map,我的经验是80%到90%的双循环都可以用for+map,这是空间换时间,空间换时间,应该说现在是我们更常用的方式。

  • 接入规范

我们接入的业务有几十个,这里面有非常多的app,需要跟他们沟通清楚,每个业务去讲非常困难,所以我们设计了一个接入规范,一个新增的APP要接入,按照我们的接入规范来就可以了。当然每个业务的接入规范可能,像游戏平台呀、应用平台等。

魅族云同步的未来

  • 业务发展

魅族的载体是手机,它可能会有其他的第三方应用,又不想自己做,这个时候就需要寻求团队的帮忙了,需求是越来越多的,怎么办?像做移动APP的都不希望自己的客户端太大,可以集成几十个应用,但是不希望集成几百个应用,于是我们做了一个SDK-API,还有一个云端扩展式服务,这里面是什么意思?刚刚提到的接入规范只是在代码这层,SDK-API完全分离出来。

  • 开放

像魅族今天的开放日,还有魅族的SDK,大家都很容易拿到我们的SDK开发,当然我们有一个开放心态。魅族云同步明天能够走出Flyme,在外面的平台能够有自己的世界。

Q&A


我想知道知道用户拆分是怎么做的?期间遇到了一些什么问题?能否跟我们分享一下?   

我们当时也不是一下子到这个规模的,后面我们发展的时候,总结出来了三点:

媒体联系

票务咨询:赵丹丹 15802217295

赞助咨询:郭艳慧 13043218801

媒体支持:景    怡 13920859305

提交需求