標題有點高大上,是爲了解決實際應用中的一個問題。作了一個Android應用,用於記錄平常消費帳單,開始是單機版的,我老婆說太low了,起碼要能看到彼此的消費狀況吧。爲此,我還專門寫了一套基於protobuf的RPC組件,用於網絡通訊,http://www.cnblogs.com/zmkeil/p/5176758.html。html
應用自己比較簡單,幾張簡單粗暴的UI,涵蓋了增、刪、改各類功能,外加一個後臺service組件,用於上傳帳單,並同步他人帳單。也算是麻雀雖小五臟俱全吧,看幾張效果圖。代碼見https://github.com/zmkeil/MyBill,能夠直接安裝使用,不想帳單被我偷窺的話,在配置中將服務器地址亂填便可。mysql
言歸正傳,按照DBA的工做方式,數據庫同步的最簡單方法就是把一個庫上的全部操做,完徹底全地在另外一個庫上執行一遍,mysql的主從庫,就是利用binlog來複制全部的insert、update、delete等操做,來實現同步的,這其實也是一種增量同步的思想。借鑑這一思想,在這個應用中咱們主要作兩個事情:android
這裏爲了把複雜度降到最低,咱們只定義了兩種帳單操做:insert、update。在表中增長一個is_deleted字段,update該字段來實現刪除功能。
把握一個最基本的原則,服務端只負責記錄帳單操做,不保存任何客戶端的狀態(如已經同步了哪些操做等),換而言之,服務端只提供最基本的需求:1)有新操做來,我把操做寫到數據庫中,而且把該操做記錄下來,供別人同步;2)要同步別人的操做,那你須要提供從哪一條開始同步,最多同步多少條等操做。git
首先看一下protobuf-rpc的service,很是簡單,只有一個接口。github
package microbill; option cc_generic_services = true; message Record { enum Type { NEW = 0; UPDATE = 1; } required Type type = 1; required string id = 2; required fixed32 year = 3; required fixed32 month = 4; optional fixed32 day = 5; optional fixed32 pay_earn = 6; optional string gay = 7; optional string comments = 9; optional fixed32 cost = 10; } message BillRequest { required string gay = 1; // push self's records repeated Record records = 2; // pull other's records optional fixed32 begin_index = 3; optional fixed32 max_line = 4 [default = 10]; } message BillResponse { required bool status = 1; optional string error_msg = 2; repeated Record records = 4; } service BillService { rpc update(BillRequest) returns (BillResponse); }
Request有三方面信息:sql
Response有兩方面信息:數據庫
下面來看具體的實現。android中固然是使用了sqlite來記錄帳單,服務端則採用mysql。應用自己不須要實時性,但要可靠,不能出現數據重複、缺失等。客戶端上程序運行的週期很短,用戶可能打開記錄一下就關閉了,並且網絡也不必定開啓。服務端的運行環境就相對穩定不少,程序能夠一直運行(只要不crash),網絡也較穩定。重點要解決的問題:數組
第一個問題,主要針對第一個事情。這個比較簡單,純粹是客戶端上的事,不須要服務端配合。能夠參考mysql的binlog思想,專門以一個records.txt文件來記錄全部的操做,格式以下:服務器
index operate id
一、index從1開始,依次遞增,惟一標示該次操做。這樣就能夠用另外一個文件updated_index.txt來記錄已經上傳到了哪一條(是順序上傳的),這個文件很是簡單,只要記錄一個index便可。那麼下次上傳時,首先根據updated_index.txt找到須要上傳的起始index,而後到records.txt中去找(直接seek到第index行便可),每次默認上傳2條操做。僅當response.status = true時,才更新updated_index.txt文件(即原index += 2)。這裏有兩個問題:網絡
二、operate記錄操做類型,如前所述,只有insert,update兩種操做
三、id記錄了本次操做的的對象(庫中的id字段值),若是按照binlog的話,應該記錄操做的數據(如cost,comment,day等),但那樣會比較複雜。因此只記錄了id值,而後再到庫中去反查具體的數據。這邊有個可優化點:對於update操做,可能只更新了一個字段,但這裏會把全部字段所有填寫到request中。
第2、三個問題,主要針對第二個事情,其實是客戶端和服務端配合,來達到多個客戶端間同步的目的。首先說明一下:服務端全部的帳單都記錄在一張表中,也是以id做爲key值,如前所述,這個id值是不對重複的。
一、服務端按照用戶維度,對上傳上來的操做記錄進行管理。爲每個用戶準備一個隊列,隊列中的元素和客戶端上records.txt文件中的每條記錄相似。不一樣的是,這裏記錄的是別人的操做:每當有用戶上傳新操做記錄來時,服務端首先將該操做寫庫,而後在全部其餘用戶的隊列中增長上這條操做。
二、另外每一個用戶準備一個文件(append模式打開),每條操做記錄寫到隊列以前,先寫到文件中。那麼服務端重啓時,就能夠從文件中恢復隊列了。
三、客戶請求到來時,首先取出其中的begin_index,max_line字段,而後到他本身的隊列中找,若是begin_index已經超過了隊列的長度,說明沒有新的更新;不然找出max_line條操做記錄,根據其中的id到庫中反查具體數據,填充response.records(同客戶端)。
四、客戶端用一個sync_index.txt文件,記錄下次要同步的別人的操做記錄index,初始爲1,每次response.records不爲空時,更新該值(+= respons.records.count())。
剛開始想得很簡單,不過到如今前先後後快4個月了,呵呵~~ 總算如今有個比較OK的版本了,代碼不夠嚴謹,補了又補,功能還行。
記得剛開始寫RPC框架時,熱情高漲,天天下班寫到凌晨二、3點,那時候正好是最冷的時候,給本身點個贊。後來寫android,就比較拖沓了,和用戶操做直接相關的,會比較煩。
到此告一段落。
RPC框架,http://www.cnblogs.com/zmkeil/p/5176758.html服務端代碼,https://github.com/zmkeil/microbill-server.gitandroid代碼,https://github.com/zmkeil/MyBill.git