數據庫拆分實戰

前言

對遺留系統的微服務化改造,從整體上來說,整個過程包含兩個部分:一,通過某一種方法論將系統進行微服務劃分,比如 DDD 倡導的限界上下文劃分方法。根據系統的特點和運行狀態,又分爲具體的兩種實施策略,絞殺者模式和修繕模式。二,數據庫的拆分,只有在數據層面也拆分開,才能真正達到服務化的目的。具體也可以分爲,與業務服務拆分同時進行,或者等業務服務拆分後再單獨進行兩種策略。

似曾相識的步驟

如果不考慮在拆庫的同時引入新功能,拆庫其實也是一種重構。Martin Fowler 在**《Refacotring》**中強調數據庫具有高度的耦合性,數據庫重構存在相當的難度。不過好在還有另一本權威著作來爲此背書,那就是**《Refactoring Databases》**。

來看看這本書提到的數據庫重構步驟:

  1. Verify that a database refactoring is appropriate

  2. Choose the most appropriate database refactoring

  3. Deprecate the original database schema

  4. Test before, during, and after

  5. Modify the database schema

  6. Migrate the source data

  7. Refactor external access program(s)

  8. Run your regression tests

  9. Version control your work

  10. Announce the refactoring

  11. What you have learned

是不是和代碼的重構似曾相識,分析 -> 測試 -> 修改 -> 測試……

同時也看看我們的數據庫拆分實踐是否能和這些步驟有所呼應。

背景介紹

我們曾經對某客戶企業的系統做服務化改造。根據其組織架構和系統特點,最終採取了先服務拆分,再數據庫拆分的演進路線。

到服務化改造基本完成時,系統邏輯結構如下圖所示:

右邊的圖完全就是《Refactoring Databases》裏說的 Multi-Application Database

接下來就是數據庫的重構了,也是本文的重點。

分析在前

系統數據庫採用 MySQL,由於之前是一個大單體,所有的數據都存在一個數據庫裏。隨着業務的增長,單庫雖然已經使用了頂級的硬件,性能仍顯不足。所以不管從架構上,還是性能上,拆庫都迫在眉睫。這也就回答了 Verify that a database refactoring is appropriate 的問題。

數據庫重構相對於代碼重構畢竟影響更廣,風險更大。直接採用 XP 的模式風險太大,必要的分析必不可少,整個過程力求一次正確。

首先必須瞭解數據庫的全貌,經過一番溝通梳理出架構圖如下:

整個數據庫由主庫,備庫,歷史庫,歸檔庫組成,備庫主要用於監控和 BI 等,歷史庫用於存放達到某個狀態後的訂單數據,主庫和歸檔庫由於歷史原因都會被業務服務訪問。歸檔庫性能相對較差,只用于歸檔數據。

主庫,歷史庫,歸檔庫之間可以互相遷移數據,遷移代碼是完全自研的,支持單個訂單的一系列數據遷移,也支持批量的訂單遷移。

業務服務的遷移歷經一年多的時間,單體也進化成了十幾個微服務,要想一次性把數據庫全部拆開不太現實,風險也不可控。最好的方式是找出當前數據庫的瓶頸,先將業務上訪問量最大的部分拆開。經過調研,決定先將數據庫一分爲二,先將發貨單拆出去,類似於修繕模式,訂單及其他數據先保留。這也呼應了 Choose the most apporiate database refactoring,所以設想拆分後的數據庫應該如下圖所示:

從圖上不難看出,需要修改的點包括:

1. 業務代碼

1.1 發貨單服務的數據庫配置

1.2 所有類似 join 查詢的級聯操作,主要集中在頁面查詢,導出,報表等。(寫入操作在微服務拆分時基本已經修改)

2. 數據

2.1 新建發貨單數據庫,schema 和用戶

2.2 已有的發貨單相關的數據遷移至新數據庫

3. 遷移代碼

3.1 源庫和目標庫都要支持多源配置,現在是一分二,將來會更多

3.2 遷移邏輯,要保證數據一致性。即一個訂單的數據要麼全在主庫,要麼全在歷史庫或歸檔庫

4. 監控和 BI

4.1 多源配置

4.2 監控邏輯修改

中間過程

通知上下游

這一點非常重要,在一個涉及多部門的系統上做數據庫遷移。有些影響絕不是靠自己就能考慮周全的,在做所有遷移之前就要通知各方評估各自的影響和改動週期。

業務代碼的修改

  1. 測試先行

重構最重要的一點是不改變程序的外在表現。對於數據庫重構來說,只需要保證對外暴露的 API 在特定輸入下,輸出是一致的。

在這個點上,測試是比較容易寫的。自動化測試和人工測試同時展開,以黑盒集成測試爲主。在每個 API 修改之前先根據現有結果編寫測試,同時 QA 記錄輸入,輸出,注意各種邊界情況的測試。在這個階段基本忽略現有代碼邏輯的正確性,先保證拆庫前後 API 的行爲一致。

當然,這是理想的情況,在真正開始做以後,就會發現情況並不是這麼簡單。

  1. 業務代碼修改

