Gtid_set / Gtid_state

Gtid_set

GTID的定义由Gtid_set实现
Gtid_set = array( sidno => link_list(Interval) )
Interval = (start, end)

  • Gtid_set的结构是一个以sidno为序号的数组,每个元素都指向一个Interval组成的链表,链表中的每个Interval用来存放一组GTID的区间
  • Gtid_set使用int32 类型的sidno代替server_uuid作为 Interval 链表的索引
  • Sid_map建立128 位的server_uuid与32位的sidno之间的映射

Gtid_state

GTID还会维护的三个变量,代码中由类Gtid_state定义
logged_gtids
【系统变量】gtid_executed
已经执行过并记录到binlog的GTID集合系统变量
lost_gtids
【系统变量】gtid_purged
从binlog删除的GTID集合。当MySQL调用 purge_logs删除binlog时,同时会更新lost_gtids的内容
owned_gtids
【系统变量】gtid_owned
正由线程执行的GTID集合
还有一个SHOW SLAVE STATUS显示的变量Retrieved_Gtid_Set:Slave收到的GTID集合
【注1】

  • 不能保证最后的GTID事务是完整收到的(Retrieved_Gtid_Set的最后一个GTID),可能由于连接中断,系统崩溃等原因导致只收到“部分”事务,在MySQL 5.7.2中由修复,详见Relay log 中 GTID group 完整性检测
  • 倒数第二个GTID事务肯定完整收到,因为如果能进行最后一个事务的传输,说明倒数第二个GTID事务传输成功

    GTID的生命周期

  • 首先,执行数据库操作时,产生一个GTID,立即记录到全局和当前线程的 gtid_owned (owned_gtids)

  • 其次,提交数据库事务时,新产生的GTID 被写入 binlog,接着记录到 gtid_executed(logged_gtids),然后从全局与线程区域的 gtid_owned (owned_gtids)状态中清除
  • 最后,如果执行了purge操作删除binlog,被删除的GTID会记录到gtid_purged(lost_gtids),这些GTID仍然包含在 gtid_executed(logged_gtids)全局状态里

    Log Event

    先来看一下GTID模式下的binlog event
    增加了Previous_gtids_log_event,Gtid_log_event:

    Previous_gtids_log_event

    在每个binlog的开始(Format_description_log_event之后),包含在创建该binlog之前执行过的GTID集合

    Gtid_log_event

    在每个事务的开始,包含该事务的GTID

    Master/Slave复制

    Server/Client传输协议

    传输过程为:

  • Client -> Server:执行命令消息(COM_BINLOG_DUMP_GTID等)

  • Server -> Client:命令执行结果

这里从源码上详细的分析GTID模式下Master/Slave的复制过程,交互过程为:
— Slave:

  • STOP SLAVE(如果有必要)
  • CHANGE MASTER TO …
  • START SLAVE
  • 正常的Master/Slave复制

    CHANGE MASTER TO …

    命令的请求格式为:

如果设置MASTER_AUTO_POSITION方式连接Master,Slave发送的binlog_name和binlog_offset都为空,Master只使用gtids_executed定位Slave需要执行的binlog event
【调用栈】

