MySQL复制

一、复制概述

复制是指将主库的 DDL DML 操作通过 binlog 日志传到从库上,然后在从库上对日志重新执行,使得从库和主库数据保持同步

1.1 复制原理

a) 主库事务提交时会把数据变更作为事件 event 记录在 binlog 日志中;MySQL主库上的 sync_binlog 参数控制 binlog 日志刷新到磁盘的频率

b) 创建 I/O 线程连接主库,I/O 线程向主库请求数据

c) 主库创建 binlog dump 线程读取 binlog 中的 event 。发送给 I/O 线程

d) I/O 线程获取到 event 数据后更新到从库的 relay log

e) 从库上的 SQL线程读取中继日志 relay log中的 event,更新到数据库

 

二、三种binlog格式对复制的影响

2.1 SBR

基于语句级别的复制。binlog_format=statement,记录 SQL 语句到 binlog

优点:

  • Binlog文件较小
  • 可用于审计

缺点:

  • 执行insert select需要持有更多的行锁
  • update要扫表
  • auto_increment会阻塞其他insert语句
  • 对一些系统函数复制会导致主从不一致
    • Load_file()
    • Uuid()
    • User()
    • Found_rows()
    • Sysdate(),master执行时间是 00:00:00,由于有延时,到slave的执行时间是 00:00:01
    • ……

 

2.2 RBR

基于行级别的复制。binlog_format=row,记录每一行的变化到 binlog

优点:

  • 在某些情况下复制速度更快(SQL复杂,表有主键)
  • 系统的特殊函数也可以复制
  • 更少的锁

缺点

  • Binary log比较大
  • 单语句更新表的行数过多,会形成大量binlog。造成同步延迟(解决办法:使用SBR)
  • 无法从binlog看见用户执行的SQL(mysql5.6增加了一个新的eventbinlog_row_query_log_events,记录用户的query)
  • 对于非事务引擎,持有的锁比SBR多。使用RBRSLAVE上没有并行插入
  • 生成BLOB的值花费的时间比SBR

 

行复制流程:

a) master更新数据,写入binlog

b) Slavemaster上注册io_thread接受日志变更

c) Io_thread接收变更之后,会写入到本地的realy_log

d) 通过sql_thread进行解析,执行

e) 判断是否有主键

f) 如果有主键,就进行主键匹配、写入

g) 如果没有主键,就使用二级索引进行匹配

h) 如果没有二级索引,就只能进行全表扫描

 

2.3 MIXED

混合使用rowstatement格式。对于DDL记录使用statement格式,对于table的行操作记录使用row格式

binlog_format=mixed

  • 如果使用innodb表,事物级别使用READ COMMITREAD UNCOMMIT只能使用row格式
  • 但在使用ROW格式中DDL语句还是会记录成statement格式
  • Mixed模式,以下几种情况会将binlog_formatSBR改成RBR
    • DML语句更新一个NDB表时
    • 当函数中包含UUID()
    • 2个及以上包含AUTO_INCREMENT字段的表被更新时
    • 行任何INSERT DELAYED语句时
    • UDF
    •  试图中必须使用RBR。例如创建视图使用了UUID()函数

 

三、GTID

  • GTID全称是global transaction identifieds(全局的事务标志)
  • 一个事物对应一个唯一ID
  • GTID组成:server_uuit + transaction_id
    • server_uuit 是全局唯一,默认保存在 $DATADIR/auto.cnf,如果有重复,删除了重启MySQL
    • transaction_id 是自增的
  • 搭建GTID需要配置以下参数
    • gtid_mode=on  
    • enforce-gtid-consistency=on  
    • log-bin=mysql-bin  

3.1 GTID的意义

传统复制模式下

master 宕机后,一个 slave 被提升为 new master,如果需要继续维持复制关系,就需要把另外两个 slave change master 指向 new master。那么问题就来了,原来 slave 是指向 master filename + pos 的位置,现在要指向 new master 上新的 filename + pos 位置,这两个位置是比较难对应起来的,此时两个 slave change master 比较麻烦

 

GTID模式下

和上面的场景一样,两个 slave 需要重新指向 new master,由于使用了 GTID,目前 slave-A 获取到的日志对应的GTID=G_Aslave-B 获取到的日志对应GTID=G_B。此时 new_master上存在G_AG_B(通过选举出来的,获取的日志应该是最多的),那两个slave就可以直接使用 G_AG_B这两个GTID,通过指向 new master 接着继续复制

 

