前言
前两个项目,客户端与服务器端需要进行大量的数据交互,设计方案时并没有考虑到数据量多大的情况,导致后期在实际生产环境中,经常出现客户端崩溃现象,今天在一篇博客上看到了一种简单的解决方案,转载过来,便于后期查看,原文地址:http://blog.cnbang.net/tech/2258/
在邮件/日历/SNS等客户端里,客户端数据要不断与服务端进行数据同步,在同步过程中,只拉取有修改的数据,称为增量更新,增量更新方案一般有两种,一是对比,二是日志。
对比
对比就是客户端请求服务端所有关键数据,跟本地已有的数据进行对比,筛选出增删改的数据进行更新。
用对比方法的好处是服务端什么都不用做,坏处是客户端逻辑复杂,耗网络流量。在这种方案里,数据的新增和删除很容易判断,根据客户端数据的id列表和服务端数据的id列表进行对比就行,若要判断哪个数据有修改则比较麻烦,需要取回数据进行对比,如果从服务端拉回所有对所有数据进行对比会很耗网络流量,有一个优化方式,就是对每个数据的修改进行标记。
以日历为例,一个日历可修改的字段很多,例如时间段,内容,邀请人等,全部拉回来对比不现实,对此可以在服务端给每个日历事件新增一个字段tag,表示这个日历事件的版本,服务端更新一个日历事件时会同时更新这个tag,客户端只需要取回每个id对应的tag,跟本地保存的tag对比,不一致表示这个日历事件已经更新,再去获取日历实体就完成更新了。
若服务端因为某些原因无法给每个数据保存一个版本标记,可以实时计算,在客户端和服务端约定一个算法,把所有可变参数拿出来,通过特定算法hash出一个值,对比这个hash值判断是否需要更新。
邮件协议IMAP,日历协议CalDAV就是用这种方式做增量更新,IMAP并没有做上述的优化,在判断邮件有没有更新时只能乖乖把所有数据请求回来对比,数据是XML,算是相当低效的协议。CalDAV给每个日历事件加了上述的tag,直接对比即可知道是否需要更新。
日志
日志指服务端记录数据的每一次增删改,用一个类似版本号的sync-key标记这次修改,客户端通过一个旧的sync-key向服务端请求,服务端返回这个sync-key与最新sync-key之间所有的修改给客户端,完成增量更新。
这个sync-key在服务端的实现上可以是时间,也可以是一个自增的id,sync-key之间有顺序关系就行。在一个数据集里,每次数据有更新,就新增一个sycn-key,并记录这次更新。图示这个过程:
这个方案客户端逻辑很简单,但服务端负担较大,每次数据更新都要记录,客户端请求时需要查询给出相应的数据。这个方案在实际操作中还有两个问题:
一是时间长了服务端保存数据量过大。可以通过限制记录的条数解决,超过限制就删除最旧的记录。这样做会出现一个问题,若客户端带着在服务端已被删除的sync-key上来请求,该如何处理?一般做法是返回一个错误给客户端,让客户端重新拉取所有数据。
二是若客户端sync-key过旧,增量数据可能过大。客户端数据太老,有太多数据需要更新,若一次性返回所有增量数据,这个请求可能会很大,请求时间太长,成功率也会很低。解决方式是分多次请求,客户端和服务端可以约定一个字段作为阀值,服务端每次返回的增量数据量不超过这个阀值,若总数据超过这个阀值,则分多次请求,通过每次请求返回的sync-key定位下次请求该返回哪些数据。例如客户端sync-key是100,服务端最新sync-key是1000,阀值是50,客户端第一次带sync-key=100请求,服务端第一次返回sycn-key 100-150这一段增量数据,并返回sync-key=150,并有一个值告诉客户端这个sync-key还不是最新,客户端再带上sync-key=150请求,以此类推,直到sync-key=1000。
微软的Exchange/ActiveSync就是用这种方式实现增量更新,ActiveSync还用WBXML压缩了数据,更适用于移动端。此外日历协议CalDAV的也有一个扩展协议RFC6578使用这种方式。ActiveSync和CalDAV扩展协议都有分多次请求增量数据的策略。
小结
对于Timeline式的数据,增量更新方式多是以上两种,或者这两种的变体,可以根据业务特性修改或简化其中的逻辑,例如对于微博Timeline,它可以不考虑微博的修改,不考虑同步评论转发数的变化,不考虑同步删除的微博,并且每一条微博都有一个递增的id,那它的增量更新逻辑就很简单,只需要把客户端最新一条微博的id作为since_id传到服务端,返回比这个id更新的微博就行了,这里微博id相当于日志方式的sync-key,算是对日志方式的一种简化。