仔細(xì)考慮同步,否則同步將是痛苦。
不是蘇斯博士說的 (蘇斯博士是美國著名兒童文學(xué)和圖書作家)
同步是軟件開發(fā)中的一項(xiàng)基本要素。它包括很多種形式,從強(qiáng)制使用不同設(shè)備上的時(shí)鐘來協(xié)商它們之間的延遲,到使用 @synchronized 代碼塊來序列化訪問多線程編程中的資源。
本文,我將要介紹多種 數(shù)據(jù)同步 的方法,接下來本文將使用 同步 (sync) 代替數(shù)據(jù)同步 (data synchronization)。簡(jiǎn)單來說,問題就在于:如何存儲(chǔ)時(shí)間和空間上分離的兩部分?jǐn)?shù)據(jù),讓這兩部分的數(shù)據(jù)盡可能的相同。
我個(gè)人的興趣要追溯到早期的 iOS App Store,那時(shí),同步在我的生活中扮演了重要的角色。當(dāng)時(shí),我是學(xué)習(xí)卡片應(yīng)用程序 Mental Case 的開發(fā)者。由于包括 Mac 版、 iPad 版和 iPhone 版, Mental Case 更像是一套而不是單個(gè)應(yīng)用,并且它的一大特色就是能夠在不同的設(shè)備之間同步你的學(xué)習(xí)資料。最初,在以數(shù)據(jù)為中心的年代。 Mental Case 將 Mac 作為中心,通過本地 Wi-Fi 網(wǎng)絡(luò),和一個(gè)或者多個(gè) iOS 設(shè)備同步數(shù)據(jù)。現(xiàn)在, Mental Case 系列應(yīng)用通過 iCloud 進(jìn)行點(diǎn)到點(diǎn) (peer-to-peer) 的數(shù)據(jù)同步。
合理地實(shí)現(xiàn)數(shù)據(jù)同步是有挑戰(zhàn)的,但是更大的問題是專業(yè)化而不是開發(fā)一個(gè)通用的 Web 服務(wù),然后這就要考慮更專業(yè)的解決方案。比如,當(dāng)一類 Web 服務(wù)總是需要服務(wù)端的開發(fā)的時(shí)候,使用一種同步框架能夠在你現(xiàn)有的代碼基礎(chǔ)上做最少的改變,并且完全不需要服務(wù)端的代碼。
在下文中,我將介紹在移動(dòng)設(shè)備早期出現(xiàn)的多種數(shù)據(jù)同步的方法,在高級(jí)層面上解釋它們的工作原理,并給出一些他們的最佳使用指導(dǎo)。我還將根據(jù)當(dāng)今的形勢(shì),描繪一些數(shù)據(jù)同步領(lǐng)域的新趨勢(shì)。
在開始介紹多種數(shù)據(jù)同步方法的細(xì)節(jié)之前,有必要了解它的演變過程以及如何適應(yīng)早期的技術(shù)帶來的限制的。
就消費(fèi)設(shè)備而言,數(shù)據(jù)同步始于有線連接。上世紀(jì) 90 年代末和 21 世紀(jì)初,像 Palm Pilot 和 iPod 這樣的外圍設(shè)備能夠通過火線 (Firewire) 或者 USB 和 Mac 或者 PC 進(jìn)行同步。蘋果的數(shù)字中心策略正是基于這種方法。后來,由于網(wǎng)速的提升,Wi-Fi 和藍(lán)牙在一定程度上增補(bǔ)了有線連接,但是 iTunes 現(xiàn)在仍然使用這種方式。
由于 21 世紀(jì)云服務(wù)的飛速發(fā)展,由 Mac 或者 PC 作為中心的方式已經(jīng)逐步轉(zhuǎn)向了云。云的優(yōu)勢(shì)在于無論什么時(shí)候,只要設(shè)備有網(wǎng)絡(luò),它就可以使用。有了基于云的數(shù)據(jù)同步,你再也不用呆在家里的電腦旁邊進(jìn)行同步數(shù)據(jù)了。
上面提到的每一種方式都在設(shè)備間利用了我稱之為 同步通訊 (Synchronous Communication, SC)的概念。你的 iPhone 上的一個(gè)應(yīng)用直接和一臺(tái) Mac 或者云服務(wù)通訊,然后實(shí)時(shí)地接收返回的數(shù)據(jù)。
現(xiàn)在,出現(xiàn)了一種新興的基于 異步通訊 (Asynchronous Communication, AC) 的數(shù)據(jù)同步方式。應(yīng)用不再直接和云“通話”,而是和一個(gè)框架或者本地的文件系統(tǒng)交換數(shù)據(jù)。應(yīng)用程序不再期望立刻得到回應(yīng),取而代之的是,數(shù)據(jù)是在后臺(tái)和云端進(jìn)行交互了。
這種方式將應(yīng)用程序代碼和數(shù)據(jù)同步過程解耦,將開發(fā)者從精確操縱數(shù)據(jù)同步中解放出來。遵循這一新趨勢(shì)的產(chǎn)品范例有蘋果的 Core Data-iCloud 框架,Dropbox Datastore API,甚至像 TouchDB (基于 CouchDB project)那樣的文件存儲(chǔ)。
數(shù)據(jù)同步的這段歷史并不是遵循一個(gè)單一的線性路徑。每個(gè)階段都是先遵循,后使用,再創(chuàng)新的更迭演化。今天,所有的這些技術(shù)仍然存在并且仍在使用,并且他們中的每一個(gè)都有可能是你的某個(gè)特定問題的合適的解決方案。
我們已經(jīng)知道數(shù)據(jù)同步的方式可以根據(jù)它們是否使用了同步通訊來分類,但是也能夠根據(jù)與客戶端交互是否使用了“智能”服務(wù)器,或者同步過程是否采用以客戶端處理復(fù)雜事情的對(duì)等方式。下面這個(gè)簡(jiǎn)單的表格列出了所有的同步技術(shù):
| 同步 | 異步 | |
| 客戶端-服務(wù)端 |
Parse StackMob Windows Azure Mobile Services Helios Custom Web Service |
Dropbox Datastore TouchDB Wasabi Sync Zumero |
| 對(duì)等方式 |
iTunes/iPod Palm Pilot |
Core Data with iCloud TICoreDataSync Core Data Ensembles |
同步對(duì)等網(wǎng)絡(luò) (Synchronous Peer-to-Peer, S-P2P) 是實(shí)際上第一個(gè)被廣泛接收的方式,并被用于像 iPod 和 PDA 這樣的外圍設(shè)備。S-P2P 實(shí)現(xiàn)簡(jiǎn)單并且本地網(wǎng)絡(luò)速度快。由于 iTunes 需要傳輸大量的媒體介質(zhì),所以 iTunes 仍然使用這種方式。
http://wiki.jikexueyuan.com/project/objc/images/10-1.png" alt="" />
Synchronous Peer-to-Peer (S-P2P)
同步客戶端服務(wù)器 (Synchronous Client-Server, S-CS) 方式隨著網(wǎng)絡(luò)的發(fā)展以及像亞馬遜云服務(wù) (AWS) 這樣的云服務(wù)的流行而變得流行起來。S-CS 可能是當(dāng)今最常用的同步方式。站在實(shí)現(xiàn)的立場(chǎng)上,它和開發(fā)任何其他的 web 服務(wù)非常相似。典型地,一個(gè)自定義的云應(yīng)用程序使用某種語言開發(fā),該全棧式開發(fā)框架可能和客戶端程序無關(guān),比如 Ruby on Rails, Django,或者 Node.js。與云通訊的速度要比本地網(wǎng)絡(luò)慢,但是 S-CS 有一個(gè)優(yōu)勢(shì)叫做“始終在線”,因此,只要網(wǎng)絡(luò)保持連接,客戶端可以在任何位置同步數(shù)據(jù)。
http://wiki.jikexueyuan.com/project/objc/images/10-2.png" alt="" />
Synchronous Client-Server (S-CS)
對(duì)于異步客戶端服務(wù)器 (Asynchronous Client-Server, A-CS) 方式,開發(fā)者使用數(shù)據(jù)存儲(chǔ)的 API,存取本地備份的數(shù)據(jù)。同步數(shù)據(jù)的過程透明地發(fā)生在后臺(tái),應(yīng)用程序代碼通過回調(diào)機(jī)制被告知是否發(fā)生變化。采用這種方式的的例子包括 Dropbox Datastore API,以及對(duì) Core Data 開發(fā)者來說的 Wasabi Sync 服務(wù)。
異步 冗余同步 方式的一個(gè)優(yōu)點(diǎn)是當(dāng)網(wǎng)絡(luò)不可用的時(shí)候,應(yīng)用程序可以繼續(xù)工作并能夠存取用戶數(shù)據(jù)。另一個(gè)優(yōu)點(diǎn)是開發(fā)者不用再關(guān)注通訊和同步的細(xì)節(jié),可以集中精力在應(yīng)用程序的其他方面,數(shù)據(jù)存儲(chǔ)看上去就好像是在設(shè)備本地進(jìn)行的。
http://wiki.jikexueyuan.com/project/objc/images/10-3.png" alt="" />
Asynchronous Client-Server (A-CS)
異步對(duì)等方式 (Asynchronous Peer-to-Peer, A-P2P) 目前尚在初期,并且沒有被廣泛地使用。A-P2P 將所有的負(fù)載分發(fā)到客戶端程序上,并且不使用直接通訊。開發(fā)一個(gè) A-P2P 框架是比較復(fù)雜的,并且導(dǎo)致了一些眾所周知的問題,包括蘋果早期想讓 iCloud 支持 Core Data (現(xiàn)在已經(jīng)支持的很好了)。和 S-CS 一樣,每一個(gè)設(shè)備都有一份數(shù)據(jù)存儲(chǔ)的完整副本。通過交換不同設(shè)備之間的文件的變化來實(shí)現(xiàn)數(shù)據(jù)同步,這些文件通常被稱為 事務(wù)日志 。事務(wù)日志被上傳到云端,然后從云端通過一個(gè)基本的文件處理服務(wù)器 (比如 iCloud, Dropbox) 分發(fā)給其他設(shè)備,這一過程不需要知道日志的具體內(nèi)容。
http://wiki.jikexueyuan.com/project/objc/images/10-4.png" alt="" />
Asynchronous Peer-to-Peer (A-P2P)
鑒于開發(fā) A-P2P 系統(tǒng)的復(fù)雜性,你也許會(huì)問為什么我們還要自找麻煩地去開發(fā)。A-P2P 框架的一個(gè)主要優(yōu)勢(shì)是它抽離了對(duì)智能服務(wù)器的需求。開發(fā)者能夠不用考慮服務(wù)端的開發(fā),并可以利用多種可用的文件傳輸服務(wù)的優(yōu)勢(shì),他們其中的大多數(shù)是免費(fèi)的。而且,由于 A-P2P 系統(tǒng)不連接到一個(gè)特定的服務(wù),也就沒有了供應(yīng)商被鎖定的危險(xiǎn)。
介紹完了不同種類的數(shù)據(jù)同步算法,我現(xiàn)在想介紹一下這些算法的常用組件。你可以想一想如何操控一個(gè)孤立的應(yīng)用程序。
所有的同步方法都有一些共同的要素,包括:
在接下來的部分,在繼續(xù)介紹如何實(shí)現(xiàn)這些算法細(xì)節(jié)之前,我想先介紹這些要素。
在只有一個(gè)數(shù)據(jù)存儲(chǔ)的獨(dú)立應(yīng)用程序中,對(duì)象的識(shí)別典型地可以使用數(shù)據(jù)庫表的行索引,或者在 Core Data 中與之類似的東西,比如 NSManagedObjectID,這些識(shí)別的方法只特定地適用于本地存儲(chǔ),并不適合在不同的設(shè)備之間識(shí)別相應(yīng)的對(duì)象。當(dāng)應(yīng)用程序同步的時(shí)候,很重要的一點(diǎn)就是在不同存儲(chǔ)中的對(duì)象能夠與其他的對(duì)象相互關(guān)聯(lián),因此需要 全局標(biāo)識(shí)符 。
全局標(biāo)識(shí)符通常就是 Universally Unique Identifiers (UUIDs);不同存儲(chǔ)中的對(duì)象,如果具有相同全局標(biāo)識(shí)符,則可以認(rèn)為在邏輯上代表一個(gè)單一實(shí)例。對(duì)一個(gè)對(duì)象的修改最后會(huì)導(dǎo)致相應(yīng)的對(duì)象也會(huì)被更新。(UUIDs 可以由 Cocoa 最近添加的 NSUUID 類來創(chuàng)建,或者經(jīng)常被遺忘的 NSProcessInfo 類的 globallyUniqueString 方法)。
UUIDs 并不是對(duì)所有的對(duì)象都適用。比如,它就不太適合那些有固定成員集合的類的對(duì)象。一個(gè)一般的例子是一個(gè)單例對(duì)象,只有一個(gè)可能的對(duì)象被允許。另外一個(gè)例子是唯一表示是字符串的類標(biāo)簽 (tag-like) 對(duì)象。
但是一個(gè)類決定了對(duì)象標(biāo)識(shí),重要的是它能在全局標(biāo)識(shí)符中被反映出來。邏輯上相同的對(duì)象在不同的存儲(chǔ)中應(yīng)該具有相同的標(biāo)識(shí)符,并且不相同的對(duì)象應(yīng)該具有不相同的標(biāo)識(shí)符。
變化追蹤 用來描述同步算法如何確定自上一次同步后,數(shù)據(jù)發(fā)生了哪些變化,然后本地存儲(chǔ)應(yīng)該如何修改。對(duì)象的每一次修改 (通常稱為 增量) 通常是一個(gè) CRUD 操作:創(chuàng)建 (creation),讀取 (read),更新 (update),刪除 (deletion)。
我們面臨的第一個(gè)選擇就是要選擇記錄粒度的大小。當(dāng)任何單個(gè)屬性變化的時(shí)候,是應(yīng)該更新實(shí)體里的全部屬性呢?還是只記錄被修改的屬性。正確的選擇也許不同;我將會(huì)在研究細(xì)節(jié)的時(shí)候更多地討論這一話題。
在任何一種情況下,你需要一種方法來記錄變化。在最簡(jiǎn)單的情形下,本地存儲(chǔ)里可能就是一個(gè) Boolean 型屬性來標(biāo)識(shí)一個(gè)對(duì)象是不是新的,或者自上一次更新后有沒有被更新。在更高級(jí)的算法中,變化被記錄在主存儲(chǔ)之外,以字典的方式記錄被修改的屬性并有一個(gè)與之相關(guān)聯(lián)的時(shí)間戳。
當(dāng)邏輯上相同的數(shù)據(jù)集有兩個(gè)或更多的存儲(chǔ)時(shí),潛在的 沖突 就可能出現(xiàn)。沒有同步的情況下,修改一個(gè)存儲(chǔ)里某個(gè)對(duì)象,與修改另一個(gè)存儲(chǔ)中與之相對(duì)應(yīng)的對(duì)象,這兩件事可能同時(shí)發(fā)生。這些改變同時(shí)發(fā)生,一些行為可能就會(huì)留下一些沖突的對(duì)象,一旦數(shù)據(jù)同步,合法的狀態(tài)就會(huì)出現(xiàn)在所有的存儲(chǔ)中。
在最簡(jiǎn)單的世界里,讀寫存儲(chǔ)可以被認(rèn)為是原子操作,因此解決沖突就可以簡(jiǎn)單地看成選擇什么版本的存儲(chǔ)。這也許比你想的要普通的多。比如,iCloud 對(duì)文檔的同步就是用的這種方式:當(dāng)發(fā)生沖突的時(shí)候,將詢問用戶希望存儲(chǔ)哪個(gè)版本 — 這沒有合并有沖突的存儲(chǔ)之間的變化。
當(dāng)解決沖突的時(shí)候,有很多種方法來決定優(yōu)先考慮哪些變化。如果你使用了一個(gè)中央服務(wù)器,那么最直接的方式就是假設(shè)最近的一次同步操作級(jí)別最高。所有在這一次同步操作中的變化將覆蓋之前存儲(chǔ)的數(shù)據(jù)。復(fù)雜一點(diǎn)的系統(tǒng)會(huì)比較沖突發(fā)生的時(shí)候的時(shí)間戳然后選擇最近的一次。
沖突解決可能會(huì)比較棘手,如果你已經(jīng)有了選擇,你應(yīng)該避免設(shè)計(jì)一個(gè)模型僅僅是讓它們變得合法。在一個(gè)新的項(xiàng)目中,這比思考所有可能出現(xiàn)的的無效狀態(tài)要容易的多。
關(guān)系可能是非常麻煩的 (這可不是對(duì)人際交往的評(píng)價(jià))。拿一個(gè)實(shí)體 A 和實(shí)體 B 之間簡(jiǎn)單的一對(duì)一的關(guān)系舉例。假設(shè) 設(shè)備1 和 設(shè)備2 都擁有對(duì)象 A[1],和與之相關(guān)的對(duì)象 B[1]。設(shè)備1 創(chuàng)建了一個(gè)對(duì)象 B[2],并將 A[1] 和 B[2] 相關(guān)聯(lián),然后刪除 B[1]。同時(shí),設(shè)備2 也刪除 B[1],但是創(chuàng)建了 B[3],并將 B[3] 和 A[1] 關(guān)聯(lián)。
http://wiki.jikexueyuan.com/project/objc/images/10-5.png" alt="" />
Orphaned object arising from conflicting changes to a one-to-one relationship.
同步之后,將會(huì)出現(xiàn)一個(gè)額外的,孤立的,不和任何對(duì)象 A 相關(guān)聯(lián)的對(duì)象 B。如果關(guān)系需要一個(gè)驗(yàn)證規(guī)則,那么你就得到了一個(gè)無效對(duì)象圖。而這僅僅是你能想到的最簡(jiǎn)單的一種關(guān)系。當(dāng)涉及到更復(fù)雜的關(guān)系時(shí),還可能會(huì)出現(xiàn)更多的問題。
但是這樣的沖突都解決了,對(duì)我們的信息還是有決定性重大幫助的。如果同樣的場(chǎng)景發(fā)生在兩臺(tái)不同的設(shè)備上,就應(yīng)該使用同樣的辦法解決。
這看上去可能顯而易見,但是很容易出錯(cuò)。還是上面那個(gè)例子,如果你的方案是隨機(jī)的選擇對(duì)象 B 中的一個(gè)刪除,在某種情況下,兩個(gè)設(shè)備可能會(huì)刪除不同的對(duì)象,那么最后就完全沒有對(duì)象 B 了。你應(yīng)該力爭(zhēng)在每個(gè)設(shè)備中刪除對(duì)應(yīng)的對(duì)象 B 。這是可以實(shí)現(xiàn)的,可以首先對(duì)對(duì)象排序,然后總是選擇相同的對(duì)象。
既然我們已經(jīng)了解了所有同步算法的基本要素,接下來,就更詳細(xì)的看一看之前介紹的每一種特定的方式,首先介紹同步 (SC) 通訊方法。
我們從最簡(jiǎn)單的可工作的 S-P2P 方案開始。假設(shè)我們有一個(gè)像 iTunes 那樣的 Mac 應(yīng)用程序,它可以通過 USB、藍(lán)牙或者 Wi-Fi 和 iPhone 進(jìn)行同步通訊。憑借快速的本地網(wǎng)絡(luò),我們不用太在意限制數(shù)據(jù)傳輸,所以我們可以在這方面偷點(diǎn)懶。
當(dāng) iPhone 第一次同步的時(shí)候,兩個(gè)應(yīng)用程序通過 Bonjour 發(fā)現(xiàn)對(duì)方,然后 Mac 應(yīng)用程序?qū)⑺乃写鎯?chǔ)數(shù)據(jù)壓縮,通過套接字將壓縮后的文件傳遞給 iPhone 應(yīng)用程序,然后 iPhone 將其解壓并安裝。
現(xiàn)在假設(shè)用戶使用 iPhone 對(duì)已經(jīng)存在的對(duì)象做了修改 (比如,給一首歌打了星級(jí))。該設(shè)備上的應(yīng)用程序給該對(duì)象設(shè)置了一個(gè) Boolean 型的標(biāo)記(比如,changedSinceSync),用來表示該對(duì)象是新的還是已經(jīng)被修改過的。
當(dāng)下一次同步發(fā)生的時(shí)候,iPhone 應(yīng)用程序?qū)⑺乃袛?shù)據(jù)存儲(chǔ)壓縮并回發(fā)給 Mac。Mac 裝載這些數(shù)據(jù),尋找被修改的實(shí)例,然后更新它自己對(duì)應(yīng)的數(shù)據(jù)。然后 Mac 又將更新后的數(shù)據(jù)存儲(chǔ)的完整拷貝發(fā)送給 iPhone,用來替代 iPhone 已經(jīng)存在的數(shù)據(jù)存儲(chǔ),然后整個(gè)流程又重新開始。
雖然還有很多變化和改進(jìn)的可能,但是這的確是一個(gè)可行的方案,并且適用于很多應(yīng)用程序??偨Y(jié)來說,同步操作需要設(shè)備能夠向其他設(shè)備傳輸數(shù)據(jù),并且能夠決定哪些被修改、合并,然后回傳更新后的數(shù)據(jù)。你保證了這兩個(gè)設(shè)備同步之后具有相同的數(shù)據(jù),所以,有很強(qiáng)的健壯性。
當(dāng)?shù)仁街屑尤肓朔?wù)器的時(shí)候,事情變得微妙起來。服務(wù)器是為了能夠更加靈活地同步數(shù)據(jù),但是它是以數(shù)據(jù)傳輸和存儲(chǔ)為代價(jià)的。我們需要盡可能地減少通訊開銷,所以來回地拷貝整個(gè)數(shù)據(jù)是不可行的。
這一次,我還是把重點(diǎn)放在最簡(jiǎn)單可行的方案上。假設(shè)數(shù)據(jù)存儲(chǔ)在服務(wù)器上的數(shù)據(jù)庫中,并且每一個(gè)對(duì)象都有一個(gè)最后更新的時(shí)間戳。當(dāng)客戶端程序第一次同步的時(shí)候,它以序列化 (比如 JSON)的形式下載所有的數(shù)據(jù),然后建立一個(gè)本地存儲(chǔ)。它同樣也在本地記錄了同步的時(shí)間戳。
當(dāng)客戶端程序發(fā)生改變的時(shí)候,它會(huì)更新對(duì)象的最后更新時(shí)間戳。服務(wù)器也會(huì)做同樣的事情,其他設(shè)備也應(yīng)該在這個(gè)過渡期里同步。
當(dāng)下一次同步發(fā)生的時(shí)候,客戶端會(huì)決定自上一次同步后,哪些對(duì)象做了修改,然后僅把被修改的對(duì)象發(fā)送給服務(wù)器。服務(wù)器會(huì)合并這些修改。如果服務(wù)器對(duì)某一個(gè)對(duì)象的拷貝被另一個(gè)客戶端做了修改,那么它會(huì)以最近的時(shí)間戳為準(zhǔn)來保存修改。
然后服務(wù)器會(huì)回傳所有比上一次從客戶端發(fā)來的時(shí)間戳新的變化。這需要考慮到合并的問題,刪除所有覆蓋的變化。
也許有很多不同的方法。比如,你可以為每一個(gè)個(gè)人屬性引入一個(gè)時(shí)間戳,然后在粒度級(jí)去追蹤變化。或者你可以在客戶端合并所有的數(shù)據(jù),然后將合并后的結(jié)果發(fā)回給服務(wù)器,這實(shí)際上是互換了角色。但是,基本說來,一個(gè)設(shè)備發(fā)送修改結(jié)果給其他設(shè)備,然后接收方合并并回發(fā)合并后的結(jié)果。
刪除需要考慮更多。因?yàn)橐坏┠銊h除了一個(gè)對(duì)象,你就不可能跟蹤它了。一種選擇是使用 軟刪除 ,也就是對(duì)象并不是被真正的刪除,而是標(biāo)記為刪除 (比如使用一個(gè) Boolean 屬性)。(這和在 Finder 中刪除一個(gè)文件類似。只有當(dāng)你清空的垃圾桶之后,它才被永久地刪除。)
異步的數(shù)據(jù)同步框架和服務(wù)的吸引力在于它們提供了現(xiàn)成的解決方案。上文提到的同步的數(shù)據(jù)同步方案是要定制的—也就是說你不得不為每一個(gè)應(yīng)用程序?qū)懞芏嗟淖远x代碼。另外,使用 S-CS 架構(gòu),你不得不在所有的平臺(tái)間復(fù)制類似的功能,來保持服務(wù)器的操作。而這需要的技能是大多數(shù) Objective-C 開發(fā)者所不具備的。
異步服務(wù) (比如, Dropbox Datastore API 和 Wasabi Sync 通常提供的框架,讓應(yīng)用程序開發(fā)者用起來好像是本地?cái)?shù)據(jù)存儲(chǔ)。這些框架在本地保存修改,然后在后臺(tái)控制與服務(wù)器的同步。
A-CS 和 S-CS 的一個(gè)最主要的區(qū)別在于,A-CS 框架額外提供的抽象層,屏蔽了直接參與同步的客戶端代碼。這也意味著,同一服務(wù)可以用于所有的數(shù)據(jù)模型,而不是特定的一種模型。
A-P2P 是最沒有被充分開發(fā)的方式,因?yàn)樗彩亲铍y實(shí)現(xiàn)的。但是它的承諾是偉大的,因?yàn)樗?A-CS 在后端更加抽象,使得一個(gè)獨(dú)立的應(yīng)用程序能夠通過不同的服務(wù)進(jìn)行同步。
盡管沒有被充分開發(fā),但還是有應(yīng)用程序已經(jīng)在使用這種方式。比如,著名的待辦事項(xiàng)軟件 Clear 就自己實(shí)現(xiàn)了 A-P2P,通過 iCloud 進(jìn)行同步,并且有在線文檔。還有一些框架像蘋果的 Core Data—iCloud 集成, TICoreDataSync 以及 Core Data Ensembles 均采用這種方式并且逐漸被使用。
作為一個(gè)應(yīng)用程序開發(fā)者,你不需要過多關(guān)心一個(gè) A-P2P 系統(tǒng)是如何工作的 — 錯(cuò)綜復(fù)雜的事物應(yīng)該盡可能被隱藏起來 — 但是還是值得在基本層面了解它是如何工作的,以及所涉及的各種挑戰(zhàn)。
在最簡(jiǎn)單的情形下,每一個(gè)設(shè)備將它的 CRUD 修改保存到事務(wù)日志文件中,然后將它們上傳到云端。每一個(gè)修改都包括一個(gè)有序參數(shù),比如一個(gè)時(shí)間戳,然后當(dāng)設(shè)備從其他設(shè)備接收到新的更改時(shí),作為回應(yīng),它會(huì)建立一個(gè)數(shù)據(jù)存儲(chǔ)的本地拷貝。
如果每一個(gè)設(shè)備一直寫事務(wù)日志,云端的數(shù)據(jù)會(huì) 無限制地 增長。重定基準(zhǔn)技術(shù)可以用來壓縮舊的變化集然后設(shè)置一個(gè)新的 基準(zhǔn)線 。實(shí)際上,由所有舊變化的結(jié)束到新對(duì)象的產(chǎn)生代表了存儲(chǔ)的初始化狀態(tài)。這減少了歷史遺留的冗余的日志。比如,如果刪除了一個(gè)對(duì)象,所有與這個(gè)對(duì)象相關(guān)的修改都被刪除了。
這一段簡(jiǎn)短的描述也許使 A-P2P 看起來是簡(jiǎn)單的算法,但是上面的描述隱藏了許多許多復(fù)雜的東西。A-P2P 是復(fù)雜的,甚至比其他數(shù)據(jù)同步的形式都要復(fù)雜
A-P2P 最大的一個(gè)風(fēng)險(xiǎn)是發(fā)散 (divergence)。由于沒有中央服務(wù)器,沒有不同設(shè)備間的直接通訊,隨著時(shí)間的推移,一個(gè)不良的實(shí)現(xiàn)很容易導(dǎo)致不一致性。(我敢打賭,作為一個(gè)應(yīng)用程序開發(fā)者,你絕對(duì)不想處理像蝴蝶效應(yīng)那樣的問題。)
如果你能保證在云端永久存儲(chǔ)著全部數(shù)據(jù)存儲(chǔ)的最新副本,A-P2P 也就沒那么難了。但是,每一次存儲(chǔ)都拷貝數(shù)據(jù)需要大量的數(shù)據(jù)傳輸,所以 A-P2P 的應(yīng)用程序需要以塊為單位接收數(shù)據(jù),而且它們也不能及時(shí)的知道其他數(shù)據(jù)和設(shè)備。修改甚至?xí)话错樞虻竭_(dá),或者期望從其他設(shè)備發(fā)來的修改還沒有來到。你可以從字面上期望看到還沒有被創(chuàng)建的對(duì)象發(fā)生的改變。
不僅僅是變化可能無序到達(dá),甚至決定順序應(yīng)該是怎樣的都是有挑戰(zhàn)性的。時(shí)間戳通常是不可信的,特別是在 iPhone 這樣的客戶端上。如果你不小心,接受了一個(gè)將來時(shí)間的時(shí)間戳,這可能會(huì)使你不能添加新的改變。使用更健壯的方式使事件及時(shí)按序到達(dá)是可行的 (比如,Lamport Timestamps 和 Vector Clocks),但是還是有代價(jià)的:那就只能近似的使事件及時(shí)按序地到達(dá)。
類似這樣的細(xì)節(jié)還有很多,他們都給 A-P2P 的實(shí)現(xiàn)帶來了挑戰(zhàn)。但是那不意味著我們不要應(yīng)該嘗試?;貓?bào)—后端未知的同步存儲(chǔ)—是有價(jià)值的目標(biāo),而且能夠降低在應(yīng)用程序中實(shí)現(xiàn)同步的困難。
我經(jīng)常聽到人們說同步是一個(gè)已經(jīng)解決了的問題。我多么希望事實(shí)如聽上去那樣簡(jiǎn)單,因?yàn)槟菢拥脑捗恳粋€(gè)應(yīng)用程序都會(huì)支持同步。事實(shí)上,只有很少的應(yīng)用程序支持同步。更準(zhǔn)確來說,同步的方案不易被接納,代價(jià)高,或者在某些方面受限。
我們已經(jīng)知道數(shù)據(jù)同步算法有很多不同的形式,而且確實(shí)沒有普適的方法。你使用的方案取決于你的應(yīng)用程序的需要,你的資源,以及你的編程水平。
你的應(yīng)用是否需要處理大量的媒體數(shù)據(jù)?除非你有大量的啟動(dòng)資金,否則你最好在本地網(wǎng)絡(luò)使用好用的老式的 S-P2P,就像 iTunes 那樣。
想讓單個(gè)數(shù)據(jù)模型擴(kuò)展到社交網(wǎng)絡(luò)或者實(shí)現(xiàn)跨平臺(tái)?自定義 Web 服務(wù)的 S-CS 也許是一個(gè)選擇。
正在開發(fā)一個(gè)新的應(yīng)用程序,重點(diǎn)是要無論在任何地方都能夠同步,但是你又不想花費(fèi)太多的時(shí)間在這方面?那么就使用像 Dropbox Datastore API 這樣的 A-CS 方案吧。
又或者你已經(jīng)有了一個(gè)基于 Core Data 的應(yīng)用程序,不想和服務(wù)器混在一起,而且又不想被某個(gè)供應(yīng)商鎖起來?那么像 Ensembles 這樣的 A-P2P 方案就是你最好的選擇。(好吧,我承認(rèn),我是 Ensembles 項(xiàng)目的創(chuàng)立者和主要程序員。)
總之,做選擇的時(shí)候,要明智一點(diǎn)兒。:)