3.2 GTID filename-pos 的对应

binlog中,多了一个 GTID event,通过扫描 binlog 中的GTID值,就可以知道 GTID filename-pos的关系,如下所示

 

+-----------------+--------+----------------+-----------+-------------+---------------------------------------------------------------------+
| Log_name        | Pos    | Event_type     | Server_id | End_log_pos | Info                                                                |
+-----------------+--------+----------------+-----------+-------------+---------------------------------------------------------------------+
| mybinlog.000057 |      4 | Format_desc    |    873306 |         120 | Server ver: 5.6.37-log, Binlog ver: 4                               |
| mybinlog.000057 |    120 | Previous_gtids |    873306 |         151 |                                                                     |
| mybinlog.000057 |    151 | Gtid           |    873306 |         199 | SET @@SESSION.GTID_NEXT= '987c1082-8001-11e8-a03b-52540089b89e:1'   |
| mybinlog.000057 |    199 | Query          |    873306 |         274 | BEGIN                                                               |
| mybinlog.000057 |    274 | Table_map      |    873306 |         335 | table_id: 70 (hb.heartbeat)                                         |
| mybinlog.000057 |    335 | Update_rows    |    873306 |         485 | table_id: 70 flags: STMT_END_F                                      |
| mybinlog.000057 |    485 | Xid            |    873306 |         516 | COMMIT /* xid=10 */                                                 |
| mybinlog.000057 |    516 | Gtid           |    873306 |         564 | SET @@SESSION.GTID_NEXT= '987c1082-8001-11e8-a03b-52540089b89e:2'   |
| mybinlog.000057 |    564 | Query          |    873306 |         639 | BEGIN                                                               |
| mybinlog.000057 |    639 | Table_map      |    873306 |         700 | table_id: 70 (hb.heartbeat)                                         |
| mybinlog.000057 |    700 | Update_rows    |    873306 |         850 | table_id: 70 flags: STMT_END_F                                      |
| mybinlog.000057 |    850 | Xid            |    873306 |         881 | COMMIT /* xid=16 */                                                 |
| mybinlog.000057 |    881 | Gtid           |    873306 |         929 | SET @@SESSION.GTID_NEXT= '987c1082-8001-11e8-a03b-52540089b89e:3'   |
+-----------------+--------+----------------+-----------+-------------+---------------------------------------------------------------------+

 

但是如果 binlog 很大,扫描的成本会很大,所以在 binlog 的开头部分有一个 Previous_gtids event。如果我要的 GTID Previous_gtids 的大,就扫描当前文件,反之扫描之前的文件,以此类推。因为 binlog rotate 的时候,是知道当前最大的 GTID 的,可以将该值写入到下一个新的 binlog 的开头,即 Previous_gtids

 

3.3 GTID 复制出错处理

3.3.1 slave 上插入一行数据

insert into a select 2;

3.3.2 master 上插入同样数据

insert into a select 2;

3.3.3 查看复制状态

root@localhost:mysql.sock  10:17:  [vcyber]>show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.84.87
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mybinlog.000058
          Read_Master_Log_Pos: 52587080
               Relay_Log_File: mysql-relay-bin.000006
                Relay_Log_Pos: 319820
        Relay_Master_Log_File: mybinlog.000058
             Slave_IO_Running: Yes
            Slave_SQL_Running: No
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 1062
                   Last_Error: Could not execute Write_rows event on table vcyber.a; Duplicate entry '2' for key 'id_2', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log mybinlog.000058, end_log_pos 52584859
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 52584646
              Relay_Log_Space: 52587694
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 1062
               Last_SQL_Error: Could not execute Write_rows event on table vcyber.a; Duplicate entry '2' for key 'id_2', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log mybinlog.000058, end_log_pos 52584859
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 873306
                  Master_UUID: 987c1082-8001-11e8-a03b-52540089b89e
             Master_Info_File: /data/mysql/mysql_3306/data/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: 
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 180723 10:17:19
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 987c1082-8001-11e8-a03b-52540089b89e:1-146477
            Executed_Gtid_Set: 81e0915b-5187-11e8-b129-5254004a7fb5:1,
987c1082-8001-11e8-a03b-52540089b89e:1-146470
                Auto_Position: 1

 

3.3.4 处理复制步骤

a) 将 slave gtid_next 指向执行失败 gtid

    Executed_Gtid_Set: 987c1082-8001-11e8-a03b-52540089b89e:1-148588 是已经执行过的 GTIDgtid_next 的值应该是148589

