天天看點

256變4096:分庫分表擴容如何實作平滑資料遷移?

256變4096:分庫分表擴容如何實作平滑資料遷移?

作者 | 亮言

來源 | 阿裡技術公衆号

一 背景

2020年,筆者負責的一個高德打車彈外訂單系統進行了一次擴分庫分表和資料庫遷移。該訂單系統整體部署在阿裡雲上,服務使用阿裡雲ECS部署,資料庫采用阿裡雲RDS,配置中心基于阿裡雲ACM自研,資料同步基于阿裡雲DTS自研以及自研分庫分表元件、分布式ID元件等等。

此次進行擴分庫分表的背景是,原4執行個體4庫、每個庫64張表一共256張表,部分單表已超千萬量級,按目前每日單量量級,一年内單表會達到上億條記錄,單表資料量過大會帶來資料庫性能問題。

注:【彈内彈外】彈是指彈性計算,彈内與彈外其實是指兩套獨立的彈性計算網絡環境。彈内主要是指部署在阿裡生産網的彈性計算環境,最早是基于原有淘寶技術建構的,主要用于支撐淘寶業務。彈外主要是指部署在阿裡公有雲的彈性計算環境,支撐了阿裡雲計算業務。

二 容量規劃

1 目前分庫分表情況

4執行個體(16C/64G/3T SSD),4庫(每個執行個體一個庫),每庫64張表,共256張表。

通過RDS背景一鍵診斷功能,來計算表空間使用情況(這裡拿測試環境資料庫舉例)。

256變4096:分庫分表擴容如何實作平滑資料遷移?

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遷移至新執行個體,遷移成本較小。

256變4096:分庫分表擴容如何實作平滑資料遷移?
表數

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

庫數

32個庫,每個庫128張表。未來可最大擴容到32個執行個體,無需rehash,隻需要遷移資料。

256變4096:分庫分表擴容如何實作平滑資料遷移?

阿裡雲RDS規格和價格一覽

三 資料遷移

因擴分庫分表涉及到rehash過程(256表變4096表),而阿裡雲DTS隻支援同構庫資料遷移,是以我們基于DTS的binlog轉kafka能力自研了資料同步中間件。

整個資料遷移工作包括:前期準備、資料同步環節(曆史資料全量同步、增量資料實時同步、rehash)、資料校驗環節(全量校驗、實時校驗、校驗規則配置)、資料修複工具等。

256變4096:分庫分表擴容如何實作平滑資料遷移?

1 準備工作

唯一業務ID

在進行資料同步前,需要先梳理所有表的唯一業務ID,隻有确定了唯一業務ID才能實作資料的同步操作。

需要注意的是:

  • 業務中是否有使用資料庫自增ID做為業務ID使用的,如果有需要業務先進行改造,還好訂單業務裡沒有。
  • 每個表是否都有唯一索引,這個在梳理的過程中發現有幾張表沒有唯一索引。

一旦表中沒有唯一索引,就會在資料同步過程中造成資料重複的風險,是以我們先将沒有唯一索引的表根據業務場景增加唯一索引(有可能是聯合唯一索引)。

在這裡順便提一下,阿裡雲DTS做同構資料遷移,使用的是資料庫自增ID做為唯一ID使用的,這種情況如果做雙向同步,會造成資料覆寫的問題。解決方案也有,之前我們的做法是,新舊實體采用自增ID單雙号解決,保證新舊執行個體的自增ID不會出現沖突就行。因為這次我們使用的自研雙向同步元件,這個問題這裡不細聊。

分表規則梳理

分表規則不同決定着rehash和資料校驗的不同。需逐個表梳理是使用者ID緯度分表還是非使用者ID緯度分表、是否隻分庫不分表、是否不分庫不分表等等。

2 資料同步

資料同步整體方案見下圖,資料同步基于binlog,獨立的中間服務做同步,對業務代碼無侵入。

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

256變4096:分庫分表擴容如何實作平滑資料遷移?
曆史資料全量同步

