 Sharding 遷移機(jī)制:數(shù)據(jù)分片在運(yùn)行中分裂與搬遷的零停機(jī)實踐)
動態(tài) Sharding 遷移機(jī)制數(shù)據(jù)分片在運(yùn)行中分裂與搬遷的零停機(jī)實踐在現(xiàn)代分布式存儲架構(gòu)如 TiKV、CockroachDB、ScyllaDB 或各類自研分布式 KV 引擎中彈性水平擴(kuò)展Elastic Horizontal Scalability是其戰(zhàn)勝傳統(tǒng)單機(jī)數(shù)據(jù)庫的最核心武器。當(dāng)業(yè)務(wù)數(shù)據(jù)從幾百 GB 持續(xù)膨脹到數(shù)百 TB或者某個業(yè)務(wù)分片因為大促秒殺突然涌入天量寫入流量時系統(tǒng)必須具備在不停機(jī)、不阻塞業(yè)務(wù)讀寫的前提下自動將過載的數(shù)據(jù)分片Region / Tablet / Shard進(jìn)行在線物理分裂Split并將其平滑遷移搬遷Rebalance / Migration到低負(fù)載節(jié)點的能力。這個看似簡單的“分片搬遷”操作在底層涉及 Raft 共識日志的原子截斷、跨節(jié)點物理數(shù)據(jù)搬移、兩階段配置變更以及全局路由元數(shù)據(jù)的毫秒級收斂。package sharding import ( context fmt sync ) // Region 分布式連續(xù)鍵值分片區(qū)間 type Region struct { ID uint64 StartKey []byte EndKey []byte RegionEpoch RegionEpoch // 紀(jì)元版本包含 ConfVer 與 Version Peers []*Peer LeaderID uint64 } type RegionEpoch struct { ConfVer uint64 // 副本成員變更版本號 Version uint64 // 分片分裂/合并版本號 } // SplitRegion 在 Raft 狀態(tài)機(jī)中原子執(zhí)行的分裂指令 func (r *Region) ApplySplit(splitKey []byte, newRegionID uint64, newPeerIDs []uint64) (*Region, *Region, error) { if bytesCompare(splitKey, r.StartKey) 0 || bytesCompare(splitKey, r.EndKey) 0 { return nil, nil, fmt.Errorf(非法的 SplitKey超出當(dāng)前分片物理區(qū)間) } // 1. 產(chǎn)生左分片 (保留原 RegionID更新 EndKey 與 Version) leftRegion : Region{ ID: r.ID, StartKey: r.StartKey, EndKey: splitKey, RegionEpoch: RegionEpoch{ ConfVer: r.RegionEpoch.ConfVer, Version: r.RegionEpoch.Version 1, }, Peers: r.Peers, } // 2. 產(chǎn)生右分片 (分配全局全新 RegionID繼承 StartKey 與原 EndKey) rightRegion : Region{ ID: newRegionID, StartKey: splitKey, EndKey: r.EndKey, RegionEpoch: RegionEpoch{ ConfVer: 1, Version: 1, }, Peers: createPeers(newPeerIDs), } return leftRegion, rightRegion, nil }第一步在線分片分裂Split的原子性保障當(dāng)一個 Region 的物理體積突破閾值通常為 96MB 或 512MB時調(diào)度器會觸發(fā)分裂流程尋找最佳分割點Split Key存儲引擎在底層 LSM-Tree 中掃描數(shù)據(jù)分布選取位于正中間的 Key或者基于寫入流量統(tǒng)計選取讀寫頻次最高的分割點在 Raft 復(fù)制組中下發(fā)AdminSplit指令分裂絕對不是外部進(jìn)程強(qiáng)行切文件AdminSplit命令本身被封裝為一條標(biāo)準(zhǔn)的 Raft Log Entry走多數(shù)派共識提交狀態(tài)機(jī)原子生效當(dāng)所有 Follower 在本地 Apply 這條日志時在同一個微秒內(nèi)舊 Region 的鍵值區(qū)間被截斷為 $[StartKey, SplitKey)$新 Region $[SplitKey, EndKey)$ 在本地?zé)o縫誕生。由于整個過程是在內(nèi)存元數(shù)據(jù)中完成指針修改無需任何磁盤物理數(shù)據(jù)拷貝業(yè)務(wù)讀寫耗時抖動小于 1 毫秒[Region 在線分裂與跨節(jié)點調(diào)度遷移流程] 階段 1: 單一 Region 膨脹并本地原子分裂 Node A: [ Region 1: Key A ~ Z (200MB) ] │ (Raft Apply AdminSplit Key M) ▼ Node A: [ Region 1: Key A ~ M (100MB) ] [ Region 2: Key M ~ Z (100MB) ] 階段 2: 跨節(jié)點異步調(diào)度搬遷 (Region 2 遷移至 Node B) Node A (Leader) ──(1. 添加 Node B 為 Learner)──? Node B (異步接收 SST 快照) ──(2. Promote Node B 為 Follower)──? Node B (追平 Raft Log) ──(3. 將 Leader 轉(zhuǎn)移至 Node B)──? Node B (成為新 Leader) ──(4. 移除 Node A 副本)────────? Node A (釋放本地存儲空間)第二步跨節(jié)點物理搬遷Replica Relocation分裂完成后Node A 上同時承載了兩個高負(fù)載分片。全局調(diào)度調(diào)度器如 Placement Driver, PD感知到 Node A 的 IO 水位偏高隨即下發(fā)調(diào)度指令將Region 2搬遷至空閑的 Node B1. 異步快照追趕Snapshot Catch-upNode A 并行向 Node B 發(fā)送全量 LSM-Tree SST 物理文件快照。在此期間業(yè)務(wù)寫入依然正常砸向 Node ANode B 僅作為后臺接收端完全不影響核心交易讀寫。2. 單步成員變更升級數(shù)據(jù)快照傳輸完畢后Node A 通過 Raft 單步成員變更將 Node B 提升為正式的 Follower 節(jié)點并開始增量重放這段時間積壓的 Raft Log。3. 秒級 Leader 轉(zhuǎn)移Leader Transfer當(dāng) Node B 的復(fù)制延遲降為 0 時Node A 主動向 Node B 發(fā)送TransferLeader指令。Node B 在數(shù)毫秒內(nèi)接管 Leader 角色隨后將 Node A 上的冗余副本物理剔除并回收磁盤空間。路由一致性收斂RegionEpoch防御過期請求在分片搬遷期間客戶端緩存的路由表Client Routing Table可能尚未及時刷新依然將屬于Region 2的請求發(fā)送給舊節(jié)點 Node A。系統(tǒng)如何防止讀寫發(fā)生“張冠李戴”答案在于每個分片自帶的RegionEpoch紀(jì)元版本號只要發(fā)生分裂Version自動加 1只要發(fā)生節(jié)點變更ConfVer自動加 1??蛻舳税l(fā)起任何請求時必須在請求頭中攜帶本地緩存的RegionEpoch。如果 Node A 收到請求后發(fā)現(xiàn)自己的紀(jì)元版本已更新EpochNotMatch會立即拒絕請求并返回最新的路由信息StaleCommand響應(yīng)。客戶端捕獲后自動刷新本地緩存并重定向至 Node B整個容錯重試過程在 2~5 毫秒內(nèi)透明完成上層業(yè)務(wù)代碼完全感知不到任何異常。動態(tài) Sharding 機(jī)制是分布式存儲對抗數(shù)據(jù)洪峰與容量瓶頸的終極護(hù)城河。看清這套無鎖分裂與異步搬遷的內(nèi)核實現(xiàn)才能真正駕馭現(xiàn)代云原生分布式數(shù)據(jù)庫的彈性力量。