b) 执行一个空的事务,即 begin;commit;

c) 将 gtid 还原为 automatic

-- 设置 gtid_next 为失败的 gtid
set gtid_next='987c1082-8001-11e8-a03b-52540089b89e:148589';
-- 执行一个空的事务
begin;commit;
-- 还原为 automatic
set gtid_next='automatic'

 

3.3.5 启动 sql_thread,查看复制状态

start slave sql_thread;
show slave status\G

 

四、并行复制

MySQL5.7复制的前提是,所有处于 prepare 阶段的事务都是可以并行提交

5.7 binlog 比 以前版本多了以下两个内容

  • last_committed:一个 last_commited 代表 一个组
  • sequence_number:一个 sequence_number 代表一个事务

 

如图所示,last_committed3个值,分别是 0 1 4,这代表着 3 个组

laster_commit = 4 中有3 sequence5 6 7,代表着 3 个事务,这 3 个事务可以并行复制。每一组中的 last_commited 值,都是上一个组中事务的 sequence_number 最大值。只要切换 binlog 文件,技术从0开始

 

 

4.1 ordered_commit

当事务提交,会经过三个阶段 FLUSH、SYNC 及 COMMIT。这三个步骤分别有各自的队列。

4.1.1 FLUSH 阶段

  1. 首先进入 FLUSH 队列,如果某个事物加入时,队列是空的,则担任这个事务的队长,来代表其他事务执行提交操作。其他事务继续加入时,发现已经有了队长,那么就跟随队伍。
  2. 当队长将这个队列中的事物取出期间,其他事务进入FLUSH队列需要等待。
  3. 为每个事物分配一个 sequence_number。为组分配 last_committed。last_committed=sequence_number-1
  4. 将Redo log中prepare阶段的数据刷盘
  5. 将binlog数据写入文件,当然此时只是写入文件系统的缓冲,并不能保证数据库崩溃时binlog不丢失
  6. Flush阶段队列的作用是提供了Redo log的组提交
  7. 如果在这一步完成后数据库崩溃,由于协调者binlog中不保证有该组事务的记录,所以MySQL可能会在重启后回滚该组事务
  8. 等到队长取出队列中所有事务。其他事务就可以加入这个队列。

4.1.2 SYNC阶段

这里为了增加一组事务中的事务数量,提高刷盘收益,MySQL使用两个参数控制获取队列事务组的时机:

  • binlog_group_commit_sync_delay=N:在等待N μs后,开始事务刷盘(图中Sync binlog)
  • binlog_group_commit_sync_no_delay_count=N:如果队列中的事务数达到N个,就忽视binlog_group_commit_sync_delay的设置,直接开始刷盘(图中Sync binlog)

Sync阶段队列的作用是支持binlog的组提交
如果在这一步完成后数据库崩溃,由于协调者binlog中已经有了事务记录,MySQL会在重启后通过Flush 阶段中Redo log刷盘的数据继续进行事务的提交

4.1.3 commit阶段

  1. 首先获取队列中的事务组
  2. 依次将Redo log中已经prepare的事务在引擎层提交(图中InnoDB Commit)
  3. Commit阶段不用刷盘,如上所述,Flush阶段中的Redo log刷盘已经足够保证数据库崩溃时的数据安全了
  4. Commit阶段队列的作用是承接Sync阶段的事务,完成最后的引擎提交,使得Sync可以尽早的处理下一组事务,最大化组提交的效率

 

 

4.2 多线程复制分发原理

  1. 从库 SQL thread 拿到一个新事务,取出 last_committed 及 sequence_number 值
  2. 判断当前 last_committed 是否大于当前已经执行的 sequence_number 的最小值
  3. 如果大于,则说明上一个组的事务还没有完成。此时等待最小值变大,知道 last_committed与最小值相等,才可以继续
  4. 如果小于或等于,则说明当前事务与正在执行的组是同一个组,不需要等待
  5. SQL thread 通过统计,找到一个空闲的 worker 线程,如果没有空闲则等待;
  6. 将当前事务打包,交给 worker去 APPLY这个事务

 

