基于 GTID 的复制

基于 GTID 的复制

GTID (全局事务标识符)

完全基于事务

  • 每笔事务都可以在提交到原始服务器上并由任何副本应用时进行标识和跟踪
  • 事务划分
    • 源上提交的客户端事务
      • 写入二进制日志后分配新的 GTID
    • 副本上重现的复制事务

副本和源的 GTID 保持一致

  • 只要在源上提交的所有事务也都在副本上提交,则可以保证两者之间的一致性

确保了一致性

  • 一旦在给定服务器上提交了具有给定 GTID 的事务,该服务器将忽略具有相同 GTID 的任何后续事务。这意味着在源上提交的事务只能在副本上应用一次,这有助于确保一致性

GTID 格式和存储

GTID 格式

  • 一对坐标,用冒号分隔

    GTID = source_id:transaction_id
    
    • source_id
      • 源服务器的 server_uuid
    • transaction_id
      • 事务 id,事务的序列号
  • GTID 的序列号上限
    • 当服务器实例接近限制时,会发出警告

查看

  • 查看所有已分配的 GTID
    • mysql.gtid_executed
      mysql> select * from mysql.gtid_executed;
      +--------------------------------------+----------------+--------------+
      | source_uuid                          | interval_start | interval_end |
      +--------------------------------------+----------------+--------------+
      | 2e2cfd05-7840-11eb-9cca-000c29bd5ad5 |              1 |            1 |
      | 2e2cfd05-7840-11eb-9cca-000c29bd5ad5 |              2 |            2 |
      +--------------------------------------+----------------+--------------+
      
  • 查看副本上事务的应用状态
    • performance_schema.replication_applier_status_by_worker
      mysql> select * from performance_schema.replication_applier_status_by_worker\G
      *************************** 1. row ***************************
                                                 CHANNEL_NAME: 
                                                    WORKER_ID: 0
                                                    THREAD_ID: 49
                                                SERVICE_STATE: ON
                                            LAST_ERROR_NUMBER: 0
                                           LAST_ERROR_MESSAGE: 
                                         LAST_ERROR_TIMESTAMP: 0000-00-00 00:00:00.000000
                                     LAST_APPLIED_TRANSACTION: 2e2cfd05-7840-11eb-9cca-000c29bd5ad5:2
           LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP: 2021-03-28 11:35:49.330505
          LAST_APPLIED_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP: 2021-03-28 11:35:49.330505
               LAST_APPLIED_TRANSACTION_START_APPLY_TIMESTAMP: 2021-03-28 11:39:20.567950
                 LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP: 2021-03-28 11:39:20.573935
                                         APPLYING_TRANSACTION: 
               APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP: 0000-00-00 00:00:00.000000
              APPLYING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP: 0000-00-00 00:00:00.000000
                   APPLYING_TRANSACTION_START_APPLY_TIMESTAMP: 0000-00-00 00:00:00.000000
                       LAST_APPLIED_TRANSACTION_RETRIES_COUNT: 0
         LAST_APPLIED_TRANSACTION_LAST_TRANSIENT_ERROR_NUMBER: 0
        LAST_APPLIED_TRANSACTION_LAST_TRANSIENT_ERROR_MESSAGE: 
      LAST_APPLIED_TRANSACTION_LAST_TRANSIENT_ERROR_TIMESTAMP: 0000-00-00 00:00:00.000000
                           APPLYING_TRANSACTION_RETRIES_COUNT: 0
             APPLYING_TRANSACTION_LAST_TRANSIENT_ERROR_NUMBER: 0
            APPLYING_TRANSACTION_LAST_TRANSIENT_ERROR_MESSAGE: 
          APPLYING_TRANSACTION_LAST_TRANSIENT_ERROR_TIMESTAMP: 0000-00-00 00:00:00.000000
      

GTID 集