單獨一個服務,使用遊标的方式從舊庫分批select資料,經過rehash後批量插入(batch insert)到新庫,此處需要配置jdbc連接配接串參數rewriteBatchedStatements=true才能使批處理操作生效。

另外特别需要注意的是,曆史資料也會存在不斷的更新,如果先開啟曆史資料全量同步,則剛同步完成的資料有可能不是最新的。是以這裡的做法是,先開啟增量資料單向同步(從舊庫到新庫),此時隻是開啟積壓kafka消息并不會真正消費;然後在開始曆史資料全量同步,當曆史全量資料同步完成後,在開啟消費kafka消息進行增量資料同步(提高全量同步效率減少積壓也是關鍵的一環),這樣來保證遷移資料過程中的資料一緻。

增量資料實時同步

增量資料同步考慮到灰階切流穩定性、容災和可復原能力,采用實時雙向同步方案,切流過程中一旦新庫出現穩定性問題或者新庫出現資料一緻問題,可快速復原切回舊庫,保證資料庫的穩定和資料可靠。

增量資料實時同步采用基于阿裡雲DTS的資料訂閱自研資料同步元件data-sync實作,主要方案是DTS資料訂閱能力會自動将被訂閱的資料庫binlog轉為kafka,data-sync元件訂閱kafka消息、将消息進行過濾、合并、分組、rehash、拆表、批量insert/update,最後再送出offset等一系列操作,最終完成資料同步工作。

256變4096:分庫分表擴容如何實作平滑資料遷移?
  • 過濾循環消息:需要過濾掉循環同步的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計算确定庫号和表号。

256變4096:分庫分表擴容如何實作平滑資料遷移?

資料同步過程中,從舊庫到新庫,需要拿到訂單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中間件隻會流轉業務産生的消息。

256變4096:分庫分表擴容如何實作平滑資料遷移?

3 資料校驗

資料校驗子產品由資料校驗服務data-check子產品來實作,主要是基于資料庫層面的資料對比,逐條核對每一個資料字段是否一緻,不一緻的話會經過配置的校驗規則來進行重試或者報警。

全量校驗
  • 以舊庫為基準,查詢每一條資料在新庫是否存在,以及個字段是否一緻。
  • 以新庫為基準,查詢每一條資料在舊庫是否存在,以及個字段是否一緻。
實時校驗
  • 定時任務每5分鐘校驗,查詢最近5+1分鐘舊庫和新庫更新的資料,做diff。
  • 差異資料進行二次、三次校驗(由于并發和資料延遲存在),三次校驗都不同則報警。
256變4096:分庫分表擴容如何實作平滑資料遷移?

4 資料修複

經過資料校驗,一旦發現資料不一緻,則需要對資料進行修複操作。

資料修複有兩種方案,一種是适用于大範圍的資料不一緻,采用重置kafka offset的方式,重新消費資料消息,将有問題的資料進行覆寫。

256變4096:分庫分表擴容如何實作平滑資料遷移?

四 灰階切換資料源

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配置為正式的雙向同步
256變4096:分庫分表擴容如何實作平滑資料遷移?

3 灰階切流步驟

具體灰階方案和資料源切換流程:

  1. 代碼提前配置好兩套資料庫分庫分表規則。
  2. 通過ACM配置灰階比例。
  3. 代碼攔截mybatis請求,根據使用者id後四位取模,和ACM設定中設定的灰階比例比較,将新庫辨別通過ThreadLocal傳遞到分庫分表元件。
  4. 判斷目前是否有灰階白名單,如命中将新庫辨別通過ThreadLocal傳遞到分庫分表元件。
  5. 分庫分表元件根據ACM配置拿到新分庫的分表規則,進行資料庫讀寫操作。
  6. 切量時會配合ACM配置灰階比例命中的使用者進行停寫。
256變4096:分庫分表擴容如何實作平滑資料遷移?

五 總結

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

過程中的細節還是很多的,從資料遷移的準備工作到資料同步測試,從灰階流程确定到正式生産切換,尤其是結合業務和資料的特點,有很多需要考慮的細節,文中沒有一一列出。

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