数据库内核月报

数据库内核月报 - 2018 / 06

MariaDB · 特性分析 · 基于GTID的复制分析

Author: 勉仁

Replication为数据库提供了高可用性和可扩展性解决方案,基于GTID的Replication能够更好的处理复制重连和切换Server复制。本文将主要分析MariaDB基于GTID的Replication设计与实现,并与MySQL做对比。

MariaDB的GTID

MariaDB GTID定义与实现

GTID(Global transaction ID)表示作为一个执行单元的event group,可以理解为一个“事务”(虽然也包含的非事务的DML和DDL操作)。当event group从Master复制到Slave执行后,GTID是被保留的,用来在有复制关系的server中识别对应的GTID。

使用GTID可以方便的一个Slave切换到从另一个Master Server复制,因为GTID在有复制关系的Server中是被唯一标识的。而且执行的GTID信息记录在事务表中,可以做到slave的状态是crash-safe。

在MariaDB中,GTID由三部分组成,三部分之间用’-‘分隔,例如0-161002-1:

struct rpl_gtid
{
  uint32 domain_id;
  uint32 server_id;
  uint64 seq_no;
};

第一个是无符号32位整数的domain_id,在多源复制、多主拓扑复制中每个会写入的Master通常需要设置不同的Domain_id。

第二个是无符号32位整数的server_id,集群中每个server需要设置唯一的server id,标识最初写入binlog的server,用来避免循环复制。

第三个是服务号64位整数的seq_no,seq_no在每个写入server产生binlog中是递增的。

本文为分析MariaDB复制,创建了互为主备的两个server。

server1: server_id 161002 domain_id 0

server2: server_id 161003 domain_id 0

均配置log_slave_updates

GTID相关参数

与MySQL的不同

MySQL的GTID是server_uuid和transaction_id组成,没有用于多源复制的domain_id概念。

GTID = source_id:transaction_id

在MariaDB中GTID相关执行信息都是用position,即单个GTID信息表示。在MySQL中使用了表达信息更多的GTID Set,用GTID集合来表示已经执行的GTID信息。例如某个server上gtid_executed为’528c2958-6966-11e8-8cd1-7cd30ac42730:1-9’。

DBA可以在MySQL上RESET MASTER后,设置gtid_purged来重置gtid_executed信息,gtid_purged也是GTID集合。在MariaDB中,RESET MASTER可以通过gtid_binlog_state来重置gtid信息,也可以动态设置gtid_slave_pos。

MySQL可以设置gtid_next来确定session中下一个执行的GTID。但在session中不可以设置server_id,MySQL这是一个全局变量。在MariaDB中,可以在session上设置gtid_domain_id、server_id、gtid_seq_no来指定下一个GTID信息。

可以说MariaDB与MySQL GTID相比多了domain的概念,在server已执行GTID信息表示上,MariaDB使用的是最后一个执行的GTID来表示,而MySQL使用的是GTID Set。

复制位点指定

MariaDB除了使用指定master binlog文件名和位点的方式开始replication外,还支持通过master_user_gtid来使master自动寻找binlog位点开始同步。

master_user_gtid的指定支持current_pos和slave_pos两种方式,其实位点分别使用gtid_current_pos和gtid_slave_pos。

MariaDB在start_slave_threads的时候会获取要使用的GTID位点到mi->gtid_current_pos中,当使用current_pos方式时候,和对应变量意义一样,会包含binlog写入的GTID信息。当使用slave_pos就只用复制来的slave position来定位。然后通过设置IO线程和Master连接的slave_connect_state变量来告诉Master初始位点。

int start_slave_threads()
{
  if (mi->using_gtid != Master_info::USE_GTID_NO &&
    !mi->slave_running && !mi->rli.slave_running)
  {
    error= rpl_load_gtid_state(&mi->gtid_current_pos, mi->using_gtid ==
                                         Master_info::USE_GTID_CURRENT_POS);
  }

}

/*
  Load the current GTID position into a slave_connection_state, for use when
  connecting to a master server with GTID.

  If the flag use_binlog is true, then the contents of the binary log (if
  enabled) is merged into the current GTID state (master_use_gtid=current_pos).
*/
int
rpl_load_gtid_state(slave_connection_state *state, bool use_binlog)
{
  int err;
  rpl_gtid *gtid_list= NULL;
  uint32 num_gtids= 0;

  if (use_binlog && opt_bin_log &&
      (err= mysql_bin_log.get_most_recent_gtid_list(&gtid_list, &num_gtids)))
    return err;

  err= state->load(rpl_global_gtid_slave_state, gtid_list, num_gtids);
  my_free(gtid_list);

  return err;
}

static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi)
{
  query_str.append(STRING_WITH_LEN("SET @slave_connect_state='"),
                 system_charset_info);
  if (mi->gtid_current_pos.append_to_string(&query_str))
  {
  }
}

从表达意义上,MariaDB的gtid_current_pos和MySQL的gtid_executed更像,表达了执行过的GTID,而不只是复制来的。而且gtid_current_pos可以在server没有做过slave, slave_pos为空的情况下自动开始复制。

但是MariaDB的gtid_current_pos仅带有每个domain上的位点信息,主要是sequence number。如果Master和Slave搭建的是双向同步通道,当Master和Slave之间复制关系中断一下,在恢复之前Master上有写入,那么就会出现当Master上再次start slave,这个时候搭建复制关系的时候sequence number比对端大,就会出现复制中断。宕机HA切换后,新Master上有写入或者老的Master有数据未同步到,再和重新启动的slave之间搭建复制关系也很容易出错。

例如在双向复制建立的情况下,当数据完全同步后,做如下操作,就会看到对应报错信息。

server_id 161003
stop slave;

server_id 161002
stop slave;
insert into t values(10);
start slave;

这时候show slave status可以看到报错
Got fatal error 1236 from master when reading data from binary log: 'Error: connecting slave requested to start from GTID 0-161002-2, which is not in the master's binlog'

如果使用slave_pos,就不会出现上述问题。

与MySQL不同

MySQL可以用MASTER_AUTO_POSITION为1,自动使用retrieved_set来作为位点标识。当retrieved_set集合并不是Master端子集的时候并不会导致IO线程报错。所以上述master_pos在MariaDB中的问题并不会出现在MySQL中。

MariaDB并发复制

MariaDB中仅记录了最后一个应用的binlog position信息,在并发复制的时候,MariaDB设计的是会保证同一个domain下sequence number的单调递增。其并发也是在这一前提下实现的。

并发参数slave_parallel_mode的可以配置的值有none, minimal, conservative, optimistic, aggressive。

与MySQL的不同

MySQL5.7中可以通过设置slave_parallel_type指定并发方式为DATABASE或者LOGICAL_CLOCK。

指定DATABASE方式,即按照事务涉及到的DB Name做并发,可以很容易根据该模式改为基于TableName。

指定LOGICAL_CLOCK方式,可以按照主库执行时候存在加锁重叠,即在主库执行明确不会有冲突的情况就可以并发。可以参考月报MySQL 5.7 LOGICAL_CLOCK 并行复制原理及实现分析

同时MySQL中并发执行的时候,在binlog中记录的event group顺序是可以和主库不一样的,gtid_executed在中间时刻可能存在空洞。

总结

MariaDB虽然在replication中基本命令和MySQL兼容,但在GTID内部设计和相关实现上和MySQL还是有着很不一样的地方。