GTID 集是包括一个或多个单个 GTID 或一系列 GTID 的集合。

  • 使用方式
    • 在系统变量中使用

      这两个变量的值都是以 GTID 集存储的。

      mysql> show variables like '%gtid%';
      +----------------------------------+------------------------------------------+
      | Variable_name                    | Value                                    |
      +----------------------------------+------------------------------------------+
      | binlog_gtid_simple_recovery      | ON                                       |
      | enforce_gtid_consistency         | ON                                       |
      | gtid_executed                    | 2e2cfd05-7840-11eb-9cca-000c29bd5ad5:1-2 |
      | gtid_executed_compression_period | 0                                        |
      | gtid_mode                        | ON                                       |
      | gtid_next                        | AUTOMATIC                                |
      | gtid_owned                       |                                          |
      | gtid_purged                      |                                          |
      | session_track_gtids              | OFF                                      |
      +----------------------------------+------------------------------------------+
      
      • gtid_executed
      • gtid_purged
    • 在语句中使用
      • START REPLICA | SLAVE … UNTIL SQL_BEFORE_GTIDS = gtid_set
        • 使副本进程处理到 GTID 集中第一个 gtid 就停止
      • START REPLICA | SLAVE … UNTIL SQL_AFTER_GTIDS = gtid_set
        • 使副本进程处理到 GTID 集中最后一个 gtid 之后停止
    • 以下内置函数需要 GTID 集作为输入
      • gtid_subset()
      • gtid_subtract()
  • 可以将源自同一服务器的一系列 GTID 折叠为一个表达式
    3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
    3E11FA47-71CA-11E1-9E33-C80AA9429562:1-3:11:47-49
    2174B383-5441-11E8-B90A-C80AA9429562:1-3, 24DA167-0C0C-11E8-8442-00059A3C7B00:1-19
    
    • UUID 按字母顺序排列
    • 数字间隔按升序合并

mysql.gtid_executed 表

此表用于存储 GTID,当二进制日志丢失时,此表保留了 GTID 状态。

GTID 会与事务一起存储在表中,每次事务提交,记录 GTID。GTID 的存储点与是否开启了二进制日志无关。

  • 清除此表
    mysql> reset master;
    
  • 系统变量
    • binlog_error_action
      • 如果无法访问此表进行写操作并且达到了 max_binlog_size,则服务器将根据此变量的设置进行响应
      • 有效值
        • IGNORE_ERROR:在服务器上记录一个错误,并停止二进制日志记录
        • ABORT_SERVER:(默认) 服务器关闭
  • mysql.gtid_executed 表压缩

    该表会定期压缩,速率可配置。

    • 系统变量
      • gtid_executed_compression_period
        • 在压缩表之前经过的事务数以及压缩率。
        • 默认为0,意味着不使用此显式压缩方法,而是根据需要隐式发生
    • 查看 GTID 相关线程
      • performance_schema.threads
        mysql> select * from performance_schema.threads where name like '%gtid%'\G
        *************************** 1. row ***************************
                  THREAD_ID: 33
                       NAME: thread/innodb/clone_gtid_thread
                       TYPE: BACKGROUND
             PROCESSLIST_ID: NULL
           PROCESSLIST_USER: NULL
           PROCESSLIST_HOST: NULL
             PROCESSLIST_DB: NULL
        PROCESSLIST_COMMAND: NULL
           PROCESSLIST_TIME: 2103
          PROCESSLIST_STATE: waiting for handler commit
           PROCESSLIST_INFO: NULL
           PARENT_THREAD_ID: NULL
                       ROLE: NULL
               INSTRUMENTED: YES
                    HISTORY: YES
            CONNECTION_TYPE: NULL
               THREAD_OS_ID: 4246
             RESOURCE_GROUP: SYS_default
        *************************** 2. row ***************************
                  THREAD_ID: 46
                       NAME: thread/sql/compress_gtid_table
                       TYPE: FOREGROUND
             PROCESSLIST_ID: 7
           PROCESSLIST_USER: NULL
           PROCESSLIST_HOST: NULL
             PROCESSLIST_DB: NULL
        PROCESSLIST_COMMAND: Daemon
           PROCESSLIST_TIME: 2103
          PROCESSLIST_STATE: Suspending
           PROCESSLIST_INFO: NULL
           PARENT_THREAD_ID: 1
                       ROLE: NULL
               INSTRUMENTED: YES
                    HISTORY: YES
            CONNECTION_TYPE: NULL
               THREAD_OS_ID: 4255
             RESOURCE_GROUP: SYS_default
        
    • 线程
      • thread/sql/compress_gtid_table
        • 压缩线程
        • 这个线程不在 show processlist 的输出中
        • 如果启用二进制日志
          • 不使用此压缩方法,而是在每次二进制日志轮换时压缩此表
        • 如果禁用二进制日志
          • 此线程平常时休眠,直到执行了指定数量的事务才会唤醒,以执行此表的压缩。然后再休眠,再唤醒,如此循环。
      • innodb/clone_gtid_thread
        • 此线程将 InnoDB 和非 InnoDB 事务都写入 mysql.gtid_executed 表
        • 这个线程按组收集 GTID,将它们刷新到 mysql.gtid_executed 表,然后压缩该表。
        • 如果服务器混合使用 InnoDB 事务和非 InnoDB 事务 (分别写入 mysql.gtid_executed 表) ,则 compress_gtid_table 线程执行的压缩会干扰 GTID 持久性线程的工作,并可能大大降低它的速度。
          • 在这种情况下,建议将 gtid_executed_compression_period 设置为0,以便永远不要激活 compress_gtid_table 线程。

