MySQL 主从

Posted by danner on June 15, 2019

生产中会做主从架构实现读写分离从而减轻 MySQL 压力,简单架构图如下

主从复制主要靠三个线程来实现

  • Master Binlog dump thread:响应 Slave I/O 并发送 Binlog 二进制内存;在一主多从模式下,会为每个 Slave I/O thread 创建 Binlog dump thread
  • Slave I/O thread:当执行 start slave 命令后创建 I/O 线程,从 master 的指定位置读取数据保存到本地的 relay-log下次读取位置保存到 master-info
  • Slave SQL thread: 读取 relay-log 内存并在本地数据库执行,此过程称为重演

单向主从模式如上图所示,但也可以扩展形成以下几种模式

部署

每个节点上的部署没有特别之处,参考CDH 离线部署笔记 ,只需注意几个配置即可。

  • server-id:主从节点不能相同
  • expire_logs_days :设置日志过期,单位天
  • binlog_format: binlog 格式
    • ` STATEMENT `:修改数据的 sql 语句才记录到 binlog,可以减少日志量但有可能导致主从数据不一致
    • ` ROW `:仅记录那些数据被修改,修改成什么,日志量会很大;本案例推荐
    • ` MIXED :以上两种模式混合,先用 STATEMENT,不好使才用 ROW`
  • log-bin:binlog 日志文件存储目录

两个节点都部署成功之后,开始配置

  • Master

    # 主库上创建一个 复制用户
    mysql> grant replication slave on *.* to repluser@'%' identified by '123456';
    Query OK, 0 rows affected, 1 warning (0.00 sec)
      
    mysql> flush privileges;
    Query OK, 0 rows affected (0.00 sec)
      
    # 查看当前同步点
    mysql> show master status;
    +------------------+----------+--------------+------------------+-------------------+
    | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
    +------------------+----------+--------------+------------------+-------------------+
    | mysql-bin.000010 |      593 |              |                  |                   |
    +------------------+----------+--------------+------------------+-------------------+
    1 row in set (0.00 sec)
    # binlog 就在/usr/local/mysql/arch/mysql-bin.000010
    
  • Slave

    # 由于master 之前有数据需同步,先用mysqldump 导入到 slave
    # 在 master 备份所有库操作
    hadoop001:mysqladmin:/usr/local/mysql:>mysqldump -uroot -p123456 -A > tmp/allbak.sql 
    hadoop001:mysqladmin:/usr/local/mysql:>scp tmp/allbak.sql root@hadoop004:/root/tmp
      
    # slave 恢复
    hadoop004:mysqladmin:/usr/local/mysql/:>mysql -uroot -p123456 -A < /root/tmp/allbak.sql 
      
    # slave 设置同步点
    mysql> change master to
        -> master_host='hadoop001',
        -> master_port=3306,
        -> master_user='repluser',
        -> master_password='123456',
        -> master_log_file='mysql-bin.000010',
        -> master_log_pos=593;
    Query OK, 0 rows affected, 2 warnings (0.02 sec)
    # slave 开启同步
    mysql> start slave;
    Query OK, 0 rows affected (0.00 sec)
      
    # 检测 I/O、sql 进程是否启动
    mysql> show slave status\G;
    ...
    Slave_IO_Running: Yes
    Slave_SQL_Running: Yes
    ...
      
    # 接下来在master 操作就会同步到 slave 上
    

故障案例

# slave sql 线程挂了
mysql> show slave status\G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: hadoop001
                  Master_User: repluser
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000013
          Read_Master_Log_Pos: 103741
               Relay_Log_File: relay-log.000010
                Relay_Log_Pos: 103568
        Relay_Master_Log_File: mysql-bin.000013
             Slave_IO_Running: Yes
            Slave_SQL_Running: No
					......
                   Last_Errno: 1032
          Last_Error: Coordinator stopped because there were error(s) in the worker(s). 
         The most recent failure being: Worker 0 failed executing transaction 'ANONYMOUS' 
         at master log mysql-bin.000013, end_log_pos 103710. See error log and/or 
         performance_schema.replication_applier_status_by_worker table for more details 
         about this failure or others, if any.
                 Skip_Counter: 0

Last_Error 执行Mastermysql-bin.000013 的 103710 时发生错误。此时先停止同步 stop slave ,然后再来看具体错误是什么

