資料庫 主從一致性檢查和修復
http://blog.csdn.net/dba_waterbin/article/details/43608587
騰訊遊戲資料自愈服務方案簡介
1. 引言
在正式介紹專案背景之前,讓我們先看一組資料:
這是2個灰度的業務,都是Z3伺服器,我們先只從時間成本的收益角度來看:
⑴ 左一業務資料量是330G,資料不一致時通過重做slave需要150分鐘左右,而藉助pt-table-sync只需要5分鐘,速度提升30 倍。
⑵ 右一業務的量是93G,通過sync工具花費3分鐘,而如果重做slave要35分鐘,速度提升12倍。
引入這組資料意在指明,整個過程不僅解放了DBA的雙手,也符合”零運維”的趨勢,資料自愈將是互娛DBA
2. 背景
MySQL資料庫基於binlog的資料同步方案,絕大部分情況下能保證主備資料完全一致,但某些異常情況下,例如開發使用了unsafe statement的SQL(如帶limit)及硬體故障等,都可能導致主備資料不一致。DBA通過checksum工具可以發現這些不一致的情況,但往往需要重新做一個熱備來恢復主備資料一致性,並且此過程可能需要10小時以上,而實際情況上主備資料通常僅有少量不一致,線上修復這些資料差異可以更高效地完成一個數據一致的熱備。
3. 收益
重做熱備是我們目前首選的修復方案,但有時候沒有備用的新機子卻讓修復步伐戛然而止,而如果沒有修復,倘若master
我們在引言中也道出了sync工具相比傳統的熱備在時間上的收益,但除了這個,資料自愈服務的收益包括但不限於:
▼ 業務資料更安全,恢復熱備時間變短
▼ 減少伺服器資源,避免重做熱備的機器申請
▼ 提升DBA做熱備的處理效率
▼ 降低溝通成本,保證業務持續穩定執行
4. 資料自愈解決方案
我們從開源社群引入了Percona公司的pt-table-sync,該專案從2007
下面我們對該工具的實現細節以及互娛DBA團隊在此基礎上進行定製開發的部分內容進行討論。
⑴ 流程圖
Pt-table-sync有2種修復模式:replicate模式和非replicate模式,上圖是replicate的,這也是我們所推薦,原因會在下文說明。
這裡,我們先對上圖作下簡要介紹
① 對每一個chunk,再校驗時加上for update鎖,一旦獲得鎖,就記錄下當前主庫的show master status值。以我測試機的案例:
SELECT /*water2.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `name`, CONCAT(ISNULL(`name`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `water2`.`t` FORCE INDEX (`PRIMARY`) WHERE (1=1) AND ((1=1)) FOR UPDATE |
SHOW MASTER STATUS |
② 在從庫上執行select master_pos_wait()函式,等待從庫SQL執行緒執行到show master status得到的位置,以此保證,主從上關於這個chunk的內容均不再改變。
SELECT MASTER_POS_WAIT('binlog3306.000014', 139672350, 60) |
③ 對這個chunk執行checksum,然後與主庫的checksum進行比較
DR: SELECT /*water2.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `name`, CONCAT(ISNULL(`name`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `water2`.`t` FORCE INDEX (`PRIMARY`) WHERE (1=1) AND ((1=1)) LOCK IN SHARE MODE DB: SELECT /*water2.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `name`, CONCAT(ISNULL(`name`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `water2`.`t` FORCE INDEX (`PRIMARY`) WHERE (1=1) AND ((1=1)) FOR UPDATE |
④ 如果checksum相同,說明主從資料一致,就繼續下一個chunk
⑤ 如果checksum不同,說明該chunk有不一致,深入chunk內部,逐行計算checksum並比較,如果發現某行不一致,則標記下來,繼續檢測剩餘行,直到這個chunk結束。
⑥ 直到修復該chunk所有不一致的行,繼續檢查和修復下一個chunk
⑵ checksum演算法
有2個層次的校驗演算法,一是塊級,一是行級。
① 單行資料checksum值計算
檢查表結構並獲取每一列的資料型別,把所有資料型別都轉化為字串,然後用concat_ws()函式進行拼接,由此計算出該行的checksum值,checksum預設採用crc32計算。下面是一個例子:
SELECT /*rows in chunk*/ `id`, `name`, CRC32(CONCAT_WS('#', `id`, `name`, CONCAT(ISNULL(`name`)))) AS __crc FROM `water2`.`t` FORCE INDEX (`PRIMARY`) WHERE (1=1) AND (1=1) ORDER BY `id` FOR UPDATE; |
② 資料塊checksum值的計算
智慧分析表上的索引,然後把表的資料split成若干個chunk,計算的時候以chunk為單位,可以理解為把chunk內的所有行的資料拼接起來,再計算crc32的值,即得到該chunk的checksum值。下面是一個例子:
SELECT /*water2.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `name`, CONCAT(ISNULL(`name`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `water2`.`t` FORCE INDEX (`PRIMARY`) WHERE (1=1) AND ((1=1)) FOR UPDATE; |
⑶ 資料精確切分
鎖生命週期是我們一直很注重的問題。
2010年底和2011年初,彼時我們剛剛引入了資料線上校驗方案不久,在企鵝gamedb每天日常checksum時,DB有鎖資料情況,導致TTC大量資料無法寫入的告警,mk-table-checksum 1.2.8 版本資料分片方法不合理,當表資料分佈非常不均勻時,資料切片會導致某些塊包含的資料行過大,其中innodb行鎖實現為索引間隙鎖,checksum過程會鎖住chunk的資料。
felixliang修改mk-table-checksum的原始碼,增加自定義recursive_dynamic_calculate_chunks函式,實現了精確資料切片控制,該函式內部呼叫explain檢視每個chunk包含的rows,確保每個chunk中的行數不多於chunk-size引數設定的大小。當每個chunk切分均勻後,chunk資料校驗在1秒左右完成,鎖資料情況幾乎很難感知得到。
這個方案已經在騰訊遊戲日常資料校驗穩定執行3年多,我們相信該切分演算法比官方預設的切分而言更加健壯、同時也更加安全。而pt-table-sync同樣會對不一致的表切分,由此我們遷移了該切分演算法到pt-table-sync裡面,這也是”前人栽樹,後人乘涼”的好處。
⑷ 延遲控制
這是為什麼我們要採用replicate模式的必要性。
非replicate模式只是普通的執行緒請求行為,跳過MySQL的Replication機制,本身不做延遲控制,然而,pt-table-sync在修復過程中是不能容忍從庫延遲,如果延遲太多,pt-table-sync會長期持有對chunk的for update鎖,然後等待從庫的master_pos_wait執行完畢或超時,從庫延遲越大,等待過程就越長,主庫加鎖的時間就越長,對線上影響就越大。但是如果不等待,這個工具是無法驗證主從資料是否一致。
但是,replicate模式下,補償SQL是通過master上執行,生成binlog,然後全量同步到slave,再在slave上回放,從而達到資料修復的效果。而資料安全是生命線,改錯了master就得回檔,就不是鬧著玩的。改錯了slave不會對玩家有影響,對DBA是個保護。
那麼,我們既想要replicate模式帶來的好處,又想避免補償SQL在master執行帶來的風險不可控因素,該如何做?
互娛DBA團隊通過修改get_change_dbh函式讓最後一步生成的補償SQL不走binlog,直接在slave上跑,從而避免在master上修復帶來的資料安全問題。
⑸ 普通索引
pt-table-sync採用replace into來修復主從不一致,必須保證被replace的表有主鍵或唯一鍵,否則replace into退化成insert into,而insert是不能在master上執行,因為那樣只會使不一致擴散。
對發現主從不一致的行,採用replace into 語句,在主庫上執行一遍以生成該行的全量binlog,並同步到從庫,這會以主庫資料為基礎修復從庫:
① 對於主庫有的行而從庫沒有的行,採用replace在主庫上插入(必須不能是insert)
② 對於從庫有的行而主庫沒有的行,通過在主庫執行delete來刪除
因為現網環境比較複雜,我們不能保證騰訊全球遊戲每個表上都有主鍵或唯一鍵。因此,互娛DBA團隊通過原始碼修改,當發現主庫上的表沒有唯一鍵時不會致命退出,而是繼續執行,但採用了另外一種演算法,即在slave上採用delete + replace 方式修補。
⑹ 平臺相容
我們先看mk-table-checksum與pt-table-checksum表結構。
⒈ mk-table-checksum
Ⅱ. Pt-table-checksum
是的,這2個表結構是不一樣的,而pt-table-sync的replicate模式是直接讀取pt-table-checksum的表,但是我們:
① mk-table-checksum修復版已經整合到我們的GCS平臺
② 保留pt-table-sync最新版本的功能,避免引入mk-table-sync老版本bug
基於上述2個理由,我們修改了find_replication_differences函式,讓pt-table-sync相容了mk-table-checksum,這樣既能相容現有平臺的功能,又可以用得上pt-table-sync最新版本的新特性。
⑺ 超時控制
在我們測試過程中,發現官方提供的超時控制--wait引數有”bug”
① 對於非replicate模式,wait引數無效
② 對於replicate模式,wait只能是0和非0,當非0時,任何值都是一樣的
下面是我們的測試現象
所以,無論哪種模式,wait引數都是沒有用的。
我們修改原始碼來通過外部引數的動態控制超時行為:
5. 小結
資料自愈是資料校驗的一種延續與補充,是隨著後續業務全量鋪開而互娛DBA團隊不斷定製開發演變的資料修復服務方案。在資料自愈的服務化之路上,相信我們會越來越好。
http://blog.csdn.net/dba_waterbin/article/details/43608587
騰訊遊戲資料自愈服務方案簡介
1. 引言
在正式介紹專案背景之前,讓我們先看一組資料:
這是2個灰度的業務,都是Z3伺服器,我們先只從時間成本的收益角度來看:
⑴ 左一業務資料量是330G,資料不一致時通過重做slave需要150分鐘左右,而藉助pt-table-sync只需要5分鐘,速度提升30 倍。
⑵ 右一業務的量是93G,通過sync工具花費3分鐘,而如果重做slave要35分鐘,速度提升12倍。
引入這組資料意在指明,整個過程不僅解放了DBA的雙手,也符合”零運維”的趨勢,資料自愈將是互娛DBA團隊在未來提供的服務之一。
2. 背景
MySQL資料庫基於binlog的資料同步方案,絕大部分情況下能保證主備資料完全一致,但某些異常情況下,例如開發使用了unsafe statement的SQL(如帶limit)及硬體故障等,都可能導致主備資料不一致。DBA通過checksum工具可以發現這些不一致的情況,但往往需要重新做一個熱備來恢復主備資料一致性,並且此過程可能需要10小時以上,而實際情況上主備資料通常僅有少量不一致,線上修復這些資料差異可以更高效地完成一個數據一致的熱備。
3. 收益
重做熱備是我們目前首選的修復方案,但有時候沒有備用的新機子卻讓修復步伐戛然而止,而如果沒有修復,倘若master故障,由於資料不一致,切換到slave是存在資料丟失的風險,那麼又不得不執行修復,DBA就需要評估以前slave上的連線切換到master是否會影響master的效能......這樣DBA的工作量就無形中翻倍了。
我們在引言中也道出了sync工具相比傳統的熱備在時間上的收益,但除了這個,資料自愈服務的收益包括但不限於:
▼ 業務資料更安全,恢復熱備時間變短
▼ 減少伺服器資源,避免重做熱備的機器申請
▼ 提升DBA做熱備的處理效率
▼ 降低溝通成本,保證業務持續穩定執行
4. 資料自愈解決方案
我們從開源社群引入了Percona公司的pt-table-sync,該專案從2007年啟動。
下面我們對該工具的實現細節以及互娛DBA團隊在此基礎上進行定製開發的部分內容進行討論。
⑴ 流程圖
Pt-table-sync有2種修復模式:replicate模式和非replicate模式,上圖是replicate的,這也是我們所推薦,原因會在下文說明。
這裡,我們先對上圖作下簡要介紹
① 對每一個chunk,再校驗時加上for update鎖,一旦獲得鎖,就記錄下當前主庫的show master status值。以我測試機的案例:
SELECT /*water2.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `name`, CONCAT(ISNULL(`name`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `water2`.`t` FORCE INDEX (`PRIMARY`) WHERE (1=1) AND ((1=1)) FOR UPDATE |
SHOW MASTER STATUS |
② 在從庫上執行select master_pos_wait()函式,等待從庫SQL執行緒執行到show master status得到的位置,以此保證,主從上關於這個chunk的內容均不再改變。
SELECT MASTER_POS_WAIT('binlog3306.000014', 139672350, 60) |
③ 對這個chunk執行checksum,然後與主庫的checksum進行比較
DR: SELECT /*water2.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `name`, CONCAT(ISNULL(`name`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `water2`.`t` FORCE INDEX (`PRIMARY`) WHERE (1=1) AND ((1=1)) LOCK IN SHARE MODE DB: SELECT /*water2.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `name`, CONCAT(ISNULL(`name`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `water2`.`t` FORCE INDEX (`PRIMARY`) WHERE (1=1) AND ((1=1)) FOR UPDATE |
④ 如果checksum相同,說明主從資料一致,就繼續下一個chunk
⑤ 如果checksum不同,說明該chunk有不一致,深入chunk內部,逐行計算checksum並比較,如果發現某行不一致,則標記下來,繼續檢測剩餘行,直到這個chunk結束。
⑥ 直到修復該chunk所有不一致的行,繼續檢查和修復下一個chunk
⑵ checksum演算法
有2個層次的校驗演算法,一是塊級,一是行級。
① 單行資料checksum值計算
檢查表結構並獲取每一列的資料型別,把所有資料型別都轉化為字串,然後用concat_ws()函式進行拼接,由此計算出該行的checksum值,checksum預設採用crc32計算。下面是一個例子:
SELECT /*rows in chunk*/ `id`, `name`, CRC32(CONCAT_WS('#', `id`, `name`, CONCAT(ISNULL(`name`)))) AS __crc FROM `water2`.`t` FORCE INDEX (`PRIMARY`) WHERE (1=1) AND (1=1) ORDER BY `id` FOR UPDATE; |
② 資料塊checksum值的計算
智慧分析表上的索引,然後把表的資料split成若干個chunk,計算的時候以chunk為單位,可以理解為把chunk內的所有行的資料拼接起來,再計算crc32的值,即得到該chunk的checksum值。下面是一個例子:
SELECT /*water2.t:1/1*/ 0 AS chunk_num, COUNT(*) AS cnt, COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `name`, CONCAT(ISNULL(`name`)))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM `water2`.`t` FORCE INDEX (`PRIMARY`) WHERE (1=1) AND ((1=1)) FOR UPDATE; |
⑶ 資料精確切分
鎖生命週期是我們一直很注重的問題。
2010年底和2011年初,彼時我們剛剛引入了資料線上校驗方案不久,在企鵝gamedb每天日常checksum時,DB有鎖資料情況,導致TTC大量資料無法寫入的告警,mk-table-checksum 1.2.8 版本資料分片方法不合理,當表資料分佈非常不均勻時,資料切片會導致某些塊包含的資料行過大,其中innodb行鎖實現為索引間隙鎖,checksum過程會鎖住chunk的資料。
felixliang修改mk-table-checksum的原始碼,增加自定義recursive_dynamic_calculate_chunks函式,實現了精確資料切片控制,該函式內部呼叫explain檢視每個chunk包含的rows,確保每個chunk中的行數不多於chunk-size引數設定的大小。當每個chunk切分均勻後,chunk資料校驗在1秒左右完成,鎖資料情況幾乎很難感知得到。
這個方案已經在騰訊遊戲日常資料校驗穩定執行3年多,我們相信該切分演算法比官方預設的切分而言更加健壯、同時也更加安全。而pt-table-sync同樣會對不一致的表切分,由此我們遷移了該切分演算法到pt-table-sync裡面,這也是”前人栽樹,後人乘涼”的好處。
⑷ 延遲控制
這是為什麼我們要採用replicate模式的必要性。
非replicate模式只是普通的執行緒請求行為,跳過MySQL的Replication機制,本身不做延遲控制,然而,pt-table-sync在修復過程中是不能容忍從庫延遲,如果延遲太多,pt-table-sync會長期持有對chunk的for update鎖,然後等待從庫的master_pos_wait執行完畢或超時,從庫延遲越大,等待過程就越長,主庫加鎖的時間就越長,對線上影響就越大。但是如果不等待,這個工具是無法驗證主從資料是否一致。
但是,replicate模式下,補償SQL是通過master上執行,生成binlog,然後全量同步到slave,再在slave上回放,從而達到資料修復的效果。而資料安全是生命線,改錯了master就得回檔,就不是鬧著玩的。改錯了slave不會對玩家有影響,對DBA是個保護。
那麼,我們既想要replicate模式帶來的好處,又想避免補償SQL在master執行帶來的風險不可控因素,該如何做?
互娛DBA團隊通過修改get_change_dbh函式讓最後一步生成的補償SQL不走binlog,直接在slave上跑,從而避免在master上修復帶來的資料安全問題。
⑸ 普通索引
pt-table-sync採用replace into來修復主從不一致,必須保證被replace的表有主鍵或唯一鍵,否則replace into退化成insert into,而insert是不能在master上執行,因為那樣只會使不一致擴散。
對發現主從不一致的行,採用replace into 語句,在主庫上執行一遍以生成該行的全量binlog,並同步到從庫,這會以主庫資料為基礎修復從庫:
① 對於主庫有的行而從庫沒有的行,採用replace在主庫上插入(必須不能是insert)
② 對於從庫有的行而主庫沒有的行,通過在主庫執行delete來刪除
因為現網環境比較複雜,我們不能保證騰訊全球遊戲每個表上都有主鍵或唯一鍵。因此,互娛DBA團隊通過原始碼修改,當發現主庫上的表沒有唯一鍵時不會致命退出,而是繼續執行,但採用了另外一種演算法,即在slave上採用delete + replace 方式修補。
⑹ 平臺相容
我們先看mk-table-checksum與pt-table-checksum表結構。
⒈ mk-table-checksum
Ⅱ. Pt-table-checksum
是的,這2個表結構是不一樣的,而pt-table-sync的replicate模式是直接讀取pt-table-checksum的表,但是我們:
① mk-table-checksum修復版已經整合到我們的GCS平臺
② 保留pt-table-sync最新版本的功能,避免引入mk-table-sync老版本bug
基於上述2個理由,我們修改了find_replication_differences函式,讓pt-table-sync相容了mk-table-checksum,這樣既能相容現有平臺的功能,又可以用得上pt-table-sync最新版本的新特性。
⑺ 超時控制
在我們測試過程中,發現官方提供的超時控制--wait引數有”bug”
① 對於非replicate模式,wait引數無效
② 對於replicate模式,wait只能是0和非0,當非0時,任何值都是一樣的
下面是我們的測試現象