GTID 生命周期

生命周期

  • 源端事务执行并提交,根据源端的 UUID 和序列号分配一个 GTID
  • GTID 写入二进制日志,以原子方式持久保存
    • 当日志轮换或关闭服务器时,先前二进制日志中的 GTID 会写入 mysql.gtid_executed 表
  • 源端将 GTID 添加到 gtid_executed 系统变量的 GTID 集中
    • 该 GTID 集包含所有已提交事务的 GTID,并且用作复制过程中的状态令牌
    • 此时,这个系统变量中的 GTID 集是完整的,但是 mysql.gtid_executed 表未完整
  • 将二进制日志传输到副本并存储在中继日志后,副本读取 GTID 并根据这个 GTID 设置了 gtid_next 系统变量

  • 验证 gtid_next 这个值的事务是否已使用过,如果已使用,则跳过该事务;如果未使用过这个 GTID,则应用这个事务

    • 系统变量
      • gtid_owned
        • 当前正在使用的 GTID 及线程
  • 副本上,如果开启二进制日志,当事务开始时,GTID 记入二进制日志。当二进制日志轮换或关闭服务器时,先前二进制日志中的 GTID 会写入 mysql.gtid_executed 表
    • 如果禁用二进制日志,GTID 直接写入 mysql.gtid_executed 表

GTID 分配时做了哪些更改

  • 已提交的事务生成新的 GTID
    • GTID 也可以分配给除事务之外的其他更改,在某些情况下,可以为单个事务分配多个 GTID
    • 单个语句生成多个 GTID 的情况
      • 调用存储过程以提交多个事务。该过程提交的每个事务都会生成一个 GTID
      • 多表 DROP TABLE 语句删除不同类型的表
      • 使用基于行的复制时 (binlog_format = ROW),将发出 CREATE TABLE … SELECT 语句。为 CREATE TABLE 操作生成一个 GTID,为行插入操作生成一个 GTID
  • 写入二进制日志的每个数据库更改 (DDL 或 DML) 都分配有一个 GTID

  • 非事务更新和事务更新都分配了 GTID
  • 当二进制日志中的生成语句自动删除表时,会将 GTID 分配给该语句
  • 如果未将事务写入原始服务器上的二进制日志,则服务器不会为其分配 GTID
  • 在事务的 XA PREPARE 阶段和事务的 XA COMMIT 或 XA ROLLBACK 阶段,为 XA 事务分配了单独的 GTID