# 看 master mysql-bin.000013 文件
# 先把 binlog 转化成 sql
hadoop001:mysqladmin:/usr/local/mysql/arch:>mysqlbinlog --no-defaults --base64-output=decode-rows -v -v mysql-bin.000013 > ../1.sql
# 查看pos 是在 9:17:52 分钟发生
hadoop001:mysqladmin:/usr/local/mysql/arch:>cat ../1.sql | grep "end_log_pos 103710"
#191119  9:17:52 server id 1739  end_log_pos 103710 CRC32 0x6aaf1e91    Update_rows: table id 305 flags: STMT_END_F
hadoop001:mysqladmin:/usr/local/mysql/arch:>more ../1.sql 
#191119  9:17:52 server id 1739  end_log_pos 103710 CRC32 0x6aaf1e91    Update_rows: table id 305 flags: STMT_END_F
### UPDATE `rz_db`.`wc_offset`
### WHERE
###   @1='eebce1812c5c80033d7e024be4f73800' /* VARSTRING(32) meta=32 nullable=0 is_null=0 */
###   @2='wc' /* VARSTRING(64) meta=64 nullable=1 is_null=0 */
###   @3='sscOffset' /* VARSTRING(64) meta=64 nullable=1 is_null=0 */
###   @4=0 /* INT meta=0 nullable=1 is_null=0 */
###   @5=8 /* LONGINT meta=0 nullable=1 is_null=0 */
### SET
###   @1='eebce1812c5c80033d7e024be4f73800' /* VARSTRING(32) meta=32 nullable=0 is_null=0 */
###   @2='wc' /* VARSTRING(64) meta=64 nullable=1 is_null=0 */
###   @3='sscOffset' /* VARSTRING(64) meta=64 nullable=1 is_null=0 */
###   @4=0 /* INT meta=0 nullable=1 is_null=0 */
###   @5=10 /* LONGINT meta=0 nullable=1 is_null=0 */

看上面sql是更新 partiton=0 数据的 offset=10,那为何会发送错误呢?难道 slave 表里没有 partiton=0 数据数据?

# slave
mysql> select * from wc_offset;
+----------------------------------+-------+-----------+----------+--------+
| primaryKey                       | topic | groupid   | partiton | offset |
+----------------------------------+-------+-----------+----------+--------+
| 46a5760632f2b517aa348efd07883f31 | wc    | sscOffset |        1 |      6 |
+----------------------------------+-------+-----------+----------+--------+
1 row in set (0.00 sec)

slave 还真没有这条数据(为何没有?手动删除的才能复现故障,所以千万不要在 slave 表做删除操作),导致在更新时出错了。问题找到了,该如何修复呢?

# slave 设置 sql_slave_skip_counter
mysql> set global sql_slave_skip_counter=1;
# 重新开启 slave
mysql> start slave;
Query OK, 0 rows affected (0.01 sec)

mysql> show slave status\G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: hadoop001
                  Master_User: repluser
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000013
          Read_Master_Log_Pos: 420720
               Relay_Log_File: relay-log.000011
                Relay_Log_Pos: 74942
        Relay_Master_Log_File: mysql-bin.000013
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
            .....

sql_slave_skip_counter 就能修复,我们一起来看看原理吧。 SET GLOBAL sql_slave_skip_counter = N 跳过N 个 event,event 的范围跟表有关系

  • 非事务表:
    • 每个 event 对应一个 sql
  • 事务表:
    • 每一个event 对应事务,无论事务是对应几条 sql;但若是发生错误的sql 是第一条语句,而我们想继续同步同个事务里的后几条语句,就需要用 ` slave_exec_mode ` 参数(设置 sql_slave_skip_counter 是会跳过整个事务)。

拓展

主从从

其中一个节点既要当从点也要当下一个从点的主节点,那么也是需要创建复制用户来访问;另外在 my.cnf 特别关注的配置

# 1 设置 从机日志才会写入 binlog,这样从从机才能获取 binlog
log_slave_updates=1

其他几种模式的主从模式该如何操作?

参考资料

分布式数据存储-MySQL主从复制

MySQL主从复制(4)原理画图深入浅出

MySQL 数据库主从复制

sql_slave_skip_counter,你真的用对了吗?