4.3 并行复制相关参数

  • slave-parallel-type:并行模式
    • LOGICAL_CLOCK:逻辑时钟
    • DATABASE:基于库级别
  • slave-parallel-workers:并行复制的线程
  • slave_preserve_commit_order:对于多线程slaves,来保障事务在slave上执行的顺序与relay log中的顺序严格一致,只有当“slave_parallel_workers”开启时有效;此时“log_bin”、“log_slave_updates”必须开启,而且“slave_parallel_type”值必须为“LOGICAL_CLOCK”(默认值为DATABASE)。即当多线程开启时,且根据relay log中事务的逻辑顺序执行statements,是否需要严格保持顺序,默认值为0表示并发执行忽略顺序
  • binlog_group_commit_sync_no_delay_count:等待一组里面有多少事物我才提交
  • binlog_max_flush_queue_time:等待多少时间后才进行组提交

 

五、复制相关参数

  • log-bin:是否打开binlog日志,不可动态修改
  • server-id:数据库唯一的标识。以后可能会被server-uuid取代(gtid)
  • log-bin-index:指定mysql-logbin.index的位置
  • binlog_format:binlog日志格式:statement、row、mixed三种。可动态修改
  • binlog_cache_size:binlog写入的buffer,可以动态修改。通常配置成1M
  • max_binlog_size:指单个binlog文件的大小。默认是1G
  • sync_binlog:多少个SQL后,刷新binlog到disk。默认是0系统自动刷新,0是性能最好的。如果是交易型的,建议是1,1是每个SQL刷新到磁盘中。1是性能最差的。
  • expire_logs_days:指N天后自动删除binlog。默认没有开启,就会遇到binlog把磁盘占满
  • log_bin_trust_function_creators:存储过程调用到的。假如存储过程在测试环境创建的,导出之后就带着用户名。到线上的时候就调用不了,这时候打开这个参数就好了。
  • log_warnings:异常的断开连接Abort connection,拒绝连接请求。默认是0。等于1是不会记录。
  • binlog_error_action:默认是不开启,可以开启binlog_error_action=ABORT_SERVER。假如磁盘满了,binlog写不进去,就会在应用层直接报错。这个参数是5.6之后才有
  • binlog_row_image :5.6之后的参数。在用row的时候,日志很大。binlog_row_image=[full|minimal|noblob]
    • full: update tb set c2=5 where id=1;会记录变更之前的记录,还有之后的记录。默认值
    • minimal:假如update tb set c2=xxx where id=xxx;假如一个表有10个字段,然后值update c2字段,只记录c2跟主键。
    • noblob:如果没有涉及到blob字段的update,就不记录blob
  • binlog_rows_query_log_events=1|0:默认是关闭的,row格式下只记录具体变更的内容。开启这个参数后,会把用户执行的SQL写入到binlog
  • slave-skip-errors=1062,1053,1146 #跳过指定error no类型的错误

 

-- GTID相关的参数

  • gtid_mode=on:打开GTID
  • log-slave-updates : 从库上也会记录binlog
  • enforce-gtid-consistency:控制GTID,哪些语句可以记录到LOG中。
  • gtid_next:跳过下一个事物
  • gtid_purged:进行同步的时间,忽略之前的数据。gtid_purged=uuid:1000;忽略1000以前个事物,需要reset master才能用。
  • gtid_executed:GTID到底执行过多少事物

 

-- SLAVE相关的参数

  • server-id:数据库的唯一标识
  • relay-log:slave存放的日志路径
  • relay-log-index
  • read-only :只读,但是对super用户没  有作用
  • log-slow-slave-statements: sql_thread 执行sql超过long_query_time 会记到慢日志中
  • log_slave_updates
  • max_relay_log_size:设置relay日志的大小
  • relay-log-info-file:假如从库挂掉了,同步接不上了。从relay.info上读取信息。从这些信息向master重新请求。
  • relay_log_info_repository:table --SQL thread crash safe – SQL线程安全
  • relay-log-recover:1 --I/O thread crash safe – IO线程安全
  • master_info_repository = TABLE
  • read_only = 1
  • binlog_gtid_recovery_simpliefied :默认是不开启。假如从库挂了,重新启动slave,会从新主库拉一下relay log。
  • relay_log_purge
  • relay_log_recovery 
  • replicate-same-server-id :默认是不开启。复制自己相同的server-id。建议不开启
  • skip-slave-start:从库启动之后,不自动开启同步。
  • slave_transaction_retries:执行SQL语句的时间,把区间给锁了,sql_theard就要等这些锁的释放。就会有一个重试的过程,当超过这个重试的次数,就会报错。这时候start slave sql_thread就可以修复
  • slave_parallel_workers:5.6的参数。并行复制,默认是关闭;如果有2个库,就可以定义slave_parallel_workers=2
  • relay_log_space_limit:sql_thread=NO, IO_THREAD=YES,会发现本地的relay log越来越多,磁盘使用就会越来越多。限定relay占用磁盘的大小。
  • sync_master_info:master info存到数据库的表里面。默认情况下是1000秒,如果在5.6.17之前,配置成1,就会遇到内存溢出的错误。
  • sync_relay_log_info :relay info 存到数据库的表里面。
  • slave_net_timeout:从库跟主库多少时间断开,才认为是连不上主库。默认是3600秒。推荐配置成10秒
  • slave_skip_errors:slave上的参数,忽略错误。不建议忽略错误
  • sql_slave_skip_counter:忽略多少个复制事件,遇到个别错误(主键冲突、记录不存在等)时,可以忽略这些事件,继续复制进程。一般一次只忽略一个事件,除非很肯定,否则不要设置大于1

 

