256變4096:分庫分表擴容如何實現平滑數據遷移?

簡介: 本文做者就一個高德打車彈外訂單系統進行了一次擴分庫分表和數據庫遷移。mysql

1、 背景

2020年,筆者負責的一個高德打車彈外訂單系統進行了一次擴分庫分表和數據庫遷移。該訂單系統總體部署在阿里雲上,服務使用阿里雲ECS部署,數據庫採用阿里雲RDS,配置中心基於阿里雲ACM自研,數據同步基於阿里雲DTS自研以及自研分庫分表組件、分佈式ID組件等等。redis

這次進行擴分庫分表的背景是,原4實例4庫、每一個庫64張表一共256張表,部分單表已超千萬量級,按當前每日單量量級,一年內單表會達到上億條記錄,單表數據量過大會帶來數據庫性能問題。sql

注:【彈內彈外】彈是指彈性計算,彈內與彈外實際上是指兩套獨立的彈性計算網絡環境。彈內主要是指部署在阿里生產網的彈性計算環境,最先是基於原有淘寶技術構建的,主要用於支撐淘寶業務。彈外主要是指部署在阿里公有云的彈性計算環境,支撐了阿里雲計算業務。數據庫

二 、容量規劃

1 、當前分庫分表狀況網絡

4實例(16C/64G/3T SSD),4庫(每一個實例一個庫),每庫64張表,共256張表。mybatis

經過RDS後臺一鍵診斷功能,來計算表空間使用狀況(這裏拿測試環境數據庫舉例)。架構

2 、容量計算併發

實例數異步

數據庫的瓶頸主要體如今:磁盤、CPU、內存、網絡、鏈接數,而鏈接數主要是受CPU和內存影響。CPU和內存能夠經過動態升配來提高,可是SSD磁盤容量最大支持到6T(32C如下最大3T、32C及以上最大6T)。分佈式

可是現階段兼顧成本,可先將實例擴容一倍,採用8個實例(16C/64G/3T SSD),每一個實例建4個庫(database)、每一個庫128張表(這裏其實是一個成本取捨的過程,理論上應該採起"多庫少表"的原則,單庫128張表其實太多了,單庫建議32或64張表爲宜)。

後續若是實例壓力提高可進行實例配置升級(16C/128G、32C/128G、32C/256G等);將來如出現單實例升配沒法解決,在考慮擴容實例,只須要將database遷移至新實例,遷移成本較小。

表數

按單表最多1000w條數據評估,4096張表可支持日5000w單3年(10.1壓測標準)、日2000w單5年的架構。(因業務表比較多,此處忽略掉單條數據大小的計算過程)

庫數

32個庫,每一個庫128張表。將來可最大擴容到32個實例,無需rehash,只須要遷移數據。

 

阿里雲RDS規格和價格一覽

3、 數據遷移

因擴分庫分表涉及到rehash過程(256表變4096表),而阿里雲DTS只支持同構庫數據遷移,因此咱們基於DTS的binlog轉kafka能力自研了數據同步中間件。

整個數據遷移工做包括:前期準備、數據同步環節(歷史數據全量同步、增量數據實時同步、rehash)、數據校驗環節(全量校驗、實時校驗、校驗規則配置)、數據修復工具等。

1 、準備工做

惟一業務ID

在進行數據同步前,須要先梳理全部表的惟一業務ID,只有肯定了惟一業務ID才能實現數據的同步操做。

須要注意的是:

  • 業務中是否有使用數據庫自增ID作爲業務ID使用的,若是有須要業務先進行改造,還好訂單業務裏沒有。
  • 每一個表是否都有惟一索引,這個在梳理的過程當中發現有幾張表沒有惟一索引。

一旦表中沒有惟一索引,就會在數據同步過程當中形成數據重複的風險,因此咱們先將沒有惟一索引的表根據業務場景增長惟一索引(有多是聯合惟一索引)。

在這裏順便提一下,阿里雲DTS作同構數據遷移,使用的是數據庫自增ID作爲惟一ID使用的,這種狀況若是作雙向同步,會形成數據覆蓋的問題。解決方案也有,以前咱們的作法是,新舊實體採用自增ID單雙號解決,保證新舊實例的自增ID不會出現衝突就行。由於此次咱們使用的自研雙向同步組件,這個問題這裏不細聊。

分表規則梳理

分表規則不一樣決定着rehash和數據校驗的不一樣。需逐個表梳理是用戶ID緯度分表仍是非用戶ID緯度分表、是否只分庫不分表、是否不分庫不分表等等。

二、 數據同步

數據同步總體方案見下圖,數據同步基於binlog,獨立的中間服務作同步,對業務代碼無侵入。

接下來對每個環節進行介紹。

歷史數據全量同步