系统变量

  • GTID 相关

    服务器启动时,会初始化这两个变量。

    • gtid_next
      • 默认情况下,对于在用户会话中提交的新事务,服务器会自动生成并分配一个新的 GTID
      • 变量值
        • AUTOMATIC:(默认) 自动分配 GTID
        • 也可以设置成某个 GTID 值,但是显式分配麻烦,不推荐
          • 复制线程在应用事务时,会把副本的 gtid_next 值显式的设置为源端的 GTID,而不是自己分配新的
          • 这种显式分配 GTID 一般用来模拟复制事务的过程
    • gtid_purged
      • 包含所有已提交但是二进制日志中找不到的 GTID
        • 在副本数据库上禁用了二进制日志记录的情况下提交的复制事务的 GTID
        • 二进制日志清理时已经删掉了,但是这个变量中还有保留
        • 通过变量 gtid_purged 显式添加进来的 GTID
          • 显式添加这个变量,会同时添加到 gtid_executed 变量中
      • mysqldump 备份时,默认会将备份中包含的事务,按以下方式告诉从库,这样备份中已有的事务,就不再运行,直接跳过,从下一个 GTID 开始请求二进制日志
        • set @@global.GTID_PURGED=’XXXX’;
  • 控制并行应用
    • slave_parallel_workers>0
      • 副本可以配置多线程,实现并行应用事务
    • slave_preserve_commit_order=1
      • 副本也可以设置有序提交。默认是无序提交

重置 GTID 执行历史

mysql> reset master;
  • 进行的操作
    • gtid_purged 系统变量的值设置为空字符串
    • gtid_exected 系统变量的全局值 (而不是会话值) 设置为空字符串
    • mysql.gtid_executed 表被清除
    • 如果服务器启用了二进制日志记录,则将删除现有的二进制日志文件并清除二进制日志索引文件
  • 注意事项
    • 危险操作,操作前要备份,避免丢失二进制日志
    • RESET MASTER 重置了 GTID 的执行历史。而 RESET REPLICA | SLAVE 对 GTID 的执行历史没有影响。

配置过程

假设先前存在未使用 GTID 的复制结构。包含事务但没有 GTID 的日志不能在启用了 GTID 的服务器上使用。

同步服务器

  • 设置只读
    mysql> SET @@GLOBAL.read_only = ON;
    
  • 等待所有正在进行的事务提交或回滚。然后,等待副本追赶源

关闭服务器

shell> mysqladmin -uusername -p shutdown

重启时开启 GTID

gtid_mode=ON
enforce-gtid-consistency=ON
  • 系统变量

    • gtid_mode
      • 控制开启/禁用 GTID
      • 源端必须始终开启二进制日志和 GTID,副本必须开启 GTID,但是可以不开启二进制日志
    • force_gtid_consistency
      • 强制 GTID 一致性,防止执行不受支持的语句

在副本上设置源服务器信息

  • CHANGE REPLICATION SOURCE TO
    CHANGE REPLICATION SOURCE TO
    SOURCE_HOST='172.16.0.20',
    SOURCE_USER='repl',
    SOURCE_PASSWORD='repl',
    SOURCE_PORT=3306,
    SOURCE_AUTO_POSITION=1,
    SOURCE_PUBLIC_KEY_PATH='public_key.pem',
    GET_SOURCE_PUBLIC_KEY=1;
    
    • 选项
      • SOURCE_AUTO_POSITION
        • GTID 自动定位。默认是禁用的

        • 查询副本接收到的事务

          SELECT RECEIVED_TRANSACTION_SET 
          FROM PERFORMANCE_SCHEMA.replication_connection_status
          
      • SOURCE_LOG_FILE

      • SOURCE_LOG_POS

  • CHANGE MASTER TO

    CHANGE MASTER TO
    MASTER_HOST='172.16.0.20',
    MASTER_USER='repl',
    MASTER_PASSWORD='repl',
    MASTER_PORT=3306,
    MASTER_AUTO_POSITION=1,
    MASTER_PUBLIC_KEY_PATH='public_key.pem',
    GET_MASTER_PUBLIC_KEY=1;
    
    • 选项
      • MASTER_AUTO_POSITION
      • MASTER_LOG_FILE
      • MASTER_LOG_POS

