腾讯游戏数据自愈服务方案简介

                             腾讯游戏数据自愈服务方案简介

 

1. 引言

在正式介绍项目背景之前,让我们先看一组数据:

这是2个灰度的业务,都是Z3服务器,我们先只从时间成本的收益角度来看:

⑴ A业务数据量是330G,数据不一致时通过重做slave需要150分钟左右,而借助pt-table-sync只需要5分钟,速度提升30 倍。

⑵ B业务的量是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年初,彼时我们刚刚引入了数据在线校验方案不久,在某业务每天日 常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参数都是没有用的。

我们修改源码来通过外部参数的动态控制超时行为:

 

⑻ 差异修复

线上某些高星级业务存在超过10G的大表,如果重新进行checksum的话,需要耗费大量的时间,因此我们只根据已经常态话了的checksum表进行修复。

但pt-table-sync在Relicate模式下,差异修复(--where)与其是互斥关系,需要在代码层面进行变更,在测试之后,我们认为这个限制是可以去掉的。

 

5. 小结

 

数据自愈是数据校验的一种延续与补充,是随着后续业务全量铺开而互娱DBA团队不断定制开发演变的数据修复服务方案。在数据自愈的服务化之路上,相信我们会越来越好。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: