1. 簡介
專案位址:https://github.com/seanlook/p...
主從環境下資料一致性校驗經常會用pt-table-checksum 工具,它的原理及實作過程之前寫過一篇文章:生產環境使用pt-table-checksum 檢查MySQL資料一致性。但是DBA工作中還會有些針對兩個表檢查是否一致,而這兩個表之間並沒有主從關係,pt工具是基於binlog把在主庫進行的檢查動作,在從庫重放一遍,此時就不適用了。
總是會有這樣特殊的需求,例如從阿里雲RDS實例遷移到自建mysql實例,它的資料傳輸服務實作方式是基於表的批次資料擷取,加上binlog訂閱,但強制row模式會導致pt -table-checksum沒有權限把會話暫時改成statement。另一種需求是,整庫進行字符集轉換:庫表定義都是utf8,但應用連接使用了默認的latin1,要將連接字符集和表字符集統一起來,只能以latin1導出數據,再以utf8導入,這種情況資料一致性校驗,不說binlog解析程序不支援statement(如canal),新舊庫本身內容不同,pt-table-checksum 算出的校驗值也會不一樣,失效。
所以才萌生了參考 pt-table-checksum 自己寫了一個:px-table-checksum 。
2. 實作方法
整體思路是藉用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個目標庫檢查線程,來減少不準確因素。但站在我的角度往往來說,不一致的數據會被記錄下來,如果不多,人工核對一下;如果較多,就再跑一遍檢查,如果兩次都有同一數據不一致,那就有情況了。
3. 限制
如果檢查期間源表數據,變化頻繁,有可能檢查的結果不準確也就是上面第4點的問題。很明顯,這個程序每個檢查的事務是分開的,不像pt工具能嚴格保證每個檢查sql的事務順序。但有不一致的數據再檢查一下就ok了。實際在我線上使用過程中,99.9%是準確的。
表上必須有主鍵或唯一索引程式會檢查,如果沒有會退出。
varbinay,blob等二進位字段不支援修復
其實也不是完全不支持,要看怎麼用的。開發如果有把字元先轉成字節,再存入mysql,這種就不支援修復。是有辦法可以處理,就是從來源庫查時用 hex()函數,修復sql裡面unhex()寫回去。
4. 使用說明
該python程式基於2.7開發,2.6、3.x上沒有測試。使用前需要安裝 MySQLdb和hotqueue:
$ sudo pip install MySQL-python hotqueue
要比較的表格和選項,使用全配置化,即不透過命令列的方式指定(原諒額外命令列參數使用方式會增加代碼量)。
4.1 px-table-checksum.py
主程序,執行python px-table-checksum.py 執行一致性檢查,但一定了解下面的設定檔選項。
4.2 settings_checksum.py
設定選項
CHUNK_SIZE: 每次擷取的chunk行數
REDIS_INFO: 指定使用NTredis(佇列位址
REDIS_QUED3IS_INFO: 指定使用NTredis(每個佇列地址)守著佇列
REDIS_POOL_CNT: 生產者(來源庫)redis客戶端連線池。這個設計是為了緩解GIL帶來的問題,把入列端與出列端分開,因為如果表多可能短時間有大量sql入隊列,避免hotqueue爭用
CALC_CRC32_DB: True 表示在db裡面計算checksum值,False表示取出chunk資料在python裡面計算。預設給的值是根據連接字元集定的。
DO_COMPARE: 運作模式
0: 只擷取資料計算,不比較是否一致。可以在之後在模式2下只比較
1: 計算,並比較。常用,每次計算前會刪除上一次這個待檢查表的結果,比較的結果只告訴哪些chunk號不一致。
2: 不計算,只從t_checkum結果比較。常用,計算是消耗資料庫資源的,可以只對已有的checksum計算結果比較不一致的地方。類似pt工具的--replicate-check-only選項。
GEN_DATAFIX:
與DO_COMPARE結合使用,為 True 表示對不一致的chunk找到具體不一致行,並產生修復sql;為 False 則什麼都不做。
RUN_DATAFIX:
與GEN_DATAFIX結合使用,為 True 表示對產生的修復sql,在目標庫執行。需要謹慎,如果哪一次設定了修復,記得完成後改回False,不然下次檢查另一個表就出意外了,所以特意對這個選項再加了一個確認提示。
DB_CHECKSUM: 一個字典,指定checksum的結果存到哪裡
設定檔有範例,必須指定 db_name,表會自動建立。
4.3 settings_cs_tables.py
上面的設定檔可以認為是用於控製程式的,這個設定檔是指定要校驗的來源函式庫和目標函式庫信息,以及要檢驗哪些資料表。
TABLES_CHECK: 字典,指定要檢查哪些表的一致性,db名為key,多個table名組成列表為value。暫不支援對整個db做檢查,同時比較的表格數量不建議超過8個
DB_SOURCE: 字典,指定來源庫的連接資訊
DB_SOURCE: 字典,指定目標庫的連接資訊