記一次mysql遷移的方案與遇到的坑| 8月更文挑戰

背景

因爲歷史業務數據採用mysql來存儲的,其中有一張操做記錄表video_log,每當用戶建立、更新或者審覈人員審覈的時候,對應的video_log就會加一條日誌,這個log表只有insert,可想而知,1個video對應多條log,一天10w video,平均統計一個video對應5條log,那麼一天50w的log, 一個月50 * 30 = 1500w條記錄, 一年就是1500 * 12 = 1.8億。目前線上已經有2億多的數據了,因爲log自己不面向C端,用於查詢問題的,因此能夠忍受一點的延遲。 可是隨着時間的積累,必然會愈來愈慢,影響效率,因而提出改造。mysql

image.png

方案一:老數據備份

因爲log自己不是最關鍵的數據,可是也要求實時性高(用於實時查詢問題),因此一開始的想法是核心的基礎存儲仍是保持不變,較老的數據遷移出去,畢竟忽然去查詢一年前的操做記錄的機率很小,若是忽然要查,能夠走離線。設計的話,咱們只須要一個定時腳本,天天在凌晨4點左右(業務低峯期)抽數據。抽出的數據能夠上報到一些離線存儲(通常公司都有基於hive的數倉之類的),這樣就能夠保持線上的video_log的數據不會一直增加。redis

image.png

方案二:分表

分表也是一種解決方案,相對方案一的好處就是,全部的數據都支持實時查,缺點是代碼要改造了。sql

  1. 首先確認sharding key,由於video_log是和video綁定的,因此天然而然選擇video_id做爲咱們的sharding key
  2. 按什麼分表肯定了,接下來確認下分多少張表。先定個小目標,支撐3年。每張表最大數據量爲1個億(因爲咱們的查詢簡單),按照上面的統計,咱們3年大概:3*1.8=5.4億,那麼大概須要5.4/1≈6張表。

image.png 接下來就是改造代碼了,得解決新老數據讀寫的問題。數據庫

  1. 新數據的插入直接插入新表
  2. 因爲log表只有insert,因此不存在update、delete這些操做,不須要考慮這些場景。
  3. 分表後,一個video的log存在兩張表(老表和新表),因此臨時兩張表都查,而後作個合併
  4. 同步老數據到新表中
  5. 下線讀取老表的代碼

image.png

方案三:遷移至tidb

方案二的缺點比較明顯,3年後咋辦,繼續拆表?感受始終有個歷史債在那。因而咱們的目光定位到了tidb,tidb是分佈式的數據庫,接入了tidb,咱們就無需關心分表了,這些tidb都幫咱們作了,它會本身作節點的擴容。因爲是分佈式的,因此tidb的主鍵是無序的,這點很重要。
整個流程大概分爲如下4個步驟:安全

  1. 先雙寫(記錄下剛開始雙寫時的mysql的id,在此id前的確定都是老數據)
  2. 同步老數據(經過第一步記錄的id來區分)
  3. 切讀(老數據同步完了)
  4. 下雙寫

image.png

重點說下同步老數據遇到的坑

遷移至tidb,看似很簡單,其實在job腳本這裏隱藏着幾個坑。markdown

  1. 要考慮萬一job中途斷了,從新啓動咋辦,撇開重頭跑數據的時間成本,已經同步的數據從新跑會重複,還要考慮重複數據的問題。解決重複數據的問題,能夠對老表新加一個字段標識是否已同步,每次同步完,更新下字段。缺點:線上數據大,加個字段不太安全,可能形成線上阻塞。
  2. 既然加個字段很差,那就用現有的主鍵id作約束,把主鍵id也同步過去,這樣就算腳本重啓,從頭開始跑的,也由於相同的主健已經插入過,那麼就會報錯跳過。看似很完美,然而tidb是分佈式的,主鍵id不是連續的,那麼可能出現這樣一種狀況。正常的業務數據插入tidb,tidb分配的主鍵id和mysql同步的主鍵id重複,那麼不論是誰,最後插入的那一條確定是失敗的。

image.png

最終同步腳本方案

綜合考慮數據的重複性,job重啓效率性,和整個同步的效率性,我大概作出如下方案:分佈式

  1. 任務分批提高效率:首先根據處理能力和預期完成時間,先對老數據進行分批,大概分了10批,10個job去跑不一樣批次的數據,互不干擾,且每次批量更新100條。
  2. 記錄狀態,重啓自動恢復到斷點:每次同步數據後記錄下當前同步的位置(redis記錄下當前的id),就算重啓也能夠從redis裏拿到以前的更新位置,接着更新。
  3. 避免主鍵衝突:同步除了主鍵以外的全部字段(不一樣步主鍵)

最終經過方案三的四個切換步驟+高效率的同步腳本平穩的完成了數據的遷移ide

相關文章
相關標籤/搜索