指導思想是將級聯查詢修改爲 API 調用補齊數據。然而這裏面有一個特殊情況,當遇到 join,groupBy,有 where 條件,再加上分頁的場景,API 調用補齊數據的方式就不能很好的處理。說說當時的幾種處理辦法:

實際操作下來,發現其實業務上並沒有設想的那麼難。首先只有個別 API 存在這種情況,其次這些 API 的一些字段可能是一些歷史原因造成的,刪除對現有的業務影響並不大。

當然如果最終無法在業務上達成一致,那就要考慮在報表庫和數據湖層面做聚合了,方法總是有的。

數據遷移

  1. 開發過程

過程中有三種方法:

優點:簡單易操作,開發過程無需做數據遷移。

缺點:邏輯劃分畢竟不能完全模擬真實的生產環境。例如有些表是多個服務共享的,開發時只能多個用戶同時授權。如果業務代碼修改不徹底,就會出現一個服務寫入,其他服務讀取的情況。一旦上了生產,表做了物理隔離,就會造成讀取不到數據的事故。grant select,insert,update,delete on existing_schema.existing_table to 'new_user'@'%';

優點:幾乎可以模擬生產數據庫,業務代碼好排查,畢竟新加的 schema 在之前的業務代碼裏沒有,很容易測試發現問題。

缺點:需要將部分表遷移至新 schema。如果是 MySQL,在不同 schema 之間遷移表還是比較容易的。例如:alter table existing_schema.existing_table rename new_schema.existing_table;

優點:完全模擬生產數據庫

缺點:不同物理庫之間要做數據遷移

回頭看,有條件的情況下第三種方法最爲保險。第二種方法性價比最高。

  1. 生產數據遷移過程

由於是線上運行系統,版本上線窗口時間有限,不可能在上線當晚執行全部的數據遷移,所以必須提前做,這樣數據質量也有保證。

這裏也有兩種方法來做主備遷移:

這樣在上線前就可以不斷檢查數據遷移的質量,上線當晚只需要很短時間的停機,甚至不停機。上線後兩個主庫都包含了很多彼此的歷史數據,可以不急於刪除,以防需要回滾。

主備庫的遷移代碼修改

之前要遷移的表都在一個數據庫裏,遷移可以用一個事務來保證同時成功或失敗。現在分佈在兩個庫裏,只能通過最終一致性來保證。

像以往的 AP 系統的處理方法,事件表加消息隊列,訂單的遷移觸發發貨單的遷移。實際修改過程中還碰到很多具體問題,發了兩次消息才最終達成一致,不過這些都是細節了。

這裏需要提醒的是,遷移程序的數據庫信息最好都是可配置的,以防上線過程中數據庫地址、schema、用戶名等臨時變更。

監控和 BI

這些在當時的上下文下優先級偏低,在第一次上線時,對於不能及時調整的,都先做了屏蔽處理,不過處理的思路和上面類似。

以上的這些步驟基本上和《Refactoring Databases》中提到的如下步驟不謀而合。Test before, during, and afterModify the database schemaMigrate the source dataRefactor external access program(s)Run your regression testsVersion control your workAnnounce the refactoring

上線

因爲上線之前發貨單的數據庫一直在同步主庫的數據,並且上線過程中同步仍然保持,理論上上線可以做到不停機。但是如果存在高併發的情況,主從 binlog 同步延遲大,很可能會造成部分髒數據,保險起見短暫的停機上線比較安全。

其實上面提到的問題,理論上是新老兩個版本同時在線上運行造成的。其中一個典型的例子就是灰度發佈,但是灰度發佈往往在數據上是隔離的,唯一要考慮的是配置項能不能區分開,因爲當前微服務往往會從_配置服務器_中取配置。如果配置項中不支持灰度實例配置項,就要特別注意。

這裏也有兩種方法可以選擇:

第二種方法會導致配置項分散,所以優先選擇第一種。

另外,上線之後生產環境的測試必不可少。測試環境再如何測試也不能百分百保證生產環境一定沒問題。條件允許的情況下,多測一些修改過的地方和系統的關鍵功能。

總結

回顧整個拆庫流程,整體的策略還是對的。先找到數據庫的瓶頸,把一部分拆分出去,梳理清楚整個流程,之後進一步的細分,就水到渠成了。

但是數據庫重構和代碼重構有相似之處,也有不同之處。

相似之處在於修改的過程中基本的思路是一致的,測試 -> 修改 -> 測試,小步快跑,反覆迭代。

不同之處在於拆庫還依賴於硬件的基礎設施,這就更要求測試環境儘量去模擬生產環境。總結下來,整個過程出了兩個問題都是沒有完全模擬生產環境導致的:

好在這兩個問題都及時發現,並很快糾正了過來。

在實際中,可能每個拆庫的場景都不盡相同,沒有絕對適用的流程方法,需要因地制宜,靈活操作。

最後,不管是業務代碼的修改還是數據庫的修改,最怕的是有些場景沒想到。一旦想到了,解決的辦法總是有的。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/6SiH6trc7L6_SucYRhNQbA