本文為轉載文章,查看原文請點擊以下鏈接。
-
移動端與服務器端數據庫同步
以下是簡單理解:
本文的策略可實現簡單的數據同步。
主要的關鍵點是:
- 時間戳
- 更新狀態(本文未提及)
更新狀態包括添加,修改,刪除等等,明確告訴另一端具體是做哪一種更新。 - 記錄數據更改記錄的表(本文未提及)
按我理解,時間戳與更新狀態這兩個字段可以放在需要同步的目標表里,也可新建一個更改記錄
表,主要字段包括時間戳,更新狀態,待同步數據的主鍵(如guid).
不知這種理解是否正確。
以下是正文:
數據庫同步滿足以下幾個需求:
同步時雙向傳輸數據最小化。雙向即,服務器端更新同步到移動端,和移動端更新同步到服務器。每次只傳輸兩端差異數據。
支持離線。支持離線本身是一種好的用戶體驗,而它帶來的一個其他的好處是每次移動端數據庫查詢僅需查詢本地數據庫,這樣就避免了過多的服務器端查詢。本地數據庫減少了很多服務器的壓力,當然也給用戶省了流量。數據庫更新操作也是如此,僅更新本地數據庫,然后在適當的時機與服務器端進行同步。更進一步的說,移動端查詢和更新數據只跟本地數據庫打交道。
沖突解決。如果一個用戶帳號在多個移動端進行離線使用,勢必會產生數據沖突。
設計的關鍵在于數據模型的設計,和同步算法。以下是我的想法。
下面是對象類代碼,對應數據庫的表字段。
服務器端設計:
public abstract class ServerBaseModel {
public long userId; /* Global unique user id */
public long id; /* Model id. Unique for user */
public long lastmodified; /* Last modified server time stamp */
public boolean deleted; /* delete flag */
}
移動端設計:
public abstract class ClientBaseModel {
public long userId; /* Global unique user id */
public long id; /* Model id. Unique for user */
public long lastmodified; /* Last modified server time stamp */
public boolean deleted; /* delete flag */
public boolean dirty; /* Local dirty flag */
}
首先是如何選擇表的主鍵id
使用auto increment主鍵?不行!根據前面支持離線的需求,id應該在移動端就已經生成。如果使用auto increment在同一個用戶帳號的情況下只可以做到單個移動端的唯一性,無法保證多個移動端的唯一性,更加不能保證服務器端全局的唯一性。
使用UUID作為主鍵?可行!每一條數據在移動端創建時即為之生成UUID。這樣基本可以保證服務器端全局的唯一性。對于使用UUID作為主鍵好不好的討論很多,大家可以另行參考。
我的方案。使用userId和一個用戶唯一的model id作為聯合主鍵。model id需要保證在同一userId下唯一,這樣再加上userId使得數據全局唯一。問題是如何選擇model id?一個比較可行但是不能保證完全沒有重復的是時間戳。
還有其他更好的主鍵方案嗎?
接下來是如何判斷服務器端數據已經更新
每一條數據存儲一個last modified時間戳。這個時間戳是服務器端的時間。同一條數據如果移動端的lastmodified小于服務器端的lastmodified就可以判斷數據已經更新。
移動端數據更新
移動端數據庫增加一個dirty標志,dirty標志表示本地新增或者修改的數據,這些數據會在下一次同步時上傳至服務器。
如何處理數據刪除
根據前面last modified和dirty字段的設計,整個數據模型是一個增量式的。數據只允許新增和更新,所以這里增加一個deleted標志表示數據是否已經被刪除。
以上介紹完我的移動端和服務器端數據庫同步的數據模型設計,接下來講講同步算法。
同步算法:
服務器端向移動端同步
- 獲得移動端最大的last modified,發送至服務器端。
- 服務器端查詢所有last modified值比移動端最大last modified的數據,返回至移動端。
- 移動端更新本地數據庫
移動端向服務器端同步
- 獲得所有的dirty數據,發送至服務器端。
- 服務器端處理dirty數據,返回更新的數據的新的last modified。
- 移動端更新last modified,并且清楚dirty標志。