鍍金池/ 教程/ Android/ 解決云同步的保存沖突
檢測(cè)常用的手勢(shì)
優(yōu)化layout的層級(jí)
用戶輸入
管理應(yīng)用的內(nèi)存
聯(lián)系人信息
開發(fā)輔助程序
Android多媒體
添加語音功能
顯示位置地址
提供向下與橫向?qū)Ш?/span>
支持游戲控制器
訪問可穿戴數(shù)據(jù)層
處理多點(diǎn)觸控手勢(shì)
全屏沉浸式應(yīng)用
為多線程創(chuàng)建管理器
數(shù)據(jù)保存
Intent的發(fā)送
更新Notification
優(yōu)化下載以高效地訪問網(wǎng)絡(luò)
打印
打包可穿戴應(yīng)用
接收從其他App傳送來的數(shù)據(jù)
發(fā)送與接收消息
建立靈活動(dòng)態(tài)的UI
處理鍵盤輸入
Building a Work Policy Controller
建立測(cè)試環(huán)境
創(chuàng)建表盤
分享文件
顯示Notification進(jìn)度
實(shí)現(xiàn)自適應(yīng)UI流(Flows)
使用設(shè)備管理策略增強(qiáng)安全性
使用能感知版本的組件
執(zhí)行網(wǎng)絡(luò)操作
建立文件分享
添加移動(dòng)
更新你的Security Provider來對(duì)抗SSL漏洞利用
支持鍵盤導(dǎo)航
創(chuàng)建和監(jiān)視地理圍欄
發(fā)送并同步數(shù)據(jù)
使用BigView樣式
無線連接設(shè)備
提供向上導(dǎo)航與歷史導(dǎo)航
最小化定期更新造成的影響
實(shí)現(xiàn)向下的導(dǎo)航
支持不同的屏幕大小
Android 可穿戴應(yīng)用
添加動(dòng)畫
顯示聯(lián)系人頭像
使用OpenGL ES顯示圖像
處理輸入法可見性
分享文件
保持設(shè)備喚醒
淡化系統(tǒng)Bar
使用NFC分享文件
保存到Preference
Android聯(lián)系人信息與位置信息
創(chuàng)建標(biāo)準(zhǔn)的網(wǎng)絡(luò)請(qǐng)求
使用Drawables
管理Bitmap的內(nèi)存使用
管理Activity的生命周期
按需加載視圖
傳輸資源
為可穿戴設(shè)備創(chuàng)建自定義UI
在一個(gè)線程中執(zhí)行一段特定的代碼
性能優(yōu)化
隱藏導(dǎo)航欄
創(chuàng)建目錄瀏覽器
為多種大小的屏幕進(jìn)行規(guī)劃
View間漸變
使用觸摸手勢(shì)
高效加載大圖
使用CursorLoader在后臺(tái)加載數(shù)據(jù)
創(chuàng)建抽屜式導(dǎo)航(navigation drawer)
管理音頻焦點(diǎn)
創(chuàng)建后臺(tái)服務(wù)
創(chuàng)建功能測(cè)試
創(chuàng)建使用Material Design的應(yīng)用
停止與重啟Activity
添加一個(gè)簡便的分享功能
啟動(dòng)Activity時(shí)保留導(dǎo)航
TV應(yīng)用清單
創(chuàng)建向后兼容的UI
?# 優(yōu)化自定義View
創(chuàng)建單元測(cè)試
在UI上顯示Bitmap
建立OpenGL ES的環(huán)境
構(gòu)建表盤服務(wù)
JNI Tips
建立搜索界面
實(shí)現(xiàn)自定義View的繪制
使用HTTPS與SSL
按需操控BroadcastReceiver
分享簡單的數(shù)據(jù)
繪制形狀
Android位置信息
創(chuàng)建并運(yùn)行可穿戴應(yīng)用
執(zhí)行 Sync Adpater
獲取最后可知位置
創(chuàng)建 Android 項(xiàng)目
實(shí)現(xiàn)高效的導(dǎo)航
退出全屏的Activity
創(chuàng)建Card
兼容音頻輸出設(shè)備
同步數(shù)據(jù)單元
傳輸數(shù)據(jù)時(shí)避免消耗大量電量
保存到文件
緩存Bitmap
提供配置 Activity
調(diào)度重復(fù)的鬧鐘
實(shí)現(xiàn)輔助功能
重復(fù)的下載是冗余的
隱藏狀態(tài)欄
實(shí)現(xiàn)自定義的網(wǎng)絡(luò)請(qǐng)求
規(guī)劃界面和他們之間的關(guān)系
使用Sync Adapter傳輸數(shù)據(jù)
TV應(yīng)用內(nèi)搜索
響應(yīng)觸摸事件
使用Google Cloud Messaging(已廢棄)
控制相機(jī)
Android網(wǎng)絡(luò)連接與云服務(wù)
請(qǐng)求分享一個(gè)文件
處理TV硬件
響應(yīng)UI可見性的變化
使用網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)
指定輸入法類型
優(yōu)化電池壽命
創(chuàng)建TV應(yīng)用
獲取聯(lián)系人列表
拖拽與縮放
啟動(dòng)與停止線程池中的線程
創(chuàng)建 Sync Adpater
使用 WiFi P2P 服務(wù)發(fā)現(xiàn)
開始使用Material Design
代理至新的APIs
使用include標(biāo)簽重用layouts
使得View可交互
高效顯示Bitmap
創(chuàng)建企業(yè)級(jí)應(yīng)用
Fragments之間的交互
創(chuàng)建與執(zhí)行測(cè)試用例
綜合:設(shè)計(jì)我們的樣例 App
繪制表盤
建立簡單的用戶界面
自定義動(dòng)畫
開發(fā)輔助服務(wù)
避免出現(xiàn)程序無響應(yīng)ANR(Keeping Your App Responsive)
使用ViewPager實(shí)現(xiàn)屏幕滑動(dòng)
設(shè)計(jì)高效的導(dǎo)航
Android分享操作(Building Apps with Content Sharing)
提供向后的導(dǎo)航
保持向下兼容
創(chuàng)建TV播放應(yīng)用
縮放View
使用 WiFi 建立 P2P 連接
Android后臺(tái)任務(wù)
連接到網(wǎng)絡(luò)
為 Notification 添加頁面
使TV應(yīng)用是可被搜索的
添加Action Bar
使用Material的主題
啟動(dòng)另一個(gè)Activity
顯示正在播放卡片
適配不同的系統(tǒng)版本
輕松錄制視頻
創(chuàng)建可穿戴的應(yīng)用
創(chuàng)建自定義的布局
重新創(chuàng)建Activity
使用CursorLoader執(zhí)行查詢?nèi)蝿?wù)
使用舊的APIs實(shí)現(xiàn)新API的效果
使用備份API
安全要點(diǎn)
Android入門基礎(chǔ):從這里開始
保存并搜索數(shù)據(jù)
根據(jù)網(wǎng)絡(luò)連接類型來調(diào)整下載模式
使用Tabs創(chuàng)建Swipe視圖
SMP(Symmetric Multi-Processor) Primer for Android
解析 XML 數(shù)據(jù)
使用 Volley 傳輸網(wǎng)絡(luò)數(shù)據(jù)
建立ActionBar
Android交互設(shè)計(jì)
使用Intent修改聯(lián)系人信息
增加搜索功能
輕松拍攝照片
定義形狀
測(cè)試你的Activity
在 Notifcation 中接收語音輸入
與其他應(yīng)用的交互
管理系統(tǒng)UI
追蹤手勢(shì)移動(dòng)
Android界面設(shè)計(jì)
執(zhí)行 Android 程序
顯示確認(rèn)界面
創(chuàng)建Lists與Cards
打印HTML文檔
創(chuàng)建TV應(yīng)用
為多屏幕設(shè)計(jì)
定義Shadows與Clipping視圖
使用Fragment建立動(dòng)態(tài)UI
接收Activity返回的結(jié)果
布局變更動(dòng)畫
定位常見的問題
自定義ActionBar的風(fēng)格
定義Layouts
發(fā)送簡單的網(wǎng)絡(luò)請(qǐng)求
啟動(dòng)與銷毀Activity
與UI線程通信
非UI線程處理Bitmap
創(chuàng)建TV布局
提升Layout的性能
報(bào)告任務(wù)執(zhí)行狀態(tài)
判斷并監(jiān)測(cè)網(wǎng)絡(luò)連接狀態(tài)
兼容不同的設(shè)備
處理按鍵動(dòng)作
優(yōu)化性能和電池使用時(shí)間
給其他App發(fā)送簡單的數(shù)據(jù)
Implementing App Restrictions
向后臺(tái)服務(wù)發(fā)送任務(wù)請(qǐng)求
展示Card翻轉(zhuǎn)動(dòng)畫
管理ViewGroup中的觸摸事件
兼容不同的屏幕密度
通過藍(lán)牙進(jìn)行調(diào)試
為可穿戴設(shè)備創(chuàng)建Notification
控制音量與音頻播放
獲取聯(lián)系人詳情
在表盤上顯示信息
提供向上的導(dǎo)航
滾動(dòng)手勢(shì)動(dòng)畫
幫助用戶在TV上找到內(nèi)容
創(chuàng)建TV導(dǎo)航
為索引指定App內(nèi)容
ActionBar的覆蓋疊加
Android Wear 上的位置檢測(cè)
保護(hù)安全與隱私的最佳策略
Ensuring Compatibility with Managed Profiles
解決云同步的保存沖突
獲取位置更新
創(chuàng)建List
測(cè)試程序
管理網(wǎng)絡(luò)的使用情況
為App內(nèi)容開啟深度鏈接
推薦TV內(nèi)容
建立一個(gè)Notification
管理音頻播放
設(shè)計(jì)表盤
拍照
處理控制器輸入動(dòng)作
判斷并監(jiān)測(cè)設(shè)備的底座狀態(tài)與類型
處理查詢的結(jié)果
保存到數(shù)據(jù)庫
支持多個(gè)游戲控制器
創(chuàng)建 Stub Content Provider
使得ListView滑動(dòng)順暢
處理數(shù)據(jù)層的事件
創(chuàng)建TV應(yīng)用的第一步
使得你的App內(nèi)容可被Google搜索
將 Notification 放成一疊
創(chuàng)建 Stub 授權(quán)器
暫停與恢復(fù)Activity
管理設(shè)備的喚醒狀態(tài)
Android圖像與動(dòng)畫
打印照片
云同步
創(chuàng)建TV直播應(yīng)用
為Notification賦加可穿戴特性
提供一個(gè)Card視圖
建立請(qǐng)求隊(duì)列(RequestQueue)
適配不同的語言
創(chuàng)建詳情頁
測(cè)試UI組件
接收其他設(shè)備的文件
創(chuàng)建自定義View
建立第一個(gè)App
創(chuàng)建2D Picker
監(jiān)測(cè)電池的電量與充電狀態(tài)
打印自定義文檔
抽象出新的APIs
通知提示用戶
獲取文件信息
運(yùn)用投影與相機(jī)視角
在IntentService中執(zhí)行后臺(tái)任務(wù)
多線程操作
創(chuàng)建一個(gè)Fragment
添加Action按鈕
在不同的 Android 系統(tǒng)版本支持控制器
維護(hù)兼容性
發(fā)送文件給其他設(shè)備
創(chuàng)建TV游戲應(yīng)用
創(chuàng)建自定義的View類
代碼性能優(yōu)化建議
Intent過濾
適配不同的屏幕

