面試官:系統(tǒng)重構(gòu),舊庫(kù)數(shù)據(jù)怎樣平滑地遷到新庫(kù)?
在多年的架構(gòu)評(píng)審和技術(shù)面試中,秀才發(fā)現(xiàn)“數(shù)據(jù)遷移”是一個(gè)能精準(zhǔn)衡量工程師技術(shù)深度與廣度的問(wèn)題,但凡你簡(jiǎn)歷上寫(xiě)了系統(tǒng)重構(gòu),這個(gè)問(wèn)題基本就是必問(wèn)題。當(dāng)然許多求職者的簡(jiǎn)歷中也不乏“系統(tǒng)重構(gòu)”、“數(shù)據(jù)庫(kù)分庫(kù)分表”這些亮眼項(xiàng)目,但是對(duì)其中的技術(shù)細(xì)節(jié)理解往往不夠深入,最常見(jiàn)的答案就是一個(gè)“停機(jī)遷移”。
不得不說(shuō),停機(jī)遷移確實(shí)是一種方案,但它更像是一種技術(shù)資源或方案設(shè)計(jì)不足時(shí)的妥協(xié)?,F(xiàn)在的互聯(lián)網(wǎng)業(yè)務(wù)基本都是高性能要求,長(zhǎng)時(shí)間的服務(wù)中斷是不可接受的。因此,“如何設(shè)計(jì)并實(shí)施一套完善的不停機(jī)數(shù)據(jù)遷移方案,確保過(guò)程平滑、數(shù)據(jù)零丟失且最終一致”,已成為架構(gòu)師乃至高級(jí)工程師必須掌握的核心能力。
今天,秀才就結(jié)合過(guò)往的經(jīng)驗(yàn),系統(tǒng)性地介紹一些數(shù)據(jù)遷移過(guò)程中應(yīng)該要注意的點(diǎn),希望能為你在工作和面試中做方案設(shè)計(jì)時(shí)提供一些參考。
1. 工具選型
存量數(shù)據(jù)遷移是整個(gè)數(shù)據(jù)遷移的第一步,針對(duì)MySQL,主要有兩個(gè)主流的遷移工具:mysqldum和XtraBackup。
- mysqldump:這是 MySQL 官方提供的命令行工具,用于數(shù)據(jù)庫(kù)的備份與恢復(fù)。它能將數(shù)據(jù)庫(kù)的結(jié)構(gòu)、數(shù)據(jù)和關(guān)系導(dǎo)出為一系列的 SQL 語(yǔ)句。這是一種邏輯備份,備份產(chǎn)物是可讀的 SQL 腳本。
- XtraBackup:這是一款由 Percona 公司開(kāi)發(fā)的高性能物理備份工具。它直接作用于 InnoDB 存儲(chǔ)引擎的底層文件,支持增量備份與恢復(fù),并且在備份過(guò)程中對(duì)線(xiàn)上業(yè)務(wù)影響極小。這是一種物理備份,可以理解為直接拷貝數(shù)據(jù)庫(kù)的數(shù)據(jù)文件。
下表是mysqldump和XtraBackup的一個(gè)綜合對(duì)比。
因此,在方案設(shè)計(jì)之初,我們就需要基對(duì)業(yè)務(wù)數(shù)量級(jí)、業(yè)務(wù)對(duì)性能抖動(dòng)的容忍度以及可用的遷移窗口進(jìn)行確認(rèn),最初正確的工具選擇,如果實(shí)在面試過(guò)程中,也需要向面試官展現(xiàn)這個(gè)考慮,突出自己思考問(wèn)題的全面性。
2. 關(guān)鍵參數(shù)
除了工具,數(shù)據(jù)庫(kù)自身的一個(gè)關(guān)鍵參數(shù)也必須要考慮到,那就是innodb_autoinc_lock_mode。這個(gè)參數(shù)控制著InnoDB引擎下自增主鍵的生成策略,它直接關(guān)系到后續(xù)“雙寫(xiě)”階段數(shù)據(jù)能否保持一致。
innodb_autoinc_lock_mode有三種工作模式,我們需要清楚當(dāng)前環(huán)境它的配置值。
- 模式0(傳統(tǒng)鎖):采用傳統(tǒng)的表級(jí)自增鎖。任何插入操作(無(wú)論是單條還是批量)都會(huì)鎖住整張表直到語(yǔ)句結(jié)束,并發(fā)性能最差,但能絕對(duì)保證任何情況下生成的自增ID都是連續(xù)的。
- 模式1(連續(xù)鎖,默認(rèn)):這是默認(rèn)模式,做出了優(yōu)化。對(duì)于INSERT ... VALUES這樣的簡(jiǎn)單插入,鎖在分配完主鍵后即釋放,大大提升了并發(fā)性;但對(duì)于INSERT ... SELECT這類(lèi)無(wú)法預(yù)知插入行數(shù)的語(yǔ)句,仍會(huì)退化為表鎖,以保證ID的連續(xù)性。
- 模式2(交錯(cuò)鎖):最為激進(jìn),所有類(lèi)型的插入都在申請(qǐng)主鍵后立刻釋放鎖,并發(fā)性能最好。但代價(jià)是,在并發(fā)執(zhí)行多個(gè)插入語(yǔ)句時(shí),不同語(yǔ)句獲得的ID會(huì)相互交錯(cuò),可能導(dǎo)致單個(gè)批量插入語(yǔ)句中產(chǎn)生不連續(xù)的主鍵ID。
在面試中提及你對(duì)這個(gè)參數(shù)的關(guān)注,并解釋它如何影響后續(xù)雙寫(xiě)方案中對(duì)主鍵的處理,會(huì)立刻讓面試官感受到你對(duì)數(shù)據(jù)庫(kù)底層原理的熟悉程度。比如可以補(bǔ)充道:“在雙寫(xiě)開(kāi)始前,我們必須對(duì)代碼庫(kù)進(jìn)行審查,識(shí)別出所有批量插入的業(yè)務(wù)場(chǎng)景。特別是對(duì)于INSERT ... SELECT或循環(huán)單條INSERT的邏輯,需要在innodb_autoinc_lock_mode=2的環(huán)境下進(jìn)行重點(diǎn)測(cè)試,評(píng)估其主鍵亂序的風(fēng)險(xiǎn),必要時(shí)需進(jìn)行業(yè)務(wù)代碼改造,統(tǒng)一為INSERT...VALUES的形式,以確保數(shù)據(jù)一致性。”
3. 環(huán)境確認(rèn)
除了上面說(shuō)的遷移工具的選型和關(guān)鍵參數(shù)的設(shè)置外,要設(shè)計(jì)一個(gè)好的遷移方案,我們還需要確認(rèn)一系列環(huán)境因素,比如:
- Binlog模式:確認(rèn)公司的binlog格式是否為ROW模式,STATEMENT模式在高并發(fā)下可能因執(zhí)行順序的不確定性導(dǎo)致主從不一致,而ROW模式會(huì)記錄每一行數(shù)據(jù)的變更,是保證數(shù)據(jù)精確同步的基礎(chǔ)。
- 數(shù)據(jù)庫(kù)規(guī)范:了解公司是否有統(tǒng)一的數(shù)據(jù)庫(kù)設(shè)計(jì)規(guī)范,比如,是否所有表都強(qiáng)制包含update_time字段?刪除操作是采用軟刪除還是物理刪除?這些規(guī)范將直接影響增量校驗(yàn)方案的選擇。
- ORM框架特性:了解項(xiàng)目中所使用的ORM框架,是否提供了AOP(切面)、Interceptor或Hook等機(jī)制,有的話(huà)可以方便后續(xù)雙寫(xiě)實(shí)現(xiàn)。
4. 面試實(shí)戰(zhàn)指南
前面的的三個(gè)環(huán)節(jié)都是準(zhǔn)備工作,接下來(lái)就要進(jìn)入到核心的方案設(shè)計(jì)階段了。
一個(gè)成熟的不停機(jī)遷移方案,其核心思想在于平滑過(guò)渡與風(fēng)險(xiǎn)可控。整個(gè)過(guò)程可以總結(jié)為三個(gè)階段:存量復(fù)制 -> 增量同步 -> 驗(yàn)證切換。
三個(gè)階段過(guò)程中的詳細(xì)步驟可以簡(jiǎn)化為下圖中的四個(gè)關(guān)鍵階段切換:
1
4.1 第一階段:存量數(shù)據(jù)遷移
數(shù)據(jù)遷移的第一步就是將源表中已有的存量數(shù)據(jù)導(dǎo)入到新表中,這里就要用到我們前面介紹的Mysql遷移工具了。前面也分析過(guò)這兩種工具對(duì)比,在大多數(shù)情況下,如果我們的業(yè)務(wù)量不大的話(huà),使用 mysqldump 就可以了。雖然說(shuō)他遷移的速度較慢。而這,恰恰給你提供了展示技術(shù)深度、突出亮點(diǎn)的機(jī)會(huì)。在面試時(shí),你可以這樣表述:
“在存量數(shù)據(jù)遷移階段,我們選擇使用mysqldump 工具從源表實(shí)時(shí)導(dǎo)出數(shù)據(jù)。mysqldump 作為一個(gè)邏輯備份工具,優(yōu)點(diǎn)是簡(jiǎn)單易用,但缺點(diǎn)也很明顯,即在數(shù)據(jù)量巨大時(shí),導(dǎo)出和導(dǎo)入性能都比較差。因此,我針對(duì)性地做了一系列優(yōu)化來(lái)提升其性能?!?/p>
- 導(dǎo)出端,我們開(kāi)啟了extended-insert選項(xiàng),將多行數(shù)據(jù)合并為單條INSERT語(yǔ)句,顯著減少了SQL文件體積和網(wǎng)絡(luò)傳輸壓力。
- 在導(dǎo)入端,我們采取了組合措施:
- 臨時(shí)關(guān)閉了binlog記錄(SET SQL_LOG_BIN=0),避免導(dǎo)入過(guò)程產(chǎn)生不必要的日志開(kāi)銷(xiāo)。
- 放寬了redo log的刷盤(pán)時(shí)機(jī),將innodb_flush_log_at_trx_commit設(shè)置為2。
2
4.2 第二階段:增量同步
存量數(shù)據(jù)開(kāi)啟完之后,到這里就要進(jìn)入到整個(gè)方案中最復(fù)雜、也最關(guān)鍵的核心的增量同步環(huán)節(jié)了。因?yàn)闃I(yè)務(wù)并不是停止的,時(shí)時(shí)刻刻都在發(fā)生寫(xiě)入操作,會(huì)影響到表里的數(shù)據(jù)。所以這里在這個(gè)階段,就需要開(kāi)啟雙寫(xiě),即所有的寫(xiě)操作在源表和新表中都要寫(xiě)入一次。
4.2.1 非侵入式的雙寫(xiě)機(jī)制
實(shí)現(xiàn)雙寫(xiě)的過(guò)程中,業(yè)務(wù)系統(tǒng)要同時(shí)寫(xiě)入兩個(gè)數(shù)據(jù)源,最忌諱的就是侵入式地修改每一處業(yè)務(wù)邏輯代碼,不僅工作量巨大,而且極易引入新的BUG。因此,一個(gè)優(yōu)雅的非侵入式方案是架構(gòu)師的首選。我們可以利用所使用ORM框架的AOP(面向切面編程)能力,例如GORM的ConnPool接口或MyBatis的Interceptor,在數(shù)據(jù)訪(fǎng)問(wèn)的底層實(shí)現(xiàn)一個(gè)雙寫(xiě)代理層。
3
這個(gè)代理層能夠靜默地?cái)r截所有數(shù)據(jù)庫(kù)的寫(xiě)操作(INSERT/UPDATE/DELETE),并將其復(fù)制一份,分別在源表和新表上執(zhí)行。更關(guān)鍵的是,這個(gè)雙寫(xiě)組件還可以是動(dòng)態(tài)可控的。我們需要為其設(shè)計(jì)一個(gè)開(kāi)關(guān),能夠通過(guò)配置中心或API接口,實(shí)時(shí)地調(diào)整其工作策略,例如是從“源表優(yōu)先”切換到“目標(biāo)表優(yōu)先”,或是徹底關(guān)閉某一方的寫(xiě)入。這種動(dòng)態(tài)可控性,是整個(gè)遷移方案風(fēng)險(xiǎn)管理的核心。
4
在面試時(shí),你可以結(jié)合自己熟悉的框架來(lái)具體闡述,以 Go 語(yǔ)言的 GORM 為例:
“為了在 GORM 中實(shí)現(xiàn)雙寫(xiě),我最初考慮過(guò)使用它的 Hook 機(jī)制,比如 BeforeSave, BeforeDelete。但這種方式要求我為每個(gè)數(shù)據(jù)模型都注冊(cè)一遍 Hook,維護(hù)起來(lái)比較繁瑣。經(jīng)過(guò)深入研究 GORM 的源碼,我發(fā)現(xiàn)可以從更底層的 ConnPool 接口入手。我通過(guò)裝飾器模式,封裝了源表和目標(biāo)表兩個(gè)數(shù)據(jù)源的 ConnPool,在執(zhí)行 SQL 語(yǔ)句時(shí),根據(jù)一個(gè)動(dòng)態(tài)的標(biāo)記位來(lái)決定具體的雙寫(xiě)邏輯,是先寫(xiě)源表還是先寫(xiě)目標(biāo)表?!?/p>
這里可以給出一個(gè)偽代碼,讓面試官有一個(gè)直觀的感受:
// DoubleWritePool 結(jié)構(gòu)體封裝了源和目標(biāo)兩個(gè)數(shù)據(jù)源
// 并根據(jù)當(dāng)前模式管理雙寫(xiě)邏輯
type DoubleWritePool struct {
source *sql.DB // 源數(shù)據(jù)庫(kù)連接
target *sql.DB // 目標(biāo)數(shù)據(jù)庫(kù)連接
mode string// 雙寫(xiě)模式,如 "source_first", "target_first"
}
// ExecContext 攔截?cái)?shù)據(jù)庫(kù)寫(xiě)操作
func (p *DoubleWritePool) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
switch p.mode {
case"source_first":
// 優(yōu)先寫(xiě)源表
res, err := p.source.ExecContext(ctx, query, args...)
if err != nil {
// 源表寫(xiě)入失敗,操作終止,不寫(xiě)目標(biāo)表
returnnil, err
}
// 源表成功后,嘗試寫(xiě)入目標(biāo)表,此處的錯(cuò)誤可降級(jí)處理(如只記錄日志)
// 后續(xù)依賴(lài)獨(dú)立的校驗(yàn)修復(fù)機(jī)制來(lái)保證最終一致性
go p.target.ExecContext(ctx, query, args...)
return res, nil
case"target_first":
// 切換到目標(biāo)表優(yōu)先的邏輯
// ...
default:
// 默認(rèn)或其他模式
return p.source.ExecContext(ctx, query, args...)
}
}4.2.2 雙寫(xiě)階段的核心挑戰(zhàn)
- 數(shù)據(jù)一致性問(wèn)題
只要涉及到雙寫(xiě)機(jī)制,就會(huì)帶來(lái)兩個(gè)必須解決的經(jīng)典問(wèn)題。首先是數(shù)據(jù)一致性問(wèn)題。
面試官很可能會(huì)追問(wèn):“在雙寫(xiě)過(guò)程中,如果寫(xiě)源表成功了,但寫(xiě)目標(biāo)表失敗了,怎么辦?”
我們的核心應(yīng)對(duì)原則是,在雙寫(xiě)階段,允許這種短暫的不一致存在,只保證最終一致性,所以以?xún)?yōu)先寫(xiě)入的庫(kù)為準(zhǔn)。然后靠我們后續(xù)部署的獨(dú)立的數(shù)據(jù)校驗(yàn)和修復(fù)工具,周期性的掃描和修復(fù)這類(lèi)不一致數(shù)據(jù)。
接著,你可以展示你更深層次的思考,提出一個(gè)“基于消息隊(duì)列的異步補(bǔ)償方案”的方案,然后分析出這個(gè)方案的缺陷,最終放棄,突出方案選型能力。
我們也曾評(píng)估過(guò)基于消息隊(duì)列的異步補(bǔ)償方案,但由于難以精確還原UPDATE...WHERE這類(lèi)語(yǔ)句的實(shí)際影響范圍(例如,一個(gè)UPDATE users SET level = 5 WHERE score > 10000語(yǔ)句,我們無(wú)法在消息中得知究竟哪些用戶(hù)的level被更新了),導(dǎo)致補(bǔ)償邏輯異常復(fù)雜,最終選擇了更為通用和穩(wěn)健的校驗(yàn)修復(fù)方案。
5
- 自增主鍵沖突問(wèn)題
其次是更麻煩的的自增主鍵沖突問(wèn)題。因?yàn)殡p寫(xiě)其實(shí)是要保證兩邊數(shù)據(jù)庫(kù)的數(shù)據(jù)嚴(yán)格一致。那么如果源表使用了自增主鍵,新表的數(shù)據(jù)也要包含自增主鍵。但是在高并發(fā)場(chǎng)景下,如果簡(jiǎn)單地依賴(lài)兩邊數(shù)據(jù)庫(kù)各自生成主鍵,極易因時(shí)序問(wèn)題導(dǎo)致數(shù)據(jù)錯(cuò)亂。比如,線(xiàn)程A和線(xiàn)程B幾乎同時(shí)插入數(shù)據(jù),可能出現(xiàn)A在源表拿到ID 100,B在源表拿到ID 101,但寫(xiě)入目標(biāo)表時(shí),B的操作先于A完成,導(dǎo)致B在目標(biāo)表拿到ID 100,A拿到ID 101,數(shù)據(jù)就此張冠李戴了。
6
正確的解決方案是,在寫(xiě)入目標(biāo)表時(shí),必須顯式地指定主鍵值。這意味著,在源表插入成功后,程序需要獲取到數(shù)據(jù)庫(kù)為其生成的自增ID,并將這個(gè)ID一并用于目標(biāo)表的插入操作,從而從根本上確保兩邊主鍵的絕對(duì)一致。
7
4.2.3 增量校驗(yàn)與修復(fù)機(jī)制
在雙寫(xiě)運(yùn)行的同時(shí),我們需要一個(gè)持續(xù)的任務(wù)機(jī)制來(lái)發(fā)現(xiàn)并修復(fù)兩邊的不一致數(shù)據(jù)。這里主要有兩個(gè)方案:基于時(shí)間戳輪詢(xún)和利用 Binlog。相比之下,Binlog 方案技術(shù)含量更高,也更可靠,是面試時(shí)的首選答案。
4.2.3.1 基于時(shí)間戳輪詢(xún)
我們可以啟動(dòng)一個(gè)定時(shí)任務(wù),持續(xù)不斷地輪詢(xún)?cè)幢碇衭pdate_time這樣的字段,找到變更的記錄,然后與新表進(jìn)行比對(duì)。一旦發(fā)現(xiàn)不一致,就以原表的數(shù)據(jù)為準(zhǔn),覆蓋新表。
// last_time 記錄上次掃描到的最大時(shí)間戳
var last_time = get_initial_timestamp()
for {
// 1. 根據(jù)上次的時(shí)間戳,查詢(xún)?cè)幢碇邪l(fā)生變更的行
// SELECT * FROM source_table WHERE update_time > last_time ORDER BY update_time ASC
rows := findUpdatedRows(last_time)
for _, row := range rows {
// 2. 根據(jù)主鍵,在目標(biāo)表中查找對(duì)應(yīng)的行
tgtRow := findTgt(row.id)
// 3. 比較兩行數(shù)據(jù)是否一致
if !isEqual(row, tgtRow) {
// 4. 如果不一致,用源表數(shù)據(jù)修復(fù)目標(biāo)表數(shù)據(jù)
fixTargetTable(row)
}
}
// 5. 更新時(shí)間戳,用于下一次掃描
iflen(rows) > 0 {
last_time = getMaxUpdateTime(rows)
}
// 6. 短暫休眠,避免空輪詢(xún)
sleep(1 * time.Second)
}當(dāng)然,這個(gè)方案強(qiáng)依賴(lài)于良好的表設(shè)計(jì)規(guī)范,即所有表都有update_time字段,且刪除操作為軟刪除。如果存在物理刪除,此方案會(huì)失效,因?yàn)樵幢碛涗泟h除后,基于update_time的輪詢(xún)將無(wú)法捕獲到這次刪除操作,導(dǎo)致新表殘留臟數(shù)據(jù)。
8
針對(duì)這里的第二個(gè)前提,其實(shí)正好可以作為你的一個(gè)亮點(diǎn)。你可以等待面試官追問(wèn)“那如果業(yè)務(wù)方?jīng)]有實(shí)現(xiàn)軟刪除,就是用的物理刪除,有沒(méi)有什么方法可以實(shí)現(xiàn)一致性呢”,這里你就可以展現(xiàn)出你的亮點(diǎn)方案了。
你可以這樣回答:“對(duì)于物理刪除的場(chǎng)景,在這個(gè)方案的基礎(chǔ)上還需要一個(gè)補(bǔ)救措施:增加一個(gè)低頻的反向全量校驗(yàn),即從目標(biāo)表出發(fā),反查源表。如果在源表中找不到對(duì)應(yīng)的記錄,就說(shuō)明該數(shù)據(jù)已被刪除,此時(shí)再清理目標(biāo)表的冗余數(shù)據(jù)?!?/p>
9
4.2.3.2 基于Binlog實(shí)時(shí)監(jiān)聽(tīng)
一個(gè)更高級(jí)、更實(shí)時(shí)的方案是基于Binlog的實(shí)時(shí)監(jiān)聽(tīng),這也是是面試時(shí)的首選答案。其實(shí)Mysql的 Binlog (ROW 模式) 本身就包含了變更后的數(shù)據(jù),為什么不直接利用呢?在面試的時(shí)候,你可以這樣介紹這個(gè)方案。
“在監(jiān)聽(tīng) Binlog 的基礎(chǔ)上,我們做了進(jìn)一步優(yōu)化。當(dāng)收到一條 Binlog 事件后,我們直接將 Binlog 中包含的數(shù)據(jù)內(nèi)容(即變更后的數(shù)據(jù)鏡像)與目標(biāo)表的實(shí)時(shí)數(shù)據(jù)進(jìn)行比較。如果兩者不一致,我們才會(huì)拿著主鍵去查詢(xún)?cè)幢淼淖钚聰?shù)據(jù),用這個(gè)最新數(shù)據(jù)來(lái)覆蓋目標(biāo)表。”
10
在實(shí)際應(yīng)用中,有一個(gè)關(guān)鍵點(diǎn)需要注意:當(dāng)檢測(cè)到數(shù)據(jù)不一致時(shí),修復(fù)邏輯應(yīng)當(dāng)回到源表重新查詢(xún)最新數(shù)據(jù),而不是直接依賴(lài) Binlog 的內(nèi)容。原因在于,Binlog 中的事件有可能滯后于真實(shí)狀態(tài)——你拿到的可能是一條“舊操作”,而新表的數(shù)據(jù)此時(shí)已經(jīng)被后續(xù)更新覆蓋。如果直接使用 Binlog,可能會(huì)把較新的正確數(shù)據(jù)覆蓋掉;而通過(guò)源表查詢(xún),可以確保修復(fù)時(shí)拿到的始終是最新版本。
4.2.4 主從同步延遲
在上一步介紹了增量同步過(guò)程中必須的一個(gè)環(huán)節(jié),增量校驗(yàn)與修復(fù)。如果是單庫(kù)模式下都好說(shuō),上述方案沒(méi)什么大的問(wèn)題,在實(shí)際部署中,為了保證系統(tǒng)的高可用,一般數(shù)據(jù)庫(kù)都采用的是主從部署模式,一般讀取數(shù)據(jù)都是讀取的從庫(kù),這里就可能出現(xiàn)主從同步延遲問(wèn)題了。不管是新表的主從延遲,還是原表的主從延遲,都可能導(dǎo)致我們拿到‘過(guò)期’的數(shù)據(jù),從而做出錯(cuò)誤的判斷。主從同步延遲也是面試官極易挑戰(zhàn)的一個(gè)點(diǎn)。
11
解決方案也很簡(jiǎn)單,就是再做一次校驗(yàn),雙重校驗(yàn)。面試的時(shí)候你可以這樣說(shuō):
“為了解決 主從延遲 帶來(lái)的數(shù)據(jù)一致性問(wèn)題,同時(shí)避免過(guò)度消耗主庫(kù)資源,我采用了一種“兩階段校驗(yàn)”的思路。在第一次校驗(yàn)時(shí),程序優(yōu)先從從庫(kù)讀取并進(jìn)行比對(duì),如果結(jié)果一致,就無(wú)需再繼續(xù)處理。只有在從庫(kù)校驗(yàn)未通過(guò)的情況下,才會(huì)觸發(fā)第二步:直接訪(fǎng)問(wèn)主庫(kù),拿到最新的數(shù)據(jù)再做比對(duì)。而在數(shù)據(jù)修復(fù)環(huán)節(jié),我們始終以主庫(kù)的結(jié)果為最終依據(jù)。
這個(gè)方案背后的邏輯是:主從延遲與真實(shí)不一致通常是低概率事件,因此真正需要訪(fǎng)問(wèn)主庫(kù)的情況也很少。這樣既能保證一致性,又避免了對(duì)主庫(kù)的頻繁壓力,實(shí)現(xiàn)了可靠性與性能之間的平衡。
”
4.3 第三階段:驗(yàn)證切換
經(jīng)過(guò)一段時(shí)間的增量同步,并且校驗(yàn)新舊數(shù)據(jù)源的差異趨于零,且系統(tǒng)穩(wěn)定運(yùn)行時(shí),我們就可以進(jìn)入最后的驗(yàn)證切換階段了。
直接從“源表讀寫(xiě)”切換到“新表讀寫(xiě)”,風(fēng)險(xiǎn)是巨大的。一旦切換后新庫(kù)出現(xiàn)問(wèn)題,源庫(kù)的數(shù)據(jù)已經(jīng)不再是最新,回滾將變得異常困難,甚至丟失數(shù)據(jù)。
12
所以這里又是一個(gè)面試的亮點(diǎn)展示,這里你可以提出一個(gè)更穩(wěn)妥的方案。在做最后一步數(shù)據(jù)源切換的時(shí)候,你可以這樣說(shuō)。
“為了確保遷移過(guò)程的安全性,我們?cè)诜桨钢性O(shè)計(jì)了一個(gè)中間過(guò)渡階段。具體做法是先調(diào)整雙寫(xiě)策略,改為“新表讀寫(xiě)”,同時(shí)所有的寫(xiě)請(qǐng)求,源表也會(huì)寫(xiě)一份。這一操作相當(dāng)于一次“預(yù)發(fā)布”,為我們提供了一個(gè)可觀察、可回滾的緩沖期。在這段時(shí)間內(nèi),新表承擔(dān)起權(quán)威數(shù)據(jù)源的角色,而源表依舊保持同步寫(xiě)入作為備用。如果新表在此期間出現(xiàn)性能下降或潛在缺陷等異常情況,我們能夠立即將業(yè)務(wù)切換回源表,保證業(yè)務(wù)穩(wěn)定運(yùn)行。正是這份可回退的保障,使得整個(gè)方案真正具備了穩(wěn)妥性?!?/p>
13
通過(guò)這個(gè)方案就能保證從源表切換到新表的過(guò)程中有一個(gè)安全的過(guò)渡,即使新表還有問(wèn)題也不怕,因?yàn)槲覀冇袀浞輽C(jī)制。
5. 小結(jié)
回顧整個(gè)遷移過(guò)程,要做到平滑的數(shù)據(jù)遷移遠(yuǎn)非執(zhí)行一個(gè)腳本那么簡(jiǎn)單。從前期的工具選型、參數(shù)調(diào)優(yōu),到中期的雙寫(xiě)與校驗(yàn),再到最后的驗(yàn)證切換,每一步都伴隨著技術(shù)細(xì)節(jié)。其核心思想在于通過(guò)雙寫(xiě)機(jī)制確保業(yè)務(wù)的連續(xù)性,利用校驗(yàn)修復(fù)工具保證數(shù)據(jù)的最終一致性,并設(shè)計(jì)可灰度、可回滾的切換步驟來(lái)將風(fēng)險(xiǎn)降至最低。換句話(huà)說(shuō),遷移的難點(diǎn)從不在于“能不能完成”,而在于“如何安全、平滑地完成”。如果在面試中能夠完整地講清楚這套思路,不僅能體現(xiàn)技術(shù)深度,也能彰顯你作為架構(gòu)師對(duì)復(fù)雜系統(tǒng)的全局把控力。






























