数据库 为支持移动端离线模式-数据库采用 UUID 字段

ery · 2015年03月12日 · 最后由 hooooopo 回复于 2015年03月13日 · 5863 次阅读

今天我给大家讲一个故事 如果雷同,那很正常

很久很久以前, 我们开发了一个团队协作工具,叫 Infoboxme https://www.infoboxme.com/ 类似 worktile teambition trello todoist 简单的理解就是有一个表叫做 tasks,实现对该表的增删改读操作。

数据库是 Mysql Web 端是 前后台分离的模式。 移动端是 原生的 iOS 和 Android 后端是 Rails

神对我们说, 移动端必须支持离线模式, 离线的时候,可以创建 Task。 做地铁的时候,经常离线。 神啊,离线的时候,干点别的不好吗,非创建 Task 感冒?

(神一样的需求) (神一样的产品) (神一样的解决方案) (就是这么被神出来的)

神走了,TMD 问题来了:

Web 端创建 Task 的时候, 同步调用后台 API, 可以立即获取到 API 的返回值,其中包括新的 Task 的 ID。 之后 Web 端用这个 Task 的 ID,来实现查询,修改,删除等操作。 (从此以后他们过上了幸福的生活...)

移动端创建 Task 的时候, 异步调用后台 API,因为需要支持离线模式, 后面接着,还要进行其他操作,比如, 创建 Task 的 Comment 删除 Task 修改 Task 等等 此刻无法立即获取 API 的返回值!!!拿不到 Task 的 ID,怎么办??? (完蛋了) (幸福的生活 木有啦) (下面也木有了........) (突然之间,2 个移动端程序员,1 个晕到了,1 个石化了)

凭我们多年的行医经验, 我们决定使用 UUID(户口本上曾用名是 GUID) 方案如下

移动端创建 Task 的时候, 自己负责生成 Task 的 UUID。 拼装 API 参数的时候,把 Task 的 UUID 放进去。 异步调用后台 API 如果 API 创建 Task 失败,移动端可以利用 Task 的 UUID,把本地的 Task 数据删除。 如果 API 创建 Task 成功,移动端可以利用 Task 的 UUID,实现查询,修改,删除等操作。 (30 秒之后,2 个移动端程序员复活了) (从此以后他俩过上了幸福的生活...)

API 设计如下

  • 有两套 API Web 版,移动版 (不要问我为什么不做成一套,那是另外一个故事)
  • Web 版本依赖 Task 的 ID,和 UUID 无关
  • 移动版本依赖 Task 的 UUID,和 ID 无关

后台 API 的实现

  • Task 表加个 UUID 字段,不能为空,不能重复
  • Rails Model Task before_validation 分析 task.uuid。 如果没有,创建 uuid。

实施后的效果

  • 上线近一年啦,记录数量在百万级别,木有性能问题
  • 前后端程序员,没发现任何瓶颈和障碍

感谢

力胜 灿玉 庆文 葛伟 周豪 淼程 还有神一样的存在 老蒋

致我们在云飞思的青春

(从此以后他俩过上了幸福的生活...)

废话太多,就是加一个 UUID 表项呀

看完楼主的描述,在这里 UUID 只起了一个作用,就是创建失败的时候删除本地数据。如果只是这样的话,大可不必把 UUID 作为一个字段保存到服务器啊。API 异步调用的时候把手机生成的 UUID 传到服务器,服务器创建成功之后返回请求时的 UUID 和新的 task 的 ID,这样手机就知道 taks 创建成功了,之后就可以用 task 的 ID 来进行查询、修改和删除的操作了。如果服务器超时或者返回失败,那么服务器和手机都可以将新创建的 task 删除。

Backbone.js 有类似机制,区分 id 和 client id http://backbonejs.org/#Model-id

id model.id A special property of models, the id is an arbitrary string (integer id or UUID). If you set the id in the attributes hash, it will be copied onto the model as a direct property. Models can be retrieved by id from collections, and the id is used to generate model URLs by default.

cid model.cid A special property of models, the cid or client id is a unique identifier automatically assigned to all models when they're first created. Client ids are handy when the model has not yet been saved to the server, and does not yet have its eventual true id, but already needs to be visible in the UI.

#3 楼 @cuterxy 我没有把所有的情况说清楚, 你这个方案,我们初期的时候想过,发现不能解决我们的所有情况。 实际情况很复杂,我这里只提到了 Task Model, 实际上,还有很多 Model,而且 Model 间还有关联。

我举其中一个例子, 离线的时候,创建任务后,还会创建任务的评论。 这个时候,创建任务的评论的参数中,任务资源的 ID 应该是什么哪? 此刻没有得到 ID,所以只能使用 UUID, 如果服务器数据中没有 Task 的 UUID,那么就无法找到评论对应的任务,无法实现关联。

当然我们也考虑过用缓存机制 (比如 Redis) 做 UUID 和 ID 的映射, 基于目前情况分析,这比较麻烦,也没什么优势。

#5 楼 @ery 如果情况是这样,那么为什么不全局用 UUID 作为主键呢?Web 版本用 ID,移动版本用 UUID,感觉这个系统有点混乱啊。

以后坚定使用 全局 UUID,分库也好,分表也好,异构也好,离线也好,数据迁移也好,全部解决。

#6 楼 @cuterxy 这里面有历史原因, 我举一个夸张的例子 Web 版 API 已经持续开发了 3 年,有 200 多个接口。 新的移动版本接口,只要求有 30 个接口。 此时,重写 Web 版 API 的 200 个接口,没有意义。

使用复合主键的默默路过。。。

如果是我我会选择直接用 UUID 作为主键,在移动端,那么这个 uuid 由移动端自己生成,然后在有网络的时候 POST 到服务器去,管他离不离线,服务端只接受你 post 过来的东西,WEB 那边的话就服务端生成 UUID。用 uuid,分库,分表,迁移,各种方便。然后至于性能问题,我自己觉得没多大区别,给 uuid 加个 index,不过我在 mysql 里面的做法是,创建表的时候 id:false,然后自己加个 id:string 字段,设为主键,而不是复合 id+uuid

我觉得在移动端使用 remote_id 和 local_id 或许是更好的解决方法。然后写一个同步模块用来保证 remote_id 和数据库的同步。然后移动端的操作使用 local_id。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号