标题有点高大上,是为了解决实际应用中的一个问题。作了一个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