單獨一個服務,使用遊標的方式從舊庫分批select數據,通過rehash後批量插入(batch insert)到新庫,此處須要配置jdbc鏈接串參數rewriteBatchedStatements=true才能使批處理操做生效。

另外特別須要注意的是,歷史數據也會存在不斷的更新,若是先開啓歷史數據全量同步,則剛同步完成的數據有可能不是最新的。因此這裏的作法是,先開啓增量數據單向同步(從舊庫到新庫),此時只是開啓積壓kafka消息並不會真正消費;而後在開始歷史數據全量同步,當歷史全量數據同步完成後,在開啓消費kafka消息進行增量數據同步(提升全量同步效率減小積壓也是關鍵的一環),這樣來保證遷移數據過程當中的數據一致。

增量數據實時同步

增量數據同步考慮到灰度切流穩定性、容災和可回滾能力,採用實時雙向同步方案,切流過程當中一旦新庫出現穩定性問題或者新庫出現數據一致問題,可快速回滾切回舊庫,保證數據庫的穩定和數據可靠。

增量數據實時同步採用基於阿里雲DTS的數據訂閱自研數據同步組件data-sync實現,主要方案是DTS數據訂閱能力會自動將被訂閱的數據庫binlog轉爲kafka,data-sync組件訂閱kafka消息、將消息進行過濾、合併、分組、rehash、拆表、批量insert/update,最後再提交offset等一系列操做,最終完成數據同步工做。

  • 過濾循環消息:須要過濾掉循環同步的binlog消息,這個問題比較重要後面將進行單獨介紹。
  • 數據合併:同一條記錄的多條操做只保留最後一條。爲了提升性能,data-sync組件接到kafka消息後不會馬上進行數據流轉,而是先存到本地阻塞隊列,而後由本地定時任務每X秒將本地隊列中的N條數據進行數據流轉操做。此時N條數據有多是對同一張表同一條記錄的操做,因此此處只須要保留最後一條(相似於redis aof重寫)。
  • update轉insert:數據合併時,若是數據中有insert+update只保留最後一條update,會執行失敗,因此此處須要將update轉爲insert語句。
  • 按新表合併:將最終要提交的N條數據,按照新表進行拆分合並,這樣能夠直接按照新表緯度進行數據庫批量操做,提升插入效率。

整個過程當中有幾個問題須要注意:

問題1:怎麼防止因異步消息無順序而致使的數據一致問題?

首先kafka異步消息是存在順序問題的,可是要知道的是binlog是順序的,因此dts在對詳細進行kafka消息投遞時也是順序的,此處要作的就是一個庫保證只有一個消費者就能保障數據的順序問題、不會出現數據狀態覆蓋,從而解決數據一致問題。

問題2:是否會有丟消息問題,好比消費者服務重啓等狀況下?

這裏沒有采用自動提交offset,而是每次消費數據最終入庫完成後,將offset異步存到一個mysql表中,若是消費者服務重啓宕機等,重啓後從mysql拿到最新的offset開始消費。這樣惟一的一個問題可能會出現瞬間部分消息重複消費,可是由於上面介紹的binlog是順序的,因此能保證數據的最終一致。

問題3:update轉insert會不會丟字段?

binlog是全字段發送,不會存在丟字段狀況。

問題4:循環消息問題。

後面進行單獨介紹。

rehash

前文有提到,由於是256表變4096表,因此數據每一條都須要通過一次rehash從新作分庫分表的計算。

要說rehash,就不得不先介紹下當前訂單數據的分庫分表策略,訂單ID中冗餘了用戶ID的後四位,經過用戶ID後四位作hash計算肯定庫號和表號。

數據同步過程當中,從舊庫到新庫,須要拿到訂單ID中的用戶ID後四位模4096,肯定數據在新庫中的庫表位置;重新庫到舊庫,則須要用用戶ID後四位模256,肯定數據在舊庫中的庫表位置。

雙向同步時的binlog循環消費問題

想象一下,業務寫一條數據到舊實例的一張表,因而產生了一條binlog;data-sync中間件接到binlog後,將該記錄寫入到新實例,因而在新實例也產生了一條binlog;此時data-sync中間件又接到了該binlog......不斷循環,消息愈來愈多,數據順序也被打亂。

怎麼解決該問題呢?咱們採用數據染色方案,只要可以標識寫入到數據庫中的數據使data-sync中間件寫入而非業務寫入,當下次接收到該binlog數據的時候就不須要進行再次消息流轉。

因此data-sync中間件要求,每一個數據庫實例建立一個事務表,該事務表tb_transaction只有id、tablename、status、create_time、update_time幾個字段,status默認爲0。

再回到上面的問題,業務寫一條數據到舊實例的一張表,因而產生了一條binlog;data-sync中間件接到binlog後,以下操做:

