MySQL crash-safe replication

 MySQL数据库的成功离不开其replicaiton,相对于Oracle DG和Microsoft SQL Server Log Shipping来讲,其简单易上手,基本上1,2分钟内根据手册就能完成环境的搭建。然而,随着使用的深刻,replication自身的问题会慢慢显露,其中非crash safe的特性使得许多DBA感到头疼,甚至不能理解其所发问题的缘由。简单来讲,crash-safe replication是指当master/slave任何一个节点发生宕机等意外状况下,服务器重启后master/slave的数据依然可以保证一致性。mysql

 
crash-safe master相对比较简单,只要使用事务的存储引擎,而且正确的配置就能达到crash safe的效果。对于最为常见的InnoDB存储引擎而言,只需在配置文件中进行以下的设置:

 

sync_binlog=1
innodb-flush-log-at-trx-commit=1
 
MySQL 5.6版本以前存在一个bug,即当启用上述两个参数时,会使得InnoDB存储引擎的group commit失效,从而致使在写密集的环境中性能的急剧降低。所以,DBA在性能和数据一致性中作了妥协,一般将参数innodb-flush-log-at-trx-commit设置为2,而这就致使了master再也不是crash safe的,主从数据可能会不一致。MariaDB真正解决了该问题,所以不少分支版本,好比Percona,Facebook MySQL,InnoSQL都将MariaDB的group commit方案移植到了本身的分支中,从而解决group commit失效的问题。
 
crash-safe slave的状况就有些复杂,而这多是DBA更为常见的问题。例如slave不断的报1062错误,或者发现主从数据不一致(特别是表没有主键的状况)。而这时DBA的选择一般也很无奈,基本就是全库重建了。因此说,当你有运维超过200台以上的MySQL服务器的经验时,就会发现这是一个很大的问题。
 
致使不能实现crash-safe slave有两方面的缘由,即replication中的SQL thread和IO thread。首先来看SQL thread,其主要完成两个操做:
  • 运行relay log中对应的事务信息
  • 更新relay-info.log文件
更新relay-info.log文件是为了记录已经执行relay log中的位置,当slave重启后能够根据这个位置继续同步relay log。可是,这里用户会发现这两个操做不是在一个事务中,一个是数据库操做,一个是文件操做,所以不能达到原子的效果。此外,MySQL数据库默认对于文件relay-info.log是写入到操做系统缓存,所以在发生宕机时可能致使大量的已更新位置的丢失,从而致使重复执行SQL语句,最终的现象就是主从数据不一致。MySQL 5.5新增了参数sync_relay_log_info,能够控制每次事务更新relay-info.log后就进行一次fdatasync操做,这加剧了系统负担,并且即便这样也可能存在最后一个事务丢失的状况。
 
早在MySQL 4.0时Google就发布过补丁解决过该问题( https://code.google.com/p/google-mysql-tools/wiki/TransactionalReplication),其在每次InnoDB存储引擎提交时,记录二进制日志的位置信息到事务系统段的段头。当slave重启后将保存的这部分的信息从新生成relay-info.log文件。Percona也采用了这种方式,并经过参数innodb_recovery_update_relay_log来控制是否在启动时替换relay-info.log文件。
 
MySQL 5.6采用了另外一种方法,就是将relay-info.log的信息保存在InnoDB的事务表中,这时两个操做都是数据库操做,在一个事务中就能获得原子性。例如对于slave的日志回放,其过程为:

 

BEGIN;
apply log event;
apply log event; 
UPDATE mysql.slave_relay_log_info
SET Master_log_pos = Exec_Master_Log_Pos,
Master_log_name = Relay_Master_Log_File,
Relay_log_name = Relay_Log_File,
Relay_log_pos = Relay_Log_Pos;
COMMIT
这样的处理方式解决更新relay-info.log的原子性问题,可是这是最终完美的解决方案吗?很惋惜,仍是存在一些缺陷。 能够看到因为最后表 slave_relay_log_info的更新会锁住记录,从而致使slave上的事务提交都是串行的。虽然MySQL 5.6支持并行复制,可是因为串行更新表 slave_relay_log_info,再次致使group commit失效。所以经过--log-slave-updates再立级联replication的话,性能又会受限。MariaDB正在解决该问题,很是有可能在MariaDB 10 GA版本中见到完美的解决方案。
 
IO thread用于同步master上的二进制日志,可是其在crash时依然会致使数据不一致的状况发生。IO thread将收到的二进制日志写入到relay log,每一个二进制日志由多个log event组成,因此每接受到一个log event就须要更新master-info.log。和relay-info.log同样,其也是写入操做系统缓存,参数sync_master_info能够控制fdatasync的时间。因为IO thread的更新不能像SQL thread同样进行放到一个事务进行原子操做,所以其是对数据一致性会产生影响,设想一个log event传送到了relay log中两次的情形。
 
不过好在从MySQL 5.5版本开始提供了参数relay_log_recovery,当发生crash致使重连master时,其不根据master-info.log的信息进行重连,而是根据relay-info中执行到master的位置信息从新开始拉master上的日志数据(不过须要确保日志依然存在于master上,不然就。。。)
 
crash-safe是运维人员不能忽略的一点,不然DBA将忙于处理这些异常情况致使的slave服务中止的情形。MySQL 5.6虽然提供了crash safe的解决方案,但依然存在一些不完美的可能性。因此,小伙伴们,让咱们期待MariaDB 10 GA版本的发布吧。
 
相关文章
相关标签/搜索