问题的来源

先描述一下问题:

两个系统的数据需要做数据同步,可以是数据库到数据库,也可以是数据库到内存。要求高性能(速度快),最终一致性(数据不能错)。

 

利用 Job 来定时同步数据

首先,如果是中小型系统,可以接受秒级别的延时,数据变更频率不是非常高的话,可以用这个方案。

常见的场景有:后台系统用户数据,后台系统角色、组织架构数据。

 

Job 处理过程

那 Job 的数据逻辑是怎么样的呢?

查询数据 -> 可能需要做一些转换 -> 然后插入数据

看上去好简单的样子,但是如何查询数据会是一个挑战。因为假如你的表很大,而实际每分钟只会变更几条,难道你每次都要全量同步一边?

如何识别出那些变过的数据呢?

 

为你的数据加上 UpdateTime 字段

为了解决这个问题,需要对你的数据库表进行一定的改造。其实,所有的数据库表都应该这么设计。

第一个要改造的点是加上一个UpdateTime字段,每次更新数据的时候都需要去更新这个字段。

如果用的是 MySQL 的话,可以直接把这个字段定义成这样子:

UpdateTime timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP

这样设置以后,每次数据变更 MySQL 会自动更新这个字段。另外,如果你进行了Update操作而数据其实没变,那么这个字段不会更新,非常贴心。

这个是为了解决什么问题?

我必须要知道所有数据的变更时间,我才能找到那些新插入或者新更新的数据。所以加上这个字段后,InsertUpdate的问题就解决了,但是还有Delete

 

为你的数据加上 IsDeleted 字段

你还在直接删除数据?如果是的话,赶紧改改吧,因为这么做在 ETL 系统中非常不友好。源表中的数据都删了,那 Job 还怎么找到它?难道要做两遍的数据比对?这个又要全表扫描目标表了。性能会非常差。

所以最简单的方案就是加上IsDeleted字段进行软删除,这样删除的数据其实就是Update了一下,处理起来就非常简单了。

如果你的系统在设计之初就用了软删除,那么你会轻松很多,如果不是,那么就需要修改你的程序了。

相信我,越早改越好!除非你的系统永远那么小…

 

选择合适的时间

下面一个问题才是真正头疼的地方。我们也用过很多现成的 ETL 系统来同步数据了,源表也加上了上面提到的两个字段来识别有变更的数据。

 

假设作业5分钟跑一次,那怎么写 SQL 语句呢?

SELECT * FROM User WHERE UpdateTime >= DATE_SUB(Now(),INTERVAL 5 MINUTE)

这么写有什么缺点呢?第一个缺点就是,你没办法保证你的 Job 真的是5分钟运行一次的,如果是5分01秒呢?那多出来的1秒的数据就丢了。

另外,如果你 Job 所在机器的时间和数据库的时间不一致呢?极端情况假设你的 Job 机器快了10分钟,那么 Job 就永远取不到数据了。别以为这种情况不会发生…

 

这个方案直接被拍死,那么下一个方案:

SELECT * FROM User WHERE UpdateTime >= #LastRunTime#

这里需要加入一个LastRunTime变量,直接变量由 Job 程序自己记录。加上这个变量后,就解决了 Job 没办法准确地5分钟跑一次的问题。

但是机器时间不一致的问题解决了吗?好像还没有…

 

那来一个终极完美的方案吧:

SELECT * FROM User WHERE UpdateTime >= #LastRowTime#

这里的LastRowTime是什么含义呢?它是你上次取到数据的最新一条。恩,这样就完全不依赖 Job 所在机器的时间了。哪怕它们相隔几年,数据也不会丢。因为你用的是相对时间。

那为什么是>=而不是>呢?

极端情况下,假设在同一个时间插入两条数据,它们的LastUpdateTime是一样的。而你取数据的时候,只插入了第一条,取完了才插入第二条。如果你后面查询的时候用>,这个这里第二条数据就丢掉了。

 

最后,还有一个折中方案,因为很多现成的 ETL 系统没办法记录LastRowTime,只能记录LastRunTime,那么可以这么写:

SELECT * FROM User WHERE UpdateTime >= DATE_SUB(#LastRunTime#,INTERVAL 10 MINUTE)

这里我们可以接受两台机器有时间差,但是最多十分钟,这样做也会有缺点,每次都会有一些数据重复同步。

或者,你的 ETL 系统连LastRunTime都没有(那还算是一个合格的 ETL 系统吗?),那么只能这样了,每五分钟同步一次:

SELECT * FROM User WHERE UpdateTime >= DATE_SUB(NOW(),INTERVAL 10 MINUTE)

 

利用阿里巴巴开源组件 canal

上面的方案虽说只支持中小型系统,但是你们的数据库表真的会有频繁变更吗?如果没有,那么上面的方案一直是适用的。

但是如果你们系统的更新真的非常频繁,而且你们要接近实时的同步效率那怎么办?

如果有这样需求的话,建议尝试看看阿里巴巴的canal,项目地址在这里:https://github.com/alibaba/canal

本作品由 Dozer 创作,采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。