大家好,我是多喝热水。
前言
最近我们团队正在做一个客户端应用,这个应用需要支持多设备数据同步(本地数据库 <=> 云端数据库 ),且因为产品特性,未来可能面临着大数据量同步的问题,每条数据最大能达到 100kb
左右,所以这也成为了我们开发中的卡点之一。
最终我们经过了两个版本的迭代,设计出了一份还算不错的大数据量同步机制,接下来我会分别介绍我们的设计思路以及迭代过程。
需求分析 & 系统设计
首先在设计之前我们需要明确一下我们的需求,大家可以先思考一下以下几个问题:
1)做数据同步我们需要达到什么样的目的?
2)多设备操作后再进行数据同步,应该以哪个设备的操作为主?【 涉及到数据新增/更新/删除 】。
一、我们要达到什么目的?
1)把我们没有的从云端设备拉下来【 Pull操作 】。
2)把我们有的但是云端没有的推送到云端,让云端保存【 Push操作 】。
二、多设备操作以哪个设备操作为主?
可能发生的情况
假如有一条数据,我在本地数据库修改了,但是其他设备把这条数据删除了,此时我还没有同步数据,所以我本地数据库是无感知的,如果我这个时候进行了同步操作,此时若按照第二个目的逻辑走(云端需要保存它没有的数据 ),这个时候就会导致多设备操作冲突的问题:"云端应该听谁的?"
解决方案
1)给每条数据新增一个 editTime
字段
为什么要 editTime?如果说我们不知道以哪个设备的操作为主,那么我们就以最新的编辑时间为准。但是光这么做还是会有一个问题,删除操作怎么办?数据删除了我就对比不了了不是吗?
2)给每条数据再新增一个 status
字段
为了解决删除的问题,我们在做删除的时候只做逻辑删除【比如status
为1即为删除】,而不会把这条数据真的删掉,这样我们就解决了多设备操作冲突的问题。
我们是怎么做的?
一、第一版同步方案
按照我们之前讲的两个目的,我们可以很容易的想到一种方案,那就是 先拉后推。
拉取数据
把云端的数据全部拉下来,然后和本地数据库去做对比,这样我们就能找到哪些数据是需要更新的,哪些数据是需要新增的,具体的代码我这里就不写了,知道思路写起来应该也很简单。
推送数据
经过上面的对比,我们就可以知道哪些数据是云端没有的以及需要更新的,直接把这堆数据全部推送到云端。流程如下:
这么做有什么问题?
该方案比较简单,我们只需要实现两个接口,但是弊端也很容易发现,由于我们每次都是全量拉取/推送 数据,我们在做压测的时候发现数据量在 2w
以上的时候再做同步,用户界面就卡死了,使用pull
接口拉数据根本拉不下来,浏览器进程中堆积了大量的数据导致浏览器崩溃。
怎么优化大数据量导致崩溃的问题?
分页处理?这么做可以缓解,但是治标不治本,我们可以思考一下,这个过程中我们传输了很多没有必要传输的数据,很多数据其实用户根本就没修改,我们也没有传输的必要。但是如何在云端不知道我本地数据库数据的情况下给到我本地没有的以及做了修改的数据?这 tm 好像无解啊。。。
优化方案
既然传输数据是避免不了的,那我们能否做到少传输数据,把消耗降到最小?答案是有的!回到刚才的问题,只要让云端知道了我本地数据库有哪些数据 以及每条数据的最新编辑时间,那问题是不是就解决了?
1)怎么让云端知道我有哪些数据?【 每条数据都有唯一的ID
,用ID
区分是不是就可以了 】。
2)将每条数据的最新编辑时间给到云端【 也就是editTime
】。
这么一来,我们每条数据只需要传输ID
+ editTime
,我们就可以知道哪些数据是需要创建的,哪些数据是需要做修改的了,所以接下来就有了第二版同步方案。
二、第二版同步方案
先捋一捋改进版同步方案的流程,同样我们还是先拉后推 ,但是在拉数据之前,我们要做一次预同步,怎么理解?也就是告知云端我们本地数据库有哪些数据【只传输 ID 和 editTime 】,通过这一步来过滤掉那些传输过程中的冗余数据。
预同步
查询本地数据库,取出每条数据的 ID 和 editTime 传输给云端,云端对比数据库后告知我们哪些数据是本地数据库要新增/修改的,哪些是云端需要新增/修改的【云端返回的也是数据的 ID 】。
通过预同步后,此时我们拿到的数据结构是这样的
json
{
"prePullSync": {
"postCreateIdList": ["56007654771789"], // 云端告知本地数据库:"这些数据是需要你去新增的"
"postUpdateIdList": ["76007643213789"] // 同上,这里是需要更新的
},
"prePushSync": {
"postIdList": ["26007654771789", /*...*/], // 云端告知本地数据库:"这些数据是我需要操作的,你把这些数据查出来传给我就行了"
}
}
所以现在我们还需要两个接口:"拉取和推送"。也就是最开始的方案一做法,不同的是这次我们使用分页的方式拉/推。
拉取数据【 本地需要新增/修改的 】
我们需要从云端分页拉取 prePullSync
字段的所有数据并写入到本地数据库【分页是为了应对大数据量的情况,类似文件切片上传的原理】。
推送数据【 云端需要新增/修改的 】
不用等待本地数据库更新完,推送可以同步进行,同理,根据 prePushSync
字段所需要的数据 ID
到本地数据库查询对应的数据,然后分页推送到云端。
支持断点续传吗?
如果用户无意间关闭了应用,再次打开应用去同步数据依然可以接着上次的进度传输,因为数据是按批写入的而不是一次性写入的。
该方案的表现如何?
10w+
数据 5 - 8 分钟内传输完毕(每条数据3000+字符数),可能因为网络问题会有一些波动,再往上应该也不是问题,主要就是看磁盘容量能否装的下这些数据了。
总结
最后,我理解的优化就是能不做的就不做,一定要做的就少做。本方案最核心的步骤在于预同步这一步,通过预拉取来过滤大量的冗余数据,只传输真正需要传输的那批数据,避免了每次都全量传输对比,从而达到性能最优解。而且这仅仅是大数据同步传输问题,前端还会出现大数据量渲染卡顿问题,如果大家感兴趣,有时间我会写一下这部分的优化处理。