解決云同步的保存沖突

編寫:jdneo - 原文:http://developer.android.com/training/cloudsave/conflict-res.html

這篇文章將介紹當(dāng)應(yīng)用使用Cloud Save service存儲(chǔ)數(shù)據(jù)到云端時(shí),如何設(shè)計(jì)一個(gè)魯棒性較高的沖突解決策略。云存儲(chǔ)服務(wù)允許我們?yōu)槊恳粋€(gè)在Google服務(wù)上的應(yīng)用用戶,存儲(chǔ)他們的應(yīng)用數(shù)據(jù)。應(yīng)用可以通過使用云存儲(chǔ)API,從Android設(shè)備,iOS設(shè)備或者Web應(yīng)用恢復(fù)或更新這些數(shù)據(jù)。

云存儲(chǔ)中的保存和加載過程非常直接:它只是一個(gè)數(shù)據(jù)和byte數(shù)組之間序列化轉(zhuǎn)換,并將這些數(shù)組存儲(chǔ)在云端的過程。然而,當(dāng)用戶有多個(gè)設(shè)備,并且有兩個(gè)以上的設(shè)備嘗試將它們的數(shù)據(jù)存儲(chǔ)在云端時(shí),這一保存的行為可能會(huì)引起沖突,因此我們必須決定應(yīng)該如何處理這類問題。云端數(shù)據(jù)的結(jié)構(gòu)在很大程度上決定了沖突解決方案的魯棒性,所以務(wù)必小心地設(shè)計(jì)我們的數(shù)據(jù)存儲(chǔ)結(jié)構(gòu),使得沖突解決方案的邏輯可以正確地處理每一種情況。

