背景

最近在移动开发App时遇到一个问题:在服务端与客户端之间需要进行修改,删除,更新,添加等操作同步,为此研究了一番,其中Leanote参考了印象笔记App的同步原理。

Leanote同步机制参考Evernote的机制, 关于Evernote的同步机制参考: http://dev.evernote.com/media/pdf/edam-sync.pdf

前言

Leanote主要由Notebook, Note, Tag, File(图片/附件)组成. File依附于Note存在. 当Note删除时, 其包含的File也会删除.

每个帐户(User), Notebook, Note, Tag都含有字段 Usn(Update Sequence Number), 它是整个同步系统中最重要的字段. User的Usn用于标识账户中的每一次修改, 每次修改Notebook, Note, Tag后User的Usn就会+1. 而Notebook, Note, Tag的Usn标识着一个对象最后一次被修改时的账户Usn.

举个例子, 在某一个时刻User的Usn是100. 我添加一个笔记Note1, 那么User的USN会变成101, 此时该Note1的USN也是101. 然后我再添加一个笔记Note2,这时User的Usn会变成102,Note2的Usn也是102,Note1的还是101. 这样一来我们每次同步后记录一下当时User的Usn保存为LastUSN, 下次同步的时候如果账户的Usn > LastUsn,说明账户中有东西被修改了, 此时需要先将服务器端的修改同步到本地.

当帐户第一次登录时, 此时需要进行一次全量同步, 即将服务器上所有和数据都同步到本地. 而之后用户在本地操作后, 就需要每次同步所修改的数据.

同步基本的步骤如下:

  1. Pull: 判断服务端是否有新数据, 即 通过 本地LastSyncUsn 和 服务器端Usn对比, 如果本地LastSyncUsn < 服务器端Usn, 表示服务端有修改, 此时需要同步服务器上的数据到本地. 详情请见 "同步数据".
  2. Push: 将本地修改的数据发送到服务器端. 详情请见 "发送改变".
  3. 保存状态: 获取最新同步状态, 保存服务器端最新的Usn为本地LastSyncUsn.



同步数据 Pull

从服务器端同步数据到本地.

先判断服务端是否有新数据, 即 通过 本地LastSyncUsn 和 服务器端Usn对比, 如果本地LoastSyncUsn < 服务器端Usn, 表示服务端有修改, 此时需要同步服务器上的数据到本地.

同步数据步骤:

  1. 同步Notebook
  2. 同步Note
  3. 同步Tag

同步Notebook, Note, Tag的步骤基本一致, 现拿同步Notebook作为示例, 伪代码为:

