最近實現了個比較有意思的功能,分享下想法。
背景
因為一些原因,需要寫一個中間層服務,同步 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個系統之間數據同步的復雜性,其次讓業務代碼只用操作一個庫,不用寫代碼時還老想着這張表對應哪個庫的。數據中台也是這個思想吧,各個系統的插入最后都到數據中台里,查時統一從數據中台查。
項目中也是這樣,權衡利弊,更小的代價做更多的事。
程序的性能優化,對業務充分理解再進行表結構設計,讓代碼盡可能的復用,抽象出更多公共類……
這些是增加寫代碼時的復雜性,減少了服務器運行的開銷、后續人接手的困難……
具體增加哪方面的復雜性,權衡利弊,就是人與人之間的差異吧。我喜歡看到代碼中前人靈性的一面。
最后,是個人心態方面,我依然常常被瑣事所困擾;但恢復的比以前快多了,明白自己的底線,忽略那些無所謂的小事就好。如果只盯着眼前,就一直只做眼前這些事。