怎麼進行分庫分表以及數據遷移

有一個未分庫分表的系統,未來要分庫分表,如何設計纔可以讓系統從未分庫分表動態切換到分庫分表上?

已經明白爲啥要分庫分表了,你也知道常用的分庫分表中間件了,你也設計好你們如何分庫分表的方案了(水平拆分、垂直拆分、分表),那問題來了,你接下來該怎麼把你那個單庫單表的系統給遷移到分庫分表上去?

友情提示

假設,你現有有一個單庫單表的系統,在線上在跑,假設單表有600萬數據

3個庫,每個庫裏分了4個表,每個表要放50萬的數據量

假設你已經選擇了一個分庫分表的數據庫中間件,sharding-jdbc,mycat,都可以

你怎麼把線上系統平滑地遷移到分庫分表上面去

sharding-jdbc:自己上官網,找一個官網最基本的例子,自己寫一下,試一下,跑跑看,是非常簡單的

mycat:自己上官網,找一個官網最基本的例子,自己寫一下,試一下看看

 

從low到高大上有好幾種方案,都給你說一下

 

(1)停機遷移方案

我先給你說一個最low的方案,就是很簡單,大家夥兒凌晨12點開始運維,網站或者app掛個公告,說0點到早上6點進行運維,無法訪問。。。。。。

接着到0點,停機,系統挺掉,沒有流量寫入了,此時老的單庫單表數據庫靜止了。然後你之前得寫好一個導數的一次性工具,此時直接跑起來,然後將單庫單表的數據嘩嘩譁讀出來,寫到分庫分表裏面去。

導數完了之後,就ok了,修改系統的數據庫連接配置啥的,包括可能代碼和SQL也許有修改,那你就用最新的代碼,然後直接啓動連到新的分庫分表上去。

驗證一下,ok了,完美,大家伸個懶腰,看看看凌晨4點鐘的北京夜景,打個滴滴回家吧

但是這個方案比較low,誰都能幹,我們來看看高大上一點的方案

 

(2)雙寫遷移方案

常用的一種遷移方案,比較靠譜一些,不用停機,不用看北京凌晨4點的風景

簡單來說,就是在線上系統裏面,之前所有寫庫的地方,增刪改操作,都除了對老庫增刪改,都加上對新庫的增刪改,這就是所謂雙寫,同時寫倆庫,老庫和新庫。

然後系統部署之後,新庫數據差太遠,用之前說的導數工具,跑起來讀老庫數據寫新庫,寫的時候要根據gmt_modified這類字段判斷這條數據最後修改的時間,除非是讀出來的數據在新庫裏沒有,或者是比新庫的數據新纔會寫。

接着導完一輪之後,有可能數據還是存在不一致,那麼就程序自動做一輪校驗,比對新老庫每個表的每條數據,接着如果有不一樣的,就針對那些不一樣的,從老庫讀數據再次寫。反覆循環,直到兩個庫每個表的數據都完全一致爲止。

接着當數據完全一致了,就ok了,基於僅僅使用分庫分表的最新代碼,重新部署一次,不就僅僅基於分庫分表在操作了麼,還沒有幾個小時的停機時間,很穩。所以現在基本玩兒數據遷移之類的,都是這麼幹了。

 

雙寫部署法(一)
這個就是不停機部署法,這裏我需要先引進兩個概念:歷史數據和增量數據。
假設,我們是對一張叫做test_tb的表進行拆分,因爲你要進行雙寫,系統裏頭和test_tb表有關的業務之前必定會加入一段雙寫代碼,同時往老庫和新庫中寫,然後進行部署,那麼
歷史數據:在該次部署前,數據庫表test_tb的有關數據,我們稱之爲歷史數據。
增量數據:在該次部署後,數據庫表test_tb的新產生的數據,我們稱之爲增量數據。
然後遷移流程如下
(1)先計算你要遷移的那張表的max(主鍵)。在遷移過程中,只遷移db-old中test_tb表裏,主鍵小等於該max(主鍵)的值,也就是所謂的歷史數據。
這裏有特殊情況,如果你的表用的是uuid,沒法求出max(主鍵),那就以創建時間作爲劃分歷史數據和增量數據的依據。如果你的表用的是uuid,又沒有創建時間這個字段,我相信機智的你,一定有辦法區分出歷史數據和增量數據。
(2)在代碼中,與test_tb有關的業務,多加一條往消息隊列中發消息的代碼,將操作的sql發送到消息隊列中,至於消息體如何組裝,大家自行考慮。需要注意的是,只發寫請求的sql,只發寫請求的sql,只發寫請求的sql。重要的事情說三遍!
原因有二:

