一種異構數據庫同步的簡單方法

  標題有點高大上,是爲了解決實際應用中的一個問題。作了一個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

  1. 把本身的帳單操做上傳到服務端;
  2. 把別人的帳單操做從服務端同步下來。

這裏爲了把複雜度降到最低,咱們只定義了兩種帳單操做: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

  • 當前用戶是誰
  • 本次要上傳的帳單操做(能夠爲空,已經所有上傳完了)
  • 須要同步的別人的帳單操做的起始index(是以 1,2,3… 這樣編號的,具體實現見後面),以及最多同步幾個操做(防止數據包過大)。

Response有兩方面信息:數據庫

  • 本次上傳是否成功
  • 若是別人有新操做的話,records中記錄了別人的新操做。

具體實現

  下面來看具體的實現。android中固然是使用了sqlite來記錄帳單,服務端則採用mysql。應用自己不須要實時性,但要可靠,不能出現數據重複、缺失等。客戶端上程序運行的週期很短,用戶可能打開記錄一下就關閉了,並且網絡也不必定開啓。服務端的運行環境就相對穩定不少,程序能夠一直運行(只要不crash),網絡也較穩定。重點要解決的問題:數組

  1. 客戶端如何知道哪些操做已經成功上傳了,還有那些操做等待上傳?
  2. 服務端接收到多個用戶上傳上來的操做,怎麼保存、管理,才能方便地供別人來同步?
  3. 客戶端怎麼記錄已經同步了別人的哪些操做?

  第一個問題,主要針對第一個事情。這個比較簡單,純粹是客戶端上的事,不須要服務端配合。能夠參考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)。這裏有兩個問題:網絡

  • 日積月累,records.txt會不會很大,每次上傳都從頭開始seek會很耗性能。這裏我按照月份分隔了,每月單獨記一份文件。服務端就不會有這問題,由於數據是常住內存的。
  • 有時候因爲網絡問題,一次上傳已經到了服務端,服務端作了更新,可是response卻沒能正確回到客戶端,那麼客戶端就不會更新updated_index.txt,下次會重複上傳這些操做記錄。不要緊,服務端入庫時作了去重(很簡單,只要使用primary key的特性便可);可是仍然記錄到操做列表中了,別的客戶端會下載到重複的操做,也不要緊,客戶端下載時也作了去重。哈哈!這裏有點坑,是我老婆發現的。

二、operate記錄操做類型,如前所述,只有insert,update兩種操做
三、id記錄了本次操做的的對象(庫中的id字段值),若是按照binlog的話,應該記錄操做的數據(如cost,comment,day等),但那樣會比較複雜。因此只記錄了id值,而後再到庫中去反查具體的數據。這邊有個可優化點:對於update操做,可能只更新了一個字段,但這裏會把全部字段所有填寫到request中。

  • 補充說明下,這裏的id是string類型的,格式「user_year_month_INT」,以用戶名、年、月和一個遞增的整數組成,這樣保證每一個用戶的id不會相同。

 

  第2、三個問題,主要針對第二個事情,其實是客戶端和服務端配合,來達到多個客戶端間同步的目的。首先說明一下:服務端全部的帳單都記錄在一張表中,也是以id做爲key值,如前所述,這個id值是不對重複的。
一、服務端按照用戶維度,對上傳上來的操做記錄進行管理。爲每個用戶準備一個隊列,隊列中的元素和客戶端上records.txt文件中的每條記錄相似。不一樣的是,這裏記錄的是別人的操做:每當有用戶上傳新操做記錄來時,服務端首先將該操做寫庫,而後在全部其餘用戶的隊列中增長上這條操做。
二、另外每一個用戶準備一個文件(append模式打開),每條操做記錄寫到隊列以前,先寫到文件中。那麼服務端重啓時,就能夠從文件中恢復隊列了。
三、客戶請求到來時,首先取出其中的begin_index,max_line字段,而後到他本身的隊列中找,若是begin_index已經超過了隊列的長度,說明沒有新的更新;不然找出max_line條操做記錄,根據其中的id到庫中反查具體數據,填充response.records(同客戶端)。

  • 這裏有個問題,若是同時有不少用戶,那麼除了本身,其餘人都是混在一塊兒的。因爲只有我和我老婆兩我的用,這裏就將就了;實際上,能夠在隊列元素中,多加一個字段user,就能夠區分開了。

四、客戶端用一個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

相關文章
相關標籤/搜索