本篇文章從一些有缺陷的解決方案入手,并解釋他們?yōu)楹尉哂腥毕?。之后?huì)呈現(xiàn)一個(gè)可以避免沖突的解決方案。用于討論的例子關(guān)注于游戲,但解決問題的核心思想是可以適用于任何將數(shù)據(jù)存儲(chǔ)于云端的應(yīng)用的。

沖突時(shí)獲得通知

OnStateLoadedListener方法負(fù)責(zé)從Google服務(wù)器下載應(yīng)用的狀態(tài)數(shù)據(jù)?;卣{(diào)函數(shù)OnStateLoadedListener.onStateConflict用來給應(yīng)用在本地狀態(tài)和云端存儲(chǔ)的狀態(tài)發(fā)生沖突時(shí),提供了一個(gè)解決機(jī)制:

@Override
public void onStateConflict(int stateKey, String resolvedVersion,
    byte[] localData, byte[] serverData) {
    // resolve conflict, then call mAppStateClient.resolveConflict()
 ...
}

此時(shí)應(yīng)用必須決定要保留哪一個(gè)數(shù)據(jù),或者它自己提交一個(gè)新的數(shù)據(jù)來表示合并后的數(shù)據(jù)狀態(tài),解決沖突的邏輯由我們自己來實(shí)現(xiàn)。

我們必須要意識(shí)到云存儲(chǔ)服務(wù)是在后臺(tái)執(zhí)行同步的。所以我們應(yīng)該確保應(yīng)用能夠在創(chuàng)建這一數(shù)據(jù)的Context之外接收回調(diào)。特別地,如果Google Play服務(wù)應(yīng)用在后臺(tái)檢測(cè)到了一個(gè)沖突,該回調(diào)函數(shù)會(huì)在下一次加載數(shù)據(jù)時(shí)被調(diào)用,通常來說會(huì)是在下一次用戶啟動(dòng)該應(yīng)用時(shí)。

因此,我們的云存儲(chǔ)代碼和沖突解決代碼的設(shè)計(jì)必須是和當(dāng)前Context無關(guān)的:也就是說當(dāng)我們拿到了兩個(gè)彼此沖突的數(shù)據(jù),我們必須僅通過數(shù)據(jù)集內(nèi)獲取的數(shù)據(jù)去解決沖突,而不依賴于任何其它任何外部Context。

處理簡單情況

下面列舉一些解決沖突的簡單例子。對(duì)于很多應(yīng)用而言,用這些策略或者其變體就足夠解決大多數(shù)問題了:

新的比舊的更有效:在一些情況下,新的數(shù)據(jù)可以替代舊的數(shù)據(jù)。例如,如果數(shù)據(jù)代表了用戶所選擇角色的衣服顏色,那么最近的新的選擇就應(yīng)該覆蓋老的選擇。在這種情況下,我們可能會(huì)選擇在云存儲(chǔ)數(shù)據(jù)中存儲(chǔ)時(shí)間戳。當(dāng)處理這些沖突時(shí),選擇時(shí)間戳最新的數(shù)據(jù)(記住要選擇一個(gè)可靠的時(shí)鐘,并注意對(duì)不同時(shí)區(qū)的處理)。

一個(gè)數(shù)據(jù)好于其他數(shù)據(jù):在一些情況下,我們是可以有方法在若干數(shù)據(jù)集中選取一個(gè)最好的。例如,如果數(shù)據(jù)代表了玩家在賽車比賽中的最佳時(shí)間,那么顯然,在沖突發(fā)生時(shí),我們應(yīng)該保留成績最好的那個(gè)數(shù)據(jù)。

進(jìn)行合并:有可能通過計(jì)算兩個(gè)數(shù)據(jù)集的合并版本來解決沖突。例如,我們的數(shù)據(jù)代表了用戶解鎖關(guān)卡的進(jìn)度,那么我們需要的數(shù)據(jù)就是兩個(gè)沖突數(shù)據(jù)的并集。通過這個(gè)方法,用戶的關(guān)卡解鎖進(jìn)度就不會(huì)丟失了。這里的例子使用了這一策略的一個(gè)變形。

為更復(fù)雜的情況設(shè)計(jì)一個(gè)策略

當(dāng)我們的游戲允許玩家收集可交換物品時(shí)(比如金幣或者經(jīng)驗(yàn)點(diǎn)數(shù)),情況會(huì)變得更加復(fù)雜一些。我們來假想一個(gè)游戲,叫做“金幣跑酷”,游戲中的角色通過跑步不斷地收集金幣使自己變的富有。每個(gè)收集到的金幣都會(huì)加入到玩家的儲(chǔ)蓄罐中。

下面的章節(jié)將展示三種在多個(gè)設(shè)備間解決沖突的方案:有兩個(gè)看上去還不錯(cuò),可惜最終還是不能適用于所有情況,最后一個(gè)解決方案可以解決多個(gè)設(shè)備間的數(shù)據(jù)沖突。

第一個(gè)嘗試:只保存總數(shù)

首先,這個(gè)問題看上去像是說:云存儲(chǔ)的數(shù)據(jù)只要存儲(chǔ)金幣的數(shù)量就行了。但是如果就只有這些數(shù)據(jù)是可用的,那么解決沖突的方案將會(huì)嚴(yán)重受到限制。此時(shí)最佳的方案只能是在沖突發(fā)生時(shí)存儲(chǔ)數(shù)值最大的數(shù)據(jù)。

想一下表1中所展現(xiàn)的場景。假設(shè)玩家一開始有20枚硬幣,然后在設(shè)備A上收集了10個(gè),在設(shè)備B上收集了15個(gè)。然后設(shè)備B將數(shù)據(jù)存儲(chǔ)到了云端。當(dāng)設(shè)備A嘗試去存儲(chǔ)的時(shí)候,沖突發(fā)生了?!爸槐4婵倲?shù)”的沖突解決方案會(huì)存儲(chǔ)35作為這一數(shù)據(jù)的值(兩數(shù)之間最大的)。