六、复制过滤规则

-- master端:

  • binlog-do-db = "abc":只记录 abc 库的binlog
  • binlog-ignore-db = "abc":不记录 abc 库的binlog

 

-- slave端

  • replicate-do-db = "abc":只想记录某个DB的binlog
  • replication-ignore-db="mysql":忽略某个库
  • replicate-do-table = wubx.tb;:只记录某个表
  • replicate-ignore-table=wubx.quota:忽略某个表
  • replicate-rewrite-db='a_db->b_db':把A库复制到B库
  • replicate-wild-do-table="wubx.ds_%":只记录ds开头的表

 

七、复制维护管理

7.1 查看从库状态

root@localhost:mysql.sock  17:35:  [(none)]>show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.84.88
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mybinlog.000002
          Read_Master_Log_Pos: 2967
               Relay_Log_File: mysql-relay-bin.000008
                Relay_Log_Pos: 359
        Relay_Master_Log_File: mybinlog.000002
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 2967
              Relay_Log_Space: 532
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 883306
                  Master_UUID: 81e0915b-5187-11e8-b129-5254004a7fb5
             Master_Info_File: /data/mysql/mysql_3306/data/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 6e6cd1a5-5187-11e8-b129-52540089b89e:1-11
                Auto_Position: 1

io_thread: master_log_file , read_master_log_pos 

sql_thread: relay_master_log_file, exec_master_log_pos

比较一下是不是同步完成?

master_log_file = relay_master_log_file 

read_master_log_pos = exec_master_log_pos 

 

7.2 主从一致性数据校验与修复

pt-table-checksunm:利用语句级别复制,主从库上对表分段进行校验,如果值不一样,说明不同步。     

pt-table-sync:利用语句级别复制,所有的修复都是在主库上修复。

 

八、Replication crash safe

8.1 SQL thread crash safe

8.1.1 relay_log_info_repository 为 FILE 情况下

如果将 relay_log_info_repository 设置为 FILE,MySQL会把回放记录在 relay-info.log 文件中,其中包含 SQL 线程回放到的 relay_log 和 relay_log_pos,以及对应的 master 的 master_log 和 master_log_pos

  1. SQL 线程回放 event
  2. 将回放到的 binlog 文件名和位置写到 relay-info.log 文件
  3. 参数 sync_relay_log_info = 10000 (fsync) 代表每回放 10000个 event,写一次 realy-info.log,如果设置为 1,则表示每回放一个event,就写一次relay-info.log,那么性能会很差。而且设置为 1 后,即使性能上可以接受,可还是会丢失最后一次的操作。

当一个Slave节点在复制数据时,可能发生如下情况, 数据2和数据3写入成功(且已经落盘),但是relay-info.log 中的记录还是数据1的位置(因为sync_relay_log_info 的关系,此时还没有fsync),此时Slave宕机,然后重启,便会产生如下的状况:

  1. Slave的库中存在数据2和数据3
  2. Slave读取 relay-info.lo g中的 Relay_log_name 和 Relay_log_pos ,此时记录的是回放到数据1的位置
  3. Slave 从数据1开始回放,继续插入数据2和数据3
  4. 但是,此时的数据库中存在数据2和数据3 ,于是发生了1062 的错误(重复记录)

 

8.1.2 relay_log_info_repository 为 TABLE 情况下

在 MySQL5.6 之后,将 relay_log_info_repository 设置为 TABLE,relay-info 写到 mysql.slave_relay_log_info 这张表中