// 获取远程要同步的数据
var lastSyncUsn = getLastSyncUsn(); // 本地保存的上次同步的Usn
function syncNotebook(lastSyncUsn) {var afterUsn = lastSyncUsn; // 表示取lastSyncUsn之后的notebookwhile(true) {// 调用api, 取afterUsn的10个笔记本var notebooks = api.call('/api/notebook/getSyncNotebooks?afterUsn=afterUsn&maxEntry=10');// 将获取到的notebooks存到本地updateNotebookToLocal(notebooks);// 如果取到的notebook == 10, 表示很可能还有要同步的notebookif(notebooks.length == 10) {afterUsn = notebooks[notebooks.length-1].Usn; // 取最大的Usn作为下一个标准}// 如果 < 则表示不够了, 没有要同步的Notebook了.else {break;}}
}// 将远程数据保存到本地
function updateNotebookToLocal(notebooks) {for(var i = 0; i < notebooks.length; ++i) {var notebook = notebooks[i];// 获取本地的Notebookvar localNotebook = getLocalNotebook(notebook.NotebookId);// 服务器端已删除了, 此时删除本地的if(notebook.IsDeleted) {deleteLocalNotebook(notebook.NotebookId);}else {// 如果本地没有修改, 那么将notebook保存到本地if(!localNotebook.IsDirty) {db.updateToLocal(notebook.NotebookId, notebook);}// 本地有更新, 此时需要处理冲突else {}}}
}

获取Note, Tag要同步的数据的API为

  • /api/note/getSyncNotes
  • /api/tag/getSyncTags



如何处理冲突?

冲突发生的原因: 本地修改了, 且服务器上也修改了. 此时同步服务器上的数据到本地, 发送本地数据的IsDirty=true. 此时需要处理冲突.

处理冲突由客户端来完成, 最极端的做法是: 客户端可以完全将服务器上的数据覆盖到本地, 或者完全舍弃服务器端的数据而使用本地修改的数据.

但这样做很可能会丢失数据, 所以当遇到冲突时, 应该将服务器上的数据下载到本地和本地冲突的数据进行关联, 最后采用哪个数据由用户来决定.

发送改变 Push

将本地修改的数据发送到服务器端.

发送改变步骤:

  1. 发送修改的Notebook
  2. 发送修改的Note
  3. 发送修改的Tag

发送改变, 即得到本地修改过的Notebook, Note, Tag, 然后将修改后的信息发送到服务器端. 所以本地需要有一个标识来识别哪些数据改变了. 比如可以设置一个IsDirty的字段来标识. 如果本地的Note修改了, 更新该Note的IsDirty为true, 待发送改变成功后, 设其IsDirty=false.

下面通过发送Notebook改变作为例子:

function sendNotebookChanges() {var dirtyNotebooks = getDirtyNotebooks();for(var i = 0; i < dirtNotebooks.length; ++i) {var dirtyNotebook = dirtyNotebooks[i];// 调用api, 发送改变, 必须要传usn, 服务器端根据传过去的usn来判断是否冲突var ret = api.call('/api/notebook/updateNotebook?usn=dirtyNotebook.Usn&title=dirtyNotebook.Title');// 修改成功, 将服务器端返回的Usn更新到本地, IsDirty设为falseif(ret.Ok) {updateLocalNotebook(Notebook.Id, {Usn: ret.Usn, IsDirty: false});}// 更新失败, 有冲突, 表示服务器上的数据新于本地, 此时需要解决冲突, // 解决冲突的方法可以将服务器的数据覆盖到本地else if(ret.Msg == "conflict") {var serverNote = apil.call('/api/notebook/getNotebook?notebookId=dirtyNotebook.id');updateNotebookToLocal(serverNote);}}
}

修改Note, Tag的api为:

  • /api/note/updateNote, deleteNote
  • /api/tag/addTag, deleteTag


获取最新同步状态


调用API "/api/user/getSyncState" 获取最新同步状态, 将Usn保存到本地为LastSyncUsn;

iOS开发者交流群:446310206

笔记类app之Leanote同步机制 韩俊强的博客相关推荐

  1. iOS中 HTTP/Socket/TCP/IP通信协议详解 韩俊强的博客

    版权声明:本文为博主原创文章,未经博主允许不得转载. 每日更新关注:http://weibo.com/hanjunqiang  新浪微博 简单介绍: [objc] view plaincopy //  ...

  2. iOS开发中的零碎知识点笔记 韩俊强的博客

    每日更新关注:http://weibo.com/hanjunqiang  新浪微博 1.关联 objc_setAssociatedObject关联是指把两个对象相互关联起来,使得其中的一个对象作为另外 ...

  3. iOS中 Realm的学习与使用 韩俊强的博客

    iOS开发者交流QQ群:446310206  有问题或技术交流可以咨询!欢迎加入! 这篇直接搬了一份官方文档过来看的 由于之前没用markdown搞的乱七八糟的 所以重新做了一份 后面看到官网的中文文 ...

  4. iOS中 本地通知/本地通知详解 韩俊强的博客

    布局如下:(重点讲本地通知) iOS开发者交流QQ群: 446310206 每日更新关注:http://weibo.com/hanjunqiang  新浪微博 Notification是智能手机应用编 ...

  5. 2017年年终总结 韩俊强的博客

    前言 不知不觉,2017年又接近尾声了,又到了该写年终总结的时候了,往年这个时候都会熙熙攘攘,各大平台提早预热过年的气氛,而今年显得格外的平静,这可能正如我的现在的心境,波澜而不惊!因为今年整体过的只 ...

  6. 攻克苹果2.1被拒问题 韩俊强的博客

    前言 在解决 Guideline 4.3 问题(可参考:<攻克苹果4.3被拒问题 >)后, 我们今天来解决 Guideline 2.1 问题. 时间过得真快, 转眼快要过年了, 但是文章还 ...

  7. Jekyll搭建个人博客 韩俊强的博客

    之前写了一篇HEXO搭建个人博客的教程获得了很好评,有很多读者主动给我打赏,在此感谢. 如果你看过我的文章会发现我现在的博客样式跟之前是有很大的区别的,之前我也是使用 HEXO 搭建的博客,后来发现使 ...

  8. iOS10 适配 ATS(app支持https通过App Store审核) 韩俊强的博客

    一. HTTPS 其实HTTPS从最终的数据解析的角度,与HTTP没有任何的区别,HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的 ...

  9. iOS中 为 iOS 建立 Travis CI 韩俊强的博客

    每日更新关注:http://weibo.com/hanjunqiang新浪微博! 你是否曾经试着为 iOS 项目搭建一台支持持续集成的服务器,从我的个人经验而言,这可不是一个轻松的活.首先需要准备一台 ...

最新文章

  1. 小学三年级上册计算机计划书,小学三年级班主任工作计划书
  2. Linux 下的动态库、静态库与环境变量
  3. 【VS开发】【DSP开发】WinDriver简介(或介绍)
  4. 【Linux】时间同步设置+防火墙设置+SELinux设置
  5. java Lock 源码分析
  6. IntelliJ IDEA 连接数据库 详细过程
  7. python中的进程(二)
  8. mysql动态sql语句_mysql 存储过程中使用动态sql语句
  9. onlyoffice毕升office
  10. springboot+华迪企业合同管理平台 毕业设计-附源码191555
  11. 办公专用计算机配置,办公电脑用什么配置的好 2017办公电脑配置推荐
  12. 机器学习之------信号处理(入门原理)
  13. tushare 获取复权数据
  14. Java 数据填充到word模板中
  15. 证书与签名(二):数字签名流程与签名认证流程
  16. 1和4互素吗_互素是什么意思判别方法,1和2互素,互素
  17. Pizza Cutter Gym - 101908C
  18. 你学不好英语,可能是精神内耗太多了
  19. #10064 「一本通 3.1 例 1」黑暗城堡(spfa+乘法原理)
  20. python库numpy使用技巧(一)——提取数组中非零元素

热门文章

  1. Greenplum的系统表
  2. Personalized Web Search总结
  3. Paper reading (三十二):Personalized Nutrition by Prediction of Glycemic Responses(Results)
  4. 大数据技术的概论(2)
  5. LISP excel 冻结拆分_[转载]Visual LISP与Excel电子表格
  6. android原生相机apk,原生模式相机APP
  7. java爬网页图片到本地
  8. 照度计的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  9. ISTQB基础级认证参考书
  10. Facemark:使用OpenCV进行面部特征点检测