表1. 值保存最大的數(shù)(不佳的策略)

事件 設(shè)備A的數(shù)據(jù) 設(shè)備B的數(shù)據(jù) 云端的數(shù)據(jù) 實(shí)際的總數(shù)
開始階段 20 20 20 20
玩家在A設(shè)備上收集了10個(gè)硬幣 30 20 20 30
玩家在B設(shè)備上收集了15個(gè)硬幣 30 35 20 45
設(shè)備B將數(shù)據(jù)存儲(chǔ)至云端 30 35 35 45
設(shè)備A嘗試將數(shù)據(jù)存儲(chǔ)至云端,發(fā)生沖突 30 35 35 45
設(shè)備A通過選擇兩數(shù)中最大的數(shù)來解決沖突 35 35 35 45

這一策略顯然會(huì)失敗:玩家的金幣數(shù)從20變成35,但實(shí)際上玩家總共收集了25個(gè)硬幣(A設(shè)備10個(gè),B設(shè)備15個(gè)),所以有10個(gè)硬幣丟失了。只在云端存儲(chǔ)硬幣的總數(shù)是不足以實(shí)現(xiàn)一個(gè)健壯的沖突解決算法的。

第二個(gè)嘗試:存儲(chǔ)總數(shù)和變化值

另一個(gè)方法是在存儲(chǔ)數(shù)據(jù)中包括一些額外的數(shù)據(jù),如:自上次提交后硬幣增加的數(shù)量(delta)。在這一方法中,存儲(chǔ)的數(shù)據(jù)可以用一個(gè)二元組來表示(T, d),其中T是硬幣的總數(shù),而d是硬幣增加的數(shù)量。

