最近实现了个比较有意思的功能,分享下想法。
背景
因为一些原因,需要写一个中间层服务,同步 Oracle 所有数据到 MySQL,同步部分 MySQL 表数据到 Oracle 中。
实现思路
Oracle通过给表创建物化日志,程序定时任务读取物化日志的方式同步到MySQL库中;
MySQL用阿里的canal组件,实时监控库里的改变,同步到Oracle中。
这里我想到之前读的DDIA复制那一章节,本质上应该就是双主双写数据库之间的同步。
遇到了一些困难的问题。
Oracle 定时查物化日志,同步给MySQL,canal监控到MySQL数据产生变化,会再推给Oracle,Oracle表数据发生改动,会记录导物化日志里。这里就形成了死循环。
这里我联想到了悲观锁与乐观锁,我的实现方式应该参考了乐观锁思想。
我选择在MySQL中加入标记表,因为表名、主键、操作类型是肯定都有的数据,所以在Oracle 插入时,把表名、主键、操作类型记录到缓存与表中;在canal执行时,先查缓存,查不到再查表,存在的话就表明是定时任务更新来的数据,不再推给 Oracle。最后都会根据表名、主键、操作类型,删除标记表相应数据。
这样,Oracle的数据推给时,MySQL不会再推给Oracle;终结。
MySQL数据更新,推给Oracle,Oracle物化日志再给MySQL,MySQL不会再推给Oracle;终结。
其实可以再加个标记表来标记MySQL推到Oracle的数据的,但我感觉太绕了,就没再加了。
Canal运行一段时间后,总是会报broken pie,网上查了许久也没找到答案,无奈之下翻 GitHub里的Issue,找到了解决办法。Jar包换成更新的版本就好……以前用NIO的方式,0.26以后就变成的BIO,更稳定。NIO适用多线程短连接,BIO适用于长连接。果然,项目中学习更加快一些,以前看什么鬼IO多路复用,一点都记不下来!
日志打印,本来都很分散,也全都打出来,把日志撑得很大。而且现场服务也有许多其它包的打印。我记得日志是可以改显示级别的,但服务器的控制权不在我们手中……就写个日志打印公共类去解决了一下。
也实验了不少想法,jdk1.6,让好多想用的插件都用不起来,比如Redis,消息队列这些。
但他们底层都是用Java 实现的,我用一个单例的map,去替代Redis,提升了不少判断标记的时间。
同事那边也在canal里用了Queue类,我以前都没用过,应该就是队列,数据结构是先进先出,都在一头被操作,并发都要用这个,消息队列的底层应该也是用这个吧。
Canal提供的demo代码,可能因为版本问题,finally里执行总报错,最后在finally里让线程停10秒,再调用自己,这应该是递归吧。
一些感想
软件工程中的任何困难都可以依靠增加一个中间层来简单化。写的任何代码,实现的任何功能都是通过增加一件事的复杂性,去降低另一件事的复杂性。
这次写的服务也是这样,首先是客户要求,必须要写,它屏蔽2个系统之间数据同步的复杂性,其次让业务代码只用操作一个库,不用写代码时还老想着这张表对应哪个库的。数据中台也是这个思想吧,各个系统的插入最后都到数据中台里,查时统一从数据中台查。
项目中也是这样,权衡利弊,更小的代价做更多的事。
程序的性能优化,对业务充分理解再进行表结构设计,让代码尽可能的复用,抽象出更多公共类……
这些是增加写代码时的复杂性,减少了服务器运行的开销、后续人接手的困难……
具体增加哪方面的复杂性,权衡利弊,就是人与人之间的差异吧。我喜欢看到代码中前人灵性的一面。
最后,是个人心态方面,我依然常常被琐事所困扰;但恢复的比以前快多了,明白自己的底线,忽略那些无所谓的小事就好。如果只盯着眼前,就一直只做眼前这些事。