# 開啓事務,用事務保證一下sql的原子性和一致性
start transaction;
set autocommit = 0;
# 更新事務表status=1,標識後面的業務數據開始染色
update tb_transaction set status = 1 where tablename = ${tableName};
# 如下是業務產生binlog
insert xxx;
update xxx;
update xxx;
# 更新事務表status=0,標識後面的業務數據失去染色
update tb_transaction set status = 0 where tablename = ${tableName};
commit;

此時data-sync中間件將上面這些語句打包一塊兒提交到新實例,新實例更新數據後也會生產對應上面語句的binlog;當data-sync中間件再次接收到binlog時,只要判斷遇到tb_transaction表status=1的數據開始,後面的數據都直接丟棄不要,直到遇到status=0時,再繼續接收數據,以此來保證data-sync中間件只會流轉業務產生的消息。

三、 數據校驗

數據校驗模塊由數據校驗服務data-check模塊來實現,主要是基於數據庫層面的數據對比,逐條覈對每個數據字段是否一致,不一致的話會通過配置的校驗規則來進行重試或者報警。

全量校驗

  • 以舊庫爲基準,查詢每一條數據在新庫是否存在,以及個字段是否一致。
  • 以新庫爲基準,查詢每一條數據在舊庫是否存在,以及個字段是否一致。

實時校驗

  • 定時任務每5分鐘校驗,查詢最近5+1分鐘舊庫和新庫更新的數據,作diff。
  • 差別數據進行二次、三次校驗(因爲併發和數據延遲存在),三次校驗都不一樣則報警。

4 、數據修復

通過數據校驗,一旦發現數據不一致,則須要對數據進行修復操做。

數據修復有兩種方案,一種是適用於大範圍的數據不一致,採用重置kafka offset的方式,從新消費數據消息,將有問題的數據進行覆蓋。

4、 灰度切換數據源

1 、總體灰度切流方案

總體灰度方案:SP+用戶緯度來實現,SP緯度:依靠灰度環境切量來作,用戶緯度:依賴用戶ID後四位百分比切流。

灰度切量的過程必定要配合停寫(秒級),爲何要停寫,由於數據同步存在必定延遲(正常毫秒級),而全部業務操做必定要保障都在一個實例上,不然在舊庫中業務剛剛修改了一條數據,此時切換到新庫若是數據尚未同步過來就是舊數據會有數據一致問題。因此步驟應該是:

  1. 先停寫
  2. 觀察數據所有同步完
  3. 在切換數據源
  4. 最後關閉停寫,開始正常業務寫入

2 、切流前準備——ABC驗證

雖然在切流以前,在測試環境進過了大量的測試,可是測試環境畢竟和生產環境不同,生產環境數據庫一旦出問題就多是滅頂之災,雖然上面介紹了數據校驗和數據修復流程,可是把問題攔截在發生以前是作服務穩定性最重要的工做。

所以咱們提出了ABC驗證的概念,灰度環境ABC驗證準備:

  1. 新購買兩套數據庫實例,當前訂單庫爲A,新買的兩套爲分別爲B、C
  2. 配置DTS從A單項同步到B(dts支持同構不須要rehash的數據同步),B作爲舊庫的驗證庫,C庫作爲新庫
  3. 用B和C作爲生產演練驗證
  4. 當B和C演練完成以後,在將A和C配置爲正式的雙向同步

三、 灰度切流步驟

具體灰度方案和數據源切換流程:

  1. 代碼提早配置好兩套數據庫分庫分表規則。
  2. 經過ACM配置灰度比例。
  3. 代碼攔截mybatis請求,根據用戶id後四位取模,和ACM設置中設置的灰度比例比較,將新庫標識經過ThreadLocal傳遞到分庫分表組件。
  4. 判斷當前是否有灰度白名單,如命中將新庫標識經過ThreadLocal傳遞到分庫分表組件。
  5. 分庫分表組件根據ACM配置拿到新分庫的分表規則,進行數據庫讀寫操做。
  6. 切量時會配合ACM配置灰度比例命中的用戶進行停寫。

五 、總結

整個數據遷移過程仍是比較複雜的,時間也不是很充裕(過程當中還穿插着十一全鏈路壓測改造),在有限的時間內集你們之力重複討論挖掘可能存在的問題,而後論證解決方案,不放過任何一個可能出現問題的環節,仍是那句話,把問題攔截在發生以前是作服務穩定性最重要的工做。

過程當中的細節仍是不少的,從數據遷移的準備工做到數據同步測試,從灰度流程肯定到正式生產切換,尤爲是結合業務和數據的特色,有不少須要考慮的細節,文中沒有一一列出。

最終通過近兩個月的緊張工做,無業務代碼侵入、零事故、平穩地完成了擴分庫分表和數據遷移的工做。

做者:開發者小助手_LS

原文連接

本文爲阿里雲原創內容,未經容許不得轉載

相關文章
相關標籤/搜索