通過這樣的數(shù)據(jù)存儲(chǔ)結(jié)構(gòu),我們的沖突檢測(cè)算法在魯棒性上會(huì)有更大的提升空間。但是這個(gè)方法在某些情況下依然會(huì)存在問題。

下面是包含delta數(shù)值的沖突解決算法過程:

  • 本地?cái)?shù)據(jù):(T, d)
  • 云端數(shù)據(jù):(T', d')
  • 解決后的數(shù)據(jù):(T'+d, d)

例如,當(dāng)我們?cè)诒镜貭顟B(tài)(T, d)和云端狀態(tài)(T', d)之間發(fā)生了沖突時(shí),可以將它們合并成(T'+d, d)。這意味著我們從本地拿出delta數(shù)據(jù),并將它和云端的數(shù)據(jù)結(jié)合起來,乍一看,這種方法可以很好的計(jì)量多個(gè)設(shè)備所收集的金幣。

該方法看上去很可靠,但它在具有移動(dòng)網(wǎng)絡(luò)的環(huán)境中難以適用:

  • 用戶可能在設(shè)備不在線時(shí)存儲(chǔ)數(shù)據(jù)。這些改變會(huì)以隊(duì)列形式等待手機(jī)聯(lián)網(wǎng)后提交。
  • 這個(gè)方法的同步機(jī)制是用最新的變化覆蓋掉任何之前的變化。換句話說,第二次寫入的變化會(huì)提交到云端(當(dāng)設(shè)備聯(lián)網(wǎng)了以后),而第一次寫入的變化就被忽略了。