CHANGE MASTER TO …为COM_QUERY类型
【逻辑】

  • mysql_execute_command
    • gtid_pre_statement_checks:GTID_STATEMENT_EXECUTE / GTID_STATEMENT_CANCEL / GTID_STATEMENT_SKIP
      • 检查enforce_gtid_consistency
      • 读取gtid_next
      • 检查是否包含隐式提交 && gtid_next != AUTOMATIC,报错ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET
      • 如果gtid_next为UNDEFINED_GROUP类型,报错ER_GTID_NEXT_TYPE_UNDEFINED_GROUP
      • 如果是以下类型,直接返回GTID_STATEMENT_EXECUTE
        • SQLCOM_COMMIT
        • SQLCOM_BEGIN
        • SQLCOM_ROLLBACK
        • ……
      • 如果一个事务同时更新了事务表和非事务表,或者多个非事务表,必须停止事务,例如
      • 检查当前事务(GTID)是否应该被跳过is_already_logged_transaction只有以下两种情况被认为应该跳过这个GTID
        • gtid_next类型为GTID_GROUP & gtid_next_list为空时,owned_gtid.sidno为0(owned_gtid.sidno为0说明gtid_next被执行过,见下面【解析】)
        • gtid_next类型为GTID_GROUP & gtid_next_list非空时,gtid_next_list不包含gtid_next->gtid【解析】开启GTID后
          • 事务的Event Group之前的Gtid_Event会SET GTID_NEXT
          • 当前THD会调用gtid_acquire_ownership_single,试图成为这个GTID的THD owner
          • 如果gtid_next被执行过,gtid_state->is_logged(),break,那么owned_gtid.sidno为0
          • 如果gtid_next还没有owner(owner为0),当前THD成为owner(thd->owned_gtid= gtid_next),那么owned_gtid.sidno就不为0了
          • 如果gtid_next已经有owner(owner非0),等待该GTID被执行完毕(写入binlog)或者owner THD被kill掉
    • SQLCOM_CHANGE_MASTER
      • active_mi(Master_info,保存Master的地址、端口、用户、密码、master_uuid、master_log_name、master_log_pos、*auto_position(true/false)等信息)
      • change_master
        • 如果任何Slave还在运行,报错ER_SLAVE_MUST_STOP
        • 当MASTER_AUTO_POSITION=1时,无法设置MASTER_LOG_FILE, MASTER_LOG_POS, RELAY_LOG_FILE以及RELAY_LOG_POS,报错ER_BAD_SLAVE_AUTO_POSITION
        • 检查当MASTER_AUTO_POSITION=1时GTID是否开启,否则报错ER_AUTO_POSITION_REQUIRES_GTID_MODE_ON
        • 保存当前Master的信息
        • 将解析的LEX_MASTER_INFO lex_mi保存到MASTER_INFO mi
        • 健壮性检查及处理
          • 如果没有指定host/port/log_name/log_pos,只指定了user/password等,如何处理
        • 根据need_relay_log_purge,是否purge relay log
        • 执行CHANGE MASTER TO
        • 将Master_info* mi刷入磁盘,如果等到执行START SLAVE才把Master_info刷盘,防止在执行START SLAVE之前如果mysqld异常关闭
  • trans_commit_stmt:提交这个事务(CHANGE MASTER TO语句)

    START SLAVE

    【调用栈】

【逻辑】
— Slave
start_slave:

  • 会向Master发送一系列packet
    • SELECTUNIX_TIMESTAMP()
    • SELECT VARIABLES LIKE “SERVER_ID”
    • SET @master_heartbeat_period = …
    • SET @master_binlog_checksum = …
    • SELECT @master_binlog_checksum
    • SELECT @@GLOABLE.GTID_MODE
    • ……
    • COM_BINLOG_DUMP_GTID:cli_advanced_command中net_write_command发送命令

— Master
com_binlog_dump_gtid:

  • slave_gtid_executed如果不是Master的logged_gtids的子集,报错ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER
  • Master的lost_gitds如果不是slave_gtid_executed的子集,报错ER_MASTER_HAS_PURGED_REQUIRED_GTIDS
  • 根据slave_gtid_executed,寻找第一个不在slave_gtid_executed集合中的binlog
  • 获取Master binlog列表(根据master-index.log)
  • 倒序遍历Master binlog列表
  • 如果binlog的Previous_gtids_log_event中的GTID范围为slave_gtid_executed的子集,那么可以确定,该binlog之前的所有binlog都被Slave执行过
  • 发送余下的binlog events给Slave

    Data Replication

    — Master

  • gtid_next类型为AUTOMATIC_GROUP

— Slave

  • gtid_next类型为GTID_GROUP