进行新的备份

  • 启用 GTID 之后,之前的所有备份都无法再使用。flush logs,然后重新备份。

开启副本

mysql> START SLAVE | REPLICA;
  • 查看复制状态
    mysql> show slave | replica status\G
    ...
               Replica_IO_Running: Yes
              Replica_SQL_Running: Yes
    
  • 如果副本配置错误,还可以重置
    mysql> stop slave; reset slave all;
    or
    mysql> stop replica; reset replica all;
    

去掉只读

mysql> SET @@GLOBAL.read_only = OFF;

使用 GTID 进行故障转移

GTID 简化了复制数据流的常规管理,尤其是简化故障转移。

注入空事务

源端的 gtid_executed 变量,包含所有已提交的事务。可以记录这个变量的内容,而不复制二进制日志,此方法创建的服务器本质上是快照。

  • 提交一个空事务
    SET GTID_NEXT='aaa-bbb-ccc-ddd:N';
    
    BEGIN;
    COMMIT;
    
    SET GTID_NEXT='AUTOMATIC';
    
  • 刷新并清除副本的二进制日志
    FLUSH LOGS;
    PURGE BINARY LOGS TO 'source-bin.00000N';
    
    • 一旦使用空事务以这种方式恢复了所有事务标识符,就必须刷新并清除副本的二进制日志,以防止此服务器在以后被提升为源时,用错误的事务进行复制。

恢复 GTID 模式副本

  • 使用 mysqlbinlog 找到下一个事务

  • 把这个完整事务 (直到 commit 状态,包含 SET @@SESSION.GTID_NEXT) 复制到副本

    • 二进制日志复制方式
      • mysqldump 导出,然后导入到副本
        mysqldump --master-data --set-gtid-purged
        
      • 复制文件快照到副本中
        • 可以使用企业版的 mysqlbackup 工具
      • 停服务器,冷备,然后复制到副本
        • 预先将二进制日志复制到目标服务器通常比从源中实时读取整个事务执行历史记录要快

        • 如果二进制日志中保存了完整事务

          • mysqlbinlog 将二进制日志导入到新副本
            mysqlbinlog --read-from-remote-server --read-from-remote-master
            
          • 或者直接把源端的二进制日志复制到副本
            shell> mysqlbinlog copied-binlog.000001 copied-binlog.000002 | mysql -u root -p
            
  • 停止副本并运行复制的事务
    • mysqlbinlog 的输出分隔符是 /!/,需要恢复成默认
      mysql> DELIMITER ;
      
  • 重新启动副本
    mysql> SET GTID_NEXT=automatic;
    mysql> RESET SLAVE;
    mysql> START SLAVE;
    
    Or from MySQL 8.0.22:
    mysql> SET GTID_NEXT=automatic;
    mysql> RESET REPLICA;
    mysql> START REPLICA;
    

基于 GTID 复制的限制

涉及非事务性存储引擎的 update 操作

  • 对于事务性和非事务性存储引擎,执行的语句是不同的

防止执行不受支持的语句

  • 在启用 GTID 时,必须使用 –enforce-gtid-consistency

跳过事务

  • 使用 GTID 时不支持 sql_slave_skip_counter