為了進(jìn)一步說明,我們考慮一下表2所列的場景。在表2列出的一系列操作發(fā)生后,云端的狀態(tài)將是(130, +5),最終沖突解決后的狀態(tài)是(140, +10)。這是不正確的,因?yàn)閺目傮w上而言,用戶一共在A上收集了110枚硬幣而在B上收集了120枚硬幣??倲?shù)應(yīng)該為250。

表2. “總數(shù)+增量”策略的失敗案例

事件 設(shè)備A的數(shù)據(jù) 設(shè)備B的數(shù)據(jù) 云端的數(shù)據(jù) 實(shí)際的總數(shù)
開始階段 (20, x) (20, x) (20, x) 20
玩家在A設(shè)備上收集了100個(gè)硬幣 (120, +100) (20, x) (20, x) 120
玩家在A設(shè)備上又收集了10個(gè)硬幣 (130, +10) (20, x) (20, x) 130
玩家在B設(shè)備上收集了115個(gè)硬幣 (130, +10) (125, +115) (20, x) 245
玩家在B設(shè)備上又收集了5個(gè)硬幣 (130, +10) (130, +5) (20, x) 250
設(shè)備B將數(shù)據(jù)存儲(chǔ)至云端 (130, +10) (130, +5) (130, +5) 250
設(shè)備A嘗試將數(shù)據(jù)存儲(chǔ)至云端,發(fā)生沖突 (130, +10) (130, +5) (130, +5) 250
設(shè)備A通過將本地的增量和云端的總數(shù)相加來解決沖突 (140, +10) (130, +5) (140, +10) 250

注:x代表與該場景無關(guān)的數(shù)據(jù)

我們可能會(huì)嘗試在每次保存后不重置增量數(shù)據(jù)來解決此問題,這樣的話在每個(gè)設(shè)備上第二次存儲(chǔ)的數(shù)據(jù)就能夠代表用戶至今為止收集到的所有硬幣。此時(shí),設(shè)備A在第二次本地存儲(chǔ)完成后,數(shù)據(jù)將是(130, +110)而不是(130, +10)。然而,這樣做的話就會(huì)發(fā)生如表3所述的情況:

表3. 算法改進(jìn)后的失敗案例

