天天看點

MySQL非主從環境下資料一緻性校驗及修複程式

總會有這樣特殊的需求,比如從阿裡雲rds執行個體遷移到自建mysql執行個體,它的資料傳輸服務實作方式是基于表的批量資料提取,加上binlog訂閱,但強制row模式會導緻pt-table-checksum沒有權限把會話臨時改成statement。另一種需求是,整庫進行字元集轉換:庫表定義都是utf8,但應用連接配接使用了預設的 latin1,要将連接配接字元集和表字元集統一起來,隻能以latin1導出資料,再以utf8導入,這種情況資料一緻性校驗,且不說binlog解析程式不支援statement(如canal),新舊庫本身内容不同,pt-table-checksum 算出的校驗值也會不一樣,失效。

是以才萌生了參考 pt-table-checksum 自己寫了一個:px-table-checksum 。

整體思路是借鑒pt-table-checksum,從源庫批量(即chunk)取出一塊資料如1000行,計算crc32值,同樣的語句在目标庫運作一遍,結果都存入另一個庫,最後檢查對應編号的chunk crc值是否一緻。知道不一緻還不行,得能否快速友善的修複差異,是以繼續根據那些不一緻的chunk,去目标庫和源庫找到不一緻的行,是缺失,還是多餘,還是被修改了,然後生成修複sql,根據訓示是否自動修複。

那麼問題就在于:

如何确定批次,也就是下一個chunk該怎麼取?

我還沒想做到pt-table-checksum那樣,可以根據負載動态調整chunk大小,甚至活躍線程數超過閥值就暫停檢查,上來工作量就太大了。目前每次計算的chunk的行數是固定的,可以配置1000或2000等。

是以就要用到分頁查詢,根據(自增或聯合)主鍵、唯一索引,每次limit 1000後升序取最後一條,作為下一批的起始。是以要分析表上的鍵情況,組合查詢條件。目前僅能檢查有主鍵或唯一是以的表。

如何保證源庫和目标庫,運作的sql一樣?

之前一版是目标庫和源庫,以多線程各自計算chunk,入庫,後來才意識到嚴重的bug:比如同樣是取1000行,如果目标庫少資料,那麼下一個chunk起始就不一樣,比較的結果簡直一塌糊塗。

是以必須保證相同編号的chunk,起點必須相同,是以想到用隊列,存放在源庫跑過的所有校驗sql,模拟pt工具在目标庫重放。考慮到要多線程同時比較多個表,隊列可能吃記憶體過大,于是使用了redis隊列。

直接在資料庫中計算crc32,還是取出資料在記憶體裡計算?

翻了pt-table-checksum的源碼,它是在資料庫裡計算的。但是第一節裡說過,如果目标庫和源庫要使用不同的字元集才能讀出正确的資料,隻能查詢出來之後再比較。是以 px-table-checksum 兩種都支援,隻需指定一個配置項。

同時檢查多個表,源庫sql擠在隊列,目标庫拿出來執行時過了1s,此時源庫那條資料又被修改了一次同步到了目标庫,會導緻計算結果不一緻,實則一緻,怎麼處理

無法處理,是px-table-checksum相比pt-table-checksum最大的缺陷。

但為了盡可能減少此類問題(比如主從延遲也可能會),特意設計了多個redis隊列,目标庫多個檢查線程,即比如同時指定檢查8個表,源庫檢查會有8個線程對應,但可以根據表的寫入情況,配置4個redis隊列(目前是随機入列),10個目标庫檢查線程,來減少不準确因素。

但站在我的角度往往來說,不一緻的資料會被記錄下來,如果不多,人工核對一下;如果較多,就再跑一遍檢查,如果兩次都有同一條資料不一緻,那就有情況了。

如果檢查期間源表資料,變化頻繁,有可能檢查的結果不準确

也就是上面第4點的問題。很明顯,這個程式每個檢查的事務是分開的,不像pt工具能嚴格保證每條檢查sql的事務順序。但有不一緻的資料再排查一下就ok了。實際在我線上使用過程中,99.9%是準确的。

表上必須有主鍵或唯一索引

程式會檢查,如果沒有會退出。

varbinay,blob等二進制字段不支援修複

其實也不是完全不支援,要看怎麼用的。開發如果有把字元先轉成位元組,再存入mysql,這種就不支援修複。是有辦法可以處理,那就是從源庫查時用 <code>hex()</code>函數,修複sql裡面<code>unhex()</code>寫回去。

該python程式基于2.7開發,2.6、3.x上沒有測試。使用前需要安裝 <code>mysqldb</code>和<code>hotqueue</code>:

要比較的表和選項,使用全配置化,即不通過指令行的方式指定(原諒指令行參數使用方式會額外增加代碼量)。

主程式,運作<code>python px-table-checksum.py</code> 執行一緻性檢查,但一定了解下面的配置檔案選項。

配置選項

<code>chunk_size</code>: 每次提取的chunk行數

<code>redis_info</code>: 指定使用redis隊列位址

<code>redis_queue_cnt</code>: redis隊列數量,消費者(目标庫)有一一對應的線程守着隊列

<code>redis_pool_cnt</code>: 生産者(源庫)redis用戶端連接配接池。這個設計是為了緩解gil帶來的問題,把入列端與出列端分開,因為如果表多可能短時間有大量sql入隊列,避免hotqueue争用

<code>calc_crc32_db</code>: true 表示在db裡面計算checksum值,false表示取出chunk資料在python裡面計算。預設給的值是根據連接配接字元集定的。

<code>do_compare</code>: 運作模式

0: 隻提取資料計算,不比較是否一緻。可以在之後在模式2下隻比較

1: 計算,并比較。常用,每次計算之前會删除上一次這個待檢查表的結果,比較的結果隻告訴哪些chunk号不一緻。

2: 不計算,隻從t_checkum結果比較。常用,計算是消耗資料庫資源的,可以隻對已有的checksum計算結果比較不一緻的地方。類似pt工具的<code>--replicate-check-only</code>選項。

<code>gen_datafix</code>:

與<code>do_compare</code>結合使用,為 true 表示對不一緻的chunk找到具體不一緻行,并生成修複sql;為 false 則什麼都不做。

<code>run_datafix</code>:

與<code>gen_datafix</code>結合使用,為 true 表示對生成的修複sql,在目标庫執行。需要謹慎,如果哪一次設定了修複,記得完成後改回false,不然下次檢查另一個表就出意外了,是以特意對這個選項再加了一個确認提示。

<code>db_checksum</code>: 一個字典,指定checksum的結果存到哪裡

配置檔案有示例,必須指定 <code>db_name</code>,表會自動建立。

上面的配置檔案可以認為是用于控制程式的,這個配置檔案是指定要校驗的源庫和目标庫資訊,以及要檢驗哪些表。

<code>tables_check</code>: 字典,指定要檢查哪些表的一緻性,db名為key,多個table名組成清單為value。暫不支援對整個db做檢查,同時比較的表數量不建議超過8個

<code>db_source</code>: 字典,指定源庫的連接配接資訊

<code>db_source</code>: 字典,指定目标庫的連接配接資訊