设置为 TABLE 的原理为,将 event 的回放 和 relay-info 的更新放在同一个事物里面,变成原子操作,从而保证一致性

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;

 

8.2 I/O thread crash safe

IO thread 也是接受一个个的 event,将接受到的 event,通过设置参数 master_info_repository 将 master-info(IO线程接受到的位置,master_log_name 和 master_log_pos) 信息写入到文件或者数据库表中,然后将接受到的 event 写入 relay log file。

参数 sync_master_info = 10000 表示每接收 10000 个 event,写一次 master-info

假设存在下面这个情况,event2和event3已经写入到了relay-log,但是master-info还没有同步到master-info.log。此时如果服务宕机后,MySQL重启,I/O线程会读取master-info.log的内容,读取到的位置为event1的位置,然后I/O线程会继续将event2和event3拉取过来,然后继续写入到relay-log 中。event2 和 event3 被重复写入到了relay-log文件中,当SQL线程回放时,就会产生1062 的错误(重复记录),看到的现象还是SQL线程报错,IO线程正常,但其实是因为 IO线程重复写入了。

8.2.1 master_info_repository

IO thread 同样也提供了 master_info_repository参数,TABLE 记录数据库表,FILE 记录文件。但是可以解决 crash 问题吗?

答案是不可以,为什么?

因为 IO thread 写的是 relay-log 文件吧,即使 master-info 设置为 TABLE 也无法解决宕机问题。

但是将 master-info-repository 设置为 TABLE,可以提高性能

设置为TABLE 后, master-info 将信息保存到mysql.slave_master_info 中。

那么 IO thread crash应该怎么解决?

relay-log-recover

8.2.2 relay-log-recover

relay-log-recover = 1,该参数表示 IO thread crash 重启之后,把当前接收的所有 relay-log 都删除掉,然后以 SQL thread 应用到的位置,重新去 master 请求日志

注意:以上参数都是 5.6 之后才有

 

九、复制延迟

9.1 Seconds_Behind_Master

Seconds_Behind_Master 字面意思为Slave落后Master的秒数。计算的方式为: Slave的SQL线程执行时刻的时间减去 event产生时刻的时间(event_header 中的前 4个字节就是timestamp )。字面意思:当 sql_thread 执行 event 的时候才会计算。那么当 io_thread 延迟或者 stop。那么这时候,sql_thread 已经把接收到的 relay_log应用完,那么Seconds_Behind_Master 为0,但是实际上,主从有延时。所以 Seconds_Behind_Master 只能计算 sql_thread的延迟,无法计算 io_thread。那么有什么办法解决这个问题?答案是:pt-heartbeat

 

9.2 pt-heartbeat

9.2.1 pt-heartbeat 的原理

在 Master 节点上创建一个带有时间戳字段的表, pt-heartbeat --update 每隔一段时间写入时间戳,通过主从复制,将表中的内容(带有时间戳的记录)复制到Slave节点,通过运行pt-heartbeat --monitor 的服务器的本地时间戳减去复制到Slave节点的记录的时间戳,就可知道Slave落后Master的时间

 

9.2.2 使用pt-heartbeat

# 主库:

pt-heartbeat --update -u'root' -p'abc#1234' -S '/data/mysql/mysql_3306/tmp/mysql.sock' -D hb --create-table --daemonize

-D:指定数据库

--create-table:创建存储时间戳的表

--daemonize:后台运行

 

# 从库:

pt-heartbeat --monitor -u'root' -p'abc#1234' -S '/data/mysql/mysql_3306/tmp/mysql.sock' --master-server-id 873306 -D hb

0.00s [ 0.00s, 0.00s, 0.00s ]

0.00s [ 0.00s, 0.00s, 0.00s ]

0.00s [ 0.00s, 0.00s, 0.00s ]

 

# monitor 从slave的 hb.heartbeat中找到master同步过来的记录,然后和本地系统时间做差值计算,得到落后时间

 

9.2.3 主从延迟的几个可能原因

  1. 主从配置不一致,包括硬件,参数等
  2. 主库 TPS 太高,从库单线程回放
  3. 主库运行了大事务
  4. row 模式下没有主键最为致命
  5. 主库运行了 DDL 语句造成延时

 

参考书籍:《MySQL运维内存》

参考链接:https://mp.weixin.qq.com/s/_LK8bdHPw9bZ9W1b3i5UZA

posted @ 2018-11-14 17:07  Ziroro  阅读(177)  评论(0编辑  收藏  举报