事件 設(shè)備A的數(shù)據(jù) 設(shè)備B的數(shù)據(jù) 云端的數(shù)據(jù) 實(shí)際的總數(shù)
開始階段 (20, x) (20, x) (20, x) 20
玩家在A設(shè)備上收集了100個(gè)硬幣 (120, +100) (20, x) (20, x) 120
設(shè)備A將狀態(tài)存儲(chǔ)到云端 (120, +100) (20, x) (120, +100) 120
玩家在A設(shè)備上又收集了10個(gè)硬幣 (130, +110) (20, x) (120, +100) 130
玩家在B設(shè)備上收集了1個(gè)硬幣 (130, +110) (21, +1) (120, +100) 131
設(shè)備B嘗試向云端存儲(chǔ)數(shù)據(jù),發(fā)生沖突 (130, +110) (21, +1) (120, +100) 131
設(shè)備B通過將本地的增量和云端的總數(shù)相加來解決沖突 (130, +110) (121, +1) (121, +1) 131
設(shè)備A嘗試將數(shù)據(jù)存儲(chǔ)至云端,發(fā)生沖突 (130, +110) (121, +1) (121, +1) 131
設(shè)備A通過將本地的增量和云端的總數(shù)相加來解決沖突 (231, +110) (121, +1) (231, +110) 131

注:x代表與該場景無關(guān)的數(shù)據(jù)

現(xiàn)在我們碰到了另一個(gè)問題:我們給予了玩家過多的硬幣。這個(gè)玩家拿到了211枚硬幣,但實(shí)際上他只收集了111枚。

解決辦法:

分析之前的幾次嘗試,我們發(fā)現(xiàn)這些策略存在這樣的缺陷:無法知曉哪些硬幣已經(jīng)計(jì)數(shù)了,哪些硬幣沒有被計(jì)數(shù),尤其是當(dāng)多個(gè)設(shè)備連續(xù)提交的時(shí)候,算法會(huì)出現(xiàn)混亂。

該問題的解決辦法是將我們?cè)谠贫说臄?shù)據(jù)存儲(chǔ)結(jié)構(gòu)改為字典類型,使用字符串+整形的鍵值對(duì)。每一個(gè)鍵值對(duì)都代表了一個(gè)包含硬幣的“委托人”,而總數(shù)就應(yīng)該是將所有記錄的值加起來。這一設(shè)計(jì)的宗旨是每個(gè)設(shè)備有它自己的“委托人”,并且只有設(shè)備自己可以把硬幣放到它的“委托人”當(dāng)中。

字典的結(jié)構(gòu)是:(A:a, B:b, C:c, ...),其中a代表了“委托人”A所擁有的硬幣,b是“委托人”B所擁有的硬幣,以此類推。

這樣的話,新的沖突解決策略算法將如下所示:

  • 本地?cái)?shù)據(jù):(A:a, B:b, C:c, ...)
  • 云端數(shù)據(jù):(A:a', B:b', C:c', ...)
  • 解決后的數(shù)據(jù):(A:max(a,a'), B:max(b,b'), C:max(c,c'), ...)

例如,如果本地?cái)?shù)據(jù)是(A:20, B:4, C:7)并且云端數(shù)據(jù)是(B:10, C:2, D:14),那么解決沖突后的數(shù)據(jù)將會(huì)是(A:20, B:10, C:7, D:14)。當(dāng)然,應(yīng)用的沖突解決邏輯可以根據(jù)具體的需求而有所差異。比如對(duì)于有一些應(yīng)用,我們可能希望挑選最小的值。

為了測(cè)試新的算法,將它應(yīng)用于任何一個(gè)之前提到過的場景。你將會(huì)發(fā)現(xiàn)它都能取得正確地結(jié)果。

表4闡述了這一點(diǎn),它使用了表3中所提到的場景。注意下面所列出的關(guān)鍵點(diǎn):

在初始狀態(tài),玩家有20枚硬幣。該數(shù)據(jù)準(zhǔn)確體現(xiàn)在了所有設(shè)備和云端中,我們用字典:(X:20)來代表它,其中X我們不用太多關(guān)心,初始化的數(shù)據(jù)是哪兒來對(duì)該問題沒有影響。

當(dāng)玩家在設(shè)備A上收集了100枚硬幣,這一變化會(huì)作為一個(gè)字典保存到云端。字典的值是100是因?yàn)檫@就是玩家在設(shè)備A上收集的硬幣數(shù)量。在這一過程中,沒有要執(zhí)行數(shù)據(jù)的計(jì)算(設(shè)備A僅僅是將玩家所收集的數(shù)據(jù)匯報(bào)給了云端)。