(1)只有寫請求的sql對恢復數據纔有用。

(2)系統中,絕大部分的業務需求是讀請求,寫請求比較少。

注意了,在這個階段,我們不消費消息隊列裏的數據。我們只發寫請求,消息隊列的消息堆積情況不會太嚴重!
(3)系統上線。另外,寫一段遷移程序,遷移db-old中test_tb表裏,主鍵小於該max(主鍵)的數據,也就是所謂的歷史數據。
上面步驟(1)~步驟(3)的過程如下

等到db-old中的歷史數據遷移完畢,則開始遷移增量數據,也就是在消息隊列裏的數據。
(4)將遷移程序下線,寫一段訂閱程序訂閱消息隊列中的數據
(5)訂閱程序將訂閱到到數據,通過中間件寫入新庫
(6)新老庫一致性驗證,去除代碼中的雙寫代碼,將涉及到test_tb表的讀寫操作,指向新庫。
上面步驟(4)~步驟(6)的過程如下

這裏大家可能會有一個問題,在步驟(1)~步驟(3),系統對歷史數據進行操作,會造成不一致的問題麼?
OK,不會。這裏我們對delete操作和update操作做分析,因爲只有這兩個操作纔會造成歷史數據變動,insert進去的數據都是屬於增量數據。
(1)對db-old中test_tb錶的歷史數據發出delete操作,數據還未刪除,就被遷移程序給遷走了。此時delete操作在消息隊列裏還有記錄,後期訂閱程序訂閱到該delete操作,可以進行刪除。
(2)對db-old中test_tb錶的歷史數據發出delete操作,數據已經刪除,遷移程序遷不走該行數據。此時delete操作在消息隊列裏還有記錄,後期訂閱程序訂閱到該delete操作,再執行一次delete,並不會對一致性有影響。
對update的操作類似,不贅述。

雙寫部署法(二)
上面的方法有一個硬傷,注意我有一句話

(2)在代碼中,與test_tb有關的業務,多加一條往消息隊列中發消息的代碼,將操作的sql發送到消息隊列中,至於消息體如何組裝,大家自行考慮。

大家想一下,這麼做,是不是造成了嚴重的代碼入侵。將非業務代碼嵌入業務代碼,這麼做,後期刪代碼的時候特別累。
有沒什麼方法,可以避免這個問題的?
有的,訂閱binlog日誌。關於binlog日誌,我儘量下週寫一篇《研發應該掌握的binlog知識》,這邊我就介紹一下作用

記錄所有數據庫表結構變更(例如CREATE、ALTER TABLE…)以及表數據修改(INSERT、UPDATE、DELETE…)的二進制日誌。binlog不會記錄SELECT和SHOW這類操作,因爲這類操作對據本身並沒有修改。

還記得我們在雙寫部署法(一)裏介紹的,往消息隊列裏發的消息,都是寫操作的消息。而binlog日誌記錄的也是寫操作。所以訂閱該日誌,也能滿足我們的需求。
於是步驟如下
(1)打開binlog日誌,系統正常上線就好
(2)還是寫一個遷移程序,遷移歷史數據。步驟和上面類似,不囉嗦了。
步驟(1)~步驟(2)流程圖如下

(3)寫一個訂閱程序,訂閱binlog(mysql中有canal。至於oracle中,大家就隨緣自己寫吧)。然後將訂閱到的數據通過中間件,寫入新庫。
(4)檢驗一致性,沒問題就切庫。
步驟(3)~步驟(4)流程圖如下

怎麼驗數據一致性
這裏大概介紹一下吧,這篇的篇幅太長了,大家心裏有底就行。
(1)先驗數量是否一致,因爲驗數量比較快。
至於驗具體的字段,有兩種方法:
(2.1)有一種方法是,只驗關鍵性的幾個字段是否一致。
(2.2)還有一種是 ,一次取50條(不一定50條,具體自己定,我只是舉例),然後像拼字符串一樣,拼在一起。用md5進行加密,得到一串數值。新庫一樣如法炮製,也得到一串數值,比較兩串數值是否一致。如果一致,繼續比較下50條數據。如果發現不一致,用二分法確定不一致的數據在0-25條,還是26條-50條。以此類推,找出不一致的數據,進行記錄即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章