忽略服务器

  • 不建议使用 CHANGE REPLICATION SOURCE TO | CHANGE MASTER TO 语句的 IGNORE_SERVER_IDS 选项,因为已应用的事务将被自动忽略

  • 查看副本状态

    SHOW REPLICA | SLAVE STATUS
    
    • Replicate_Ignore_Server_Ids 字段

GTID 模式和 mysqldump

  • 未启动 GTID 的服务器可以使用 mysqldump 导入到启用了 GTID 的服务器

从没有 GTID 的源复制到具有 GTID 的副本

可以设置复制通道,以将 GTID 分配给尚不存在的复制事务。使用此功能,可以从未启用 GTID 且未使用基于 GTID 的复制的源服务器复制到已启用 GTID 的副本。

这只是迫不得已的方案。

在线更改复制模式

GTID 支持

  • 自动定位和自动故障转移
  • 可以使用
    • WAIT_FOR_EXECUTED_GTID_SET()
    • session_track_gtid
  • 通过使用性能表监视复制的事务

系统变量

  • gtid_mode

    总之,就是不建议使用匿名事务。

    • GTID 模式

    • 有效值

      • ON
        • 不能复制匿名事务
          • 匿名事务未分配 GTID。每个匿名事务之前都带有 Anonymous_gtid_log_event
        • 可以使用自动定位功能 (该功能与匿名事务不兼容)
          CHANGE REPLICATION SOURCE TO SOURCE_AUTO_POSITION = 1
          
      • OFF
        • 只能复制匿名事务
      • ON_PERMISSIVE
        • 新事务将使用 GTID,同时允许复制的事务为 GTID 或匿名事务
      • OFF_PERMISSIVE
        • 新事务是匿名的,同时允许复制的事务是 GTID 或匿名事务
  • enforce_gtid_consistency
    • 强制 gtid 一致性
    • 有效值
      • WARN
      • ON
      • OFF

配置过程 (启用 GTID 事务)

  • 在所有服务器执行
    SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = WARN;
    
    • 运行一段时间,观察是否产生警告
  • 所有服务器执行
    SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = ON;
    SET @@GLOBAL.GTID_MODE = OFF_PERMISSIVE;
    SET @@GLOBAL.GTID_MODE = ON_PERMISSIVE;
    
  • 查看状态变量
    SHOW STATUS LIKE 'ONGOING_ANONYMOUS_TRANSACTION_COUNT';
    
    • Ongoing_anonymous_transaction_count
      • 显示已标记为匿名的正在进行的事务数
      • 这可以用来确保没有其他事务正在等待处理。
    • 等待此状态变量变为0 (只要显示一次为0即可)

  • 所有服务器执行

    SET @@GLOBAL.GTID_MODE = ON;
    
  • 所有服务器修改配置文件 /etc/my.cnf
    [mysqld]
    gtid_mode=ON
    enforce_gtid_consistency=ON
    
  • 副本服务器启用 GTID 自动定位
    STOP SLAVE [FOR CHANNEL 'channel'];
    CHANGE MASTER TO MASTER_AUTO_POSITION = 1 [FOR CHANNEL 'channel'];
    START SLAVE [FOR CHANNEL 'channel'];
    
    Or from MySQL 8.0.22 / 8.0.23:
    STOP REPLICA [FOR CHANNEL 'channel'];
    CHANGE REPLICATION SOURCE TO SOURCE_AUTO_POSITION = 1 [FOR CHANNEL 'channel'];
    START REPLICA [FOR CHANNEL 'channel'];
    

在线禁用 GTID 事务

官方文档:https://dev.mysql.com/doc/refman/8.0/en/replication-mode-change-online-disable-gtids.html

  • 过程和启用过程正好相反,略
  • 不管是启用还是禁用,只能相邻的两个 GTID 模式值之间切换
    • 例如,ON OFF ON_PERMISSIVE OFF_PERMISSIVE
    • 如果 ON -> OFF_PERMISSIVE,只能一步步切换,反之亦然

验证匿名事务复制

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注