每一個(gè)新的硬幣提交會(huì)打包成一個(gè)與設(shè)備關(guān)聯(lián)的字典并保存到云端。例如,假設(shè)玩家又在設(shè)備A上收集了100枚硬幣,那么對(duì)應(yīng)字典的值被更新為110。

最終的結(jié)果就是,應(yīng)用知道了玩家在每個(gè)設(shè)備上收集硬幣的總數(shù)。這樣它就能輕易地計(jì)算出實(shí)際的總數(shù)了。

表4. 鍵值對(duì)策略的成功應(yīng)用案例

事件 設(shè)備A的數(shù)據(jù) 設(shè)備B的數(shù)據(jù) 云端的數(shù)據(jù) 實(shí)際的總數(shù)
開始階段 (X:20, x) (X:20, x) (X:20, x) 20
玩家在A設(shè)備上收集了100個(gè)硬幣 (X:20, A:100) (X:20) (X:20) 120
設(shè)備A將狀態(tài)存儲(chǔ)到云端 (X:20, A:100) (X:20) (X:20, A:100) 120
玩家在A設(shè)備上又收集了10個(gè)硬幣 (X:20, A:110) (X:20) (X:20, A:100) 130
玩家在B設(shè)備上收集了1個(gè)硬幣 (X:20, A:110) (X:20, B:1) (X:20, A:100) 131
設(shè)備B嘗試向云端存儲(chǔ)數(shù)據(jù),發(fā)生沖突 (X:20, A:110) (X:20, B:1) (X:20, A:100) 131
設(shè)備B解決沖突 (X:20, A:110) (X:20, A:100, B:1) (X:20, A:100, B:1) 131
設(shè)備A嘗試將數(shù)據(jù)存儲(chǔ)至云端,發(fā)生沖突 (X:20, A:110) (X:20, A:100, B:1) (X:20, A:100, B:1) 131
設(shè)備A解決沖突 (X:20, A:110, B:1) (X:20, A:100, B:1) (X:20, A:110, B:1),total 131 131

清除你的數(shù)據(jù)

在云端允許存儲(chǔ)數(shù)據(jù)的大小是有限制的,所以在后續(xù)的論述中,我們將會(huì)關(guān)注如何避免創(chuàng)建過大的詞典。一開始,看上去每個(gè)設(shè)備只會(huì)有一條詞典記錄,即使是非常激進(jìn)的用戶也不太會(huì)擁有上千種不同的設(shè)備(對(duì)應(yīng)上千條字典記錄)。然而, 獲取設(shè)備ID的方法很難,并且我們認(rèn)為這是一種不好的實(shí)踐方式,所以我們應(yīng)該使用一個(gè)安裝ID,這更容易獲取也更可靠。這樣的話就意味著,每一次用戶在每臺(tái)設(shè)備安裝一次就會(huì)產(chǎn)生一個(gè)ID。假設(shè)每個(gè)鍵值對(duì)占據(jù)32字節(jié),由于一個(gè)個(gè)人云存儲(chǔ)緩存最多可以有128K的大小,因此最多可以存儲(chǔ)4096條記錄。

在現(xiàn)實(shí)場景中,你的數(shù)據(jù)可能更加復(fù)雜。在這種情況下,存儲(chǔ)數(shù)據(jù)的記錄條數(shù)也會(huì)進(jìn)一步受到限制。具體而言則需要取決于實(shí)現(xiàn),比如可能需要添加時(shí)間戳來指明每條記錄是何時(shí)修改的。當(dāng)你檢測(cè)到有一條記錄在過去幾個(gè)禮拜或者幾個(gè)月的時(shí)間內(nèi)都沒有被修改,那么就可以安全地將金幣數(shù)據(jù)轉(zhuǎn)移到另一條記錄中并刪除老的記錄。