app後端設計-資料增量更新
使用資料的app本地儲存,能減少網路的流量,同時極大提高了使用者的體驗(想想,很多資料都能在app本地獲取,顯示的速度當然快)。使用了本地儲存後,需要考慮的是資料的增量更新方案。
什麼是資料的增量更新?假設,使用者A的首頁在資料表中是有40條資料,id1-40,app每次獲取10條資料。第一次執行,app從資料表獲取了id1-10條資料同時儲存在本地。假設使用者離開了這個頁面再回到首頁,這時app需要再次從資料庫中獲取資料,由於之前已經有10條資料(id1-10)儲存在app本地了,那麼現在需要從資料庫中獲取的10條資料就是從剩餘的30條中資料獲取(id11-40)後並儲存在app本地。這個就是增量更新的典型例子。
增量更新的原理是在資料庫中,每條資料都必須有update_time這個值,記錄資料最後更新的時間,當app從伺服器獲取了一次資料後(返回的資料必須按時間排序,update_time最近的在第一條),記錄下第一條資料的update_time,當再次獲取資料就只需要獲取上個時間點到訪問伺服器這刻為止所更新的資料即可。
因為分頁機制的存在,這個演算法實現起來是挺多需要注意的地方,下面我舉一個簡化的例子詳細說明:
一些假設:
1. app每次請求都帶4個引數
http://test/api/timeline?count=3&page=1&since=1100&max=1200
count: 每頁的顯示條數(預設為3)
page: 當前頁碼(預設為1)
since: 時間戳,若指定此引數,則返回時間戳大於等於since的結果(應該是上次獲取的最新資料的update_time)
max: 時間戳,若指定此引數,則返回時間戳少於等於max的結果(應該是傳送時的時間)
在sql的查詢時,使用條件 since<=update_time<= max
2. api 返回的資料包含
{
"size": 10, //實際返回的資料量(因為分頁獲取的緣故,所以經常少於total值)
"total": 284, //應該返回的總資料量
"page": 1,
"count": 3,
"max": 0, //max為獲取的最後一條資料的update_time
"since": 0
},
{ //返回的資料實體
data:.......
}
3. app儲存的本地資料中的update_time是指伺服器中的這條資料的更新時間,不是指app中這條資料的更新時間。
現在開始討論:
(1)當app安裝完畢後還沒啟動,伺服器的資料表中的資料為3條,app儲存的本地資料為空
伺服器的資料表的資料
id |
update_time |
1 |
1100 |
2 |
1101 |
3 |
1101 |
app儲存的本地資料
id |
update_time |
(2)當app第一次執行(時間為11:05),因為是第一次執行,since為0,max為現在的時間點1105,在伺服器的資料表中獲取所有資料。
傳送的請求為:http://test/api/timeline?count=3&page=1&since=0&max=1105
(3)從(2)中傳送請求後,api的返回資料,伺服器的資料表中的資料,app儲存的本地資料如下:
api返回的資料
{
"size": 3, //實際返回的資料量
"total": 3, //應該返回的總資料量
"page": 1,
"count": 3,
"max": 1101,
"since":0
},
{ //返回的資料實體
data:.......
}
伺服器的資料表的資料
id |
update_time |
1 |
1100 |
2 |
1101 |
3 |
1101 |
app儲存的本地資料
id |
update_time |
1 |
1100 |
2 |
1101 |
3 |
1101 |
這裡是策略的重點(1): api返回資料中的max必須為最後一條資料的update_time
(4)現在的時間是11:20,使用者點選了頁面中“獲取更多”的按鈕,app應該從伺服器的資料表中拉取資料,在傳送請求前,伺服器的資料表中的資料如下:
伺服器的資料表的資料
id |
update_time |
1 |
1100 |
2 |
1101 |
3 |
1101 |
4 |
1118 |
5 |
1118 |
6 |
1119 |
7 |
1119 |
可看到,比起上次拉取資料的時候,伺服器的資料表多了id為4,5,6,7的資料。
這時傳送api請求,策略的重點(2):當api的返回資料size=total時,since值比上次獲取大一點,因為這時資料已經獲取完整了,沒必要重複獲取資料上次已經獲取的資料(記得條件since<=update_time<= max 嗎?)所以since值設定為1101+1=1102,max為現在的時間點:1120,請求的url如下:
http://test/api/timeline?count=3&page=1&since=1102&max=1120
傳送請求後api的返回資料和app儲存的本地資料如下:
api返回的資料
{
"size": 3, //實際返回的資料量(因為分頁獲取的緣故,所以經常少於total值)
"total": 4, //應該返回的總資料量
"page": 1,
"count": 3,
"max": 1119,
"since":1102
},
{ //返回的資料實體
data:.......
}
app的資料:
id |
update_time |
1 |
1100 |
2 |
1101 |
3 |
1101 |
4 |
1118 |
5 |
1118 |
6 |
1119 |
這裡是策略的重點(3):在資料庫中,update_time為1101~1120的資料有4條,但由於分頁的緣故,只獲取了3條(從size和total引數可以判定),這意味著1101~1120這段時間的資料沒有獲取完整,app所獲取的最後一條資料的update_time是1119,伺服器的資料表中剩下的沒有被app獲取的資料有兩種情況:
a.update_time剛好是1119
b.update_time大於1119
由於我們沒法判斷屬於哪種種情況,如果我們下次拉資料的時候 since大於1119,伺服器的資料表中id為7的資料不會再獲取,那麼會造成app中丟失了id為7的資料,所以針對上次資料獲取不完整的情況,下次獲取資料時since必須是等於1119,雖然有可能會獲取重複的資料。
(5)現在的時間是11:30,使用者點選了頁面中“獲取更多”的按鈕,app應該從伺服器的資料表中拉取資料,在傳送請求前,伺服器的資料表中的資料如下:
伺服器的資料表的資料
id |
update_time |
1 |
1100 |
2 |
1101 |
3 |
1101 |
4 |
1118 |
5 |
1118 |
6 |
1119 |
7 |
1119 |
8 |
1120 |
這時傳送api請求,這裡是策略的重點(4):當api的返回資料size少於total,為了避免有資料丟失,since為上次收到api的返回資料的max值:1119,max為現在的時間點:1130。關於策略重點(4),請結合策略的重點(3)一起理解。
請求的url如下:
http://test/api/timeline?count=3&page=1&since=1119&max=1130
傳送請求後api的返回資料和app儲存的本地資料如下:
api返回的資料
{
"size": 3, //實際返回的資料量(因為分頁獲取的緣故,所以經常少於total值)
"total": 3, //應該返回的總資料量
"page": 1,
"count": 3,
"max": 1120,
"since":1119
},
{ //返回的資料實體
data:.......
}
這是策略的重點(5):api中返回資料中id為6的資料,在app的本地資料中已經存在,對於這條資料,app端應該放棄重複插入。
最後app儲存的本地資料如下:
app的資料:
id |
update_time |
1 |
1100 |
2 |
1101 |
3 |
1101 |
4 |
1118 |
5 |
1118 |
6 |
1119 |
7 |
1119 |
8 |
1120 |
ok,整個增量更新的策略已經分析完畢了。在這個策略中,page引數幾乎沒用,之所以要保留,是為了相容分頁不帶since,max的情況。對於這個增量更新的策略,請仔細理解策略的重點(1)(2)(3)(4)(5)的分析。
增量更新的策略,還要處理一個刪除資料的同步問題。假設,在伺服器的資料表要刪除一條資料,怎麼通知app本地也刪除這條資料。我們的解決方案是伺服器的伺服器的資料表中增加一個標識is_delete,當需要在業務邏輯上刪除的時候,把這條資料的is_delete設為1,同時更新update_time。當app增量更新檢測到這條is_delete為1的資料,就在app本地資料中把這條資料刪除。為了避免在伺服器儲存太多的資料,在伺服器設定一個crontab,定期把那些已經標識is_delete設為1已經一段時間的資料刪除。
這個增量更新的策略,適用於需要分頁顯示的app頁面。