主从复制 - 图1

主从复制原理

  • 从节点会有一个IO线程和主节点建立TCP连接,请求主数据库的binlog日志
  • 主数据库会有一个IO dump线程,处理从节点请求,响应binlog日志给他
  • 从库把接收到的redolog日志写入到relay日志文件,从库上会有另外一个线程读取relay日志进行回放

    步骤

  • 确保主库和从库的service-id不同

  • 主库打开binlog功能
  • 创建从库账号

    1. create user 'backup_user'@'192.168.31.%' identified by 'backup_123';
    2. grant replication slave on *.* to 'backup_user'@'192.168.31.%';
    3. flush privileges;
  • 主库导出数据全量备份

    /usr/local/mysql/bin/mysqldump --single-transaction -uroot -proot --master-data=2 -A > backup.sql
    
    • —master-data=2:备份的SQL文件里,要记录此时主库的binlog文件和position号
    • 此时一定不能允许系统操作数据库
  • 从库还原

    • 数据导入

      CHANGE MASTER TO MASTER_HOST='192.168.31.229', MASTER_USER='backup_user',MASTER_PASSWORD='backup_123',MASTER_LOG_FILE='mysql-bin.000015',MASTER_LOG_POS=1689;
      
    • start slave;

    • show slave status;(查看主从复制状态)

      如何保证主从一致性

  • 长链接

    • 从库和主库建立长链接,主库有一个专门的线程服务于这个长链接
    • binlog 的格式

      同步方式

      全同步

  • 主库完成一个事务后,当从库也完成后,才相应给客户端

    异步

  • 如上原理描述,binlog_dump线程负责将事务的binlog同步给从库

  • 默认
  • 可能导致某一时刻主从数据不一致

    半同步

  • binlog要同步到从库,事务才返回成功

  • 主库提交事物后,当至少一有一个从库响应接收到啦binlog(写入 relay log),才相应客户端
  • 主库如果等待时间超过临界值,则转为异步复制
  • mysql5.7中,半同步可能存在幻读

    • 当主库成功提交事物然后等待从库确认,这个时候,客户端可以从主库读到最新的数据,但如果下一秒主库挂啦,主从切换,从库升级为主库,此时读到的和上一次读到的数据不一致

      增强半同步复制

  • ,mysql5.7.2版本后,对半同步辅助模式的改进,主要解决幻读

  • 通过在主库配置参数:rpl_semi_sync_master_wait_point=AFTER_SYNC
  • 主库存储引擎在提交事物之前,必须收到从库的确认消息

    GTID

    定义

  • Global Transaction ID,即全局事务ID,每个事务都会有一个GTID一一对应

  • 组成部分:GTID:server_uuid:gno
    • server_uuid:是一个实例第一次启动时候自动生成的,全局唯一
    • gno:是一个整数,初始值1,每次提交事务的时候分配给这个事务,并加一
  • 使用:在启动一个mysql实例的时候,加上参数gtid_mode=on和enforce_gtid
  • 更加简单的主从复制搭建方式

    生成方式

  • 取决于session变量gtid_next的值

  • gtid_next=automatic。
    • 代表使用默认值,这是mysql会把server_uuid:gno分配给这个事务
    • 记录binlog的时候先记录一行:SET@@SESSION.GTID_NEXT=’server_uuid:gno’;
    • 然后将GTID放入本实例的GTID集合
  • gtid_next是一个指定的gtid值,比如set gtid_next=’current_gtid’,指定为current_gtid,那么就有两种情况
    • 如果current_gtid已经存在于当前实例的gtid集合,那么接下来执行的这个事务就会被忽略不执行
    • 如果current_gtid没有存在于实例的gtid集合,那么就将这个gtid分配给接下来要执行的事务
    • 也就是说不需要系统生成GTID
  • 这个每个实例都维护了一个GTID集合,用来对应“已经执行过的事务”

    数据延迟问题

    原因

  • 主库多线程并发写入,从库单线程拉取主库的binlog

    延迟时间

  • 推荐使用percona-toolkit工具集里的pt-heartbeat

  • 它会在主库里创建一个heartbeat表,然后会有一个线程定时更新表中的时间戳字段;从库会有一个线程负责检查heartbeat表里的时间戳字段,和当前时间对比一下就知道从库落后的时间了

    解决方案

  • MYSQL5.7支持并行复制

    • 在从库里设置slave_parallel_works>0,然后将slave_parallel_type设置为LOGICAL_CLOCK
  • 在mycat或者sharding-Sphere中设置某些查询sql直接走主库

    主库故障转移

  • 主库A发生故障后,将Mysql A2设置为主库,此时还需要将从库B、C指向新的主库

    基于位点的主备切换

  • 将节点B设置为节点A2的从库

    CHANGE MASTER TO
    MASTER_HOST=$host_name
    MASTER_PORT=$port
    MASTER_USER=$username
    MASTER_PASSWORD=$password
    MASTER_LOG_FILE=$master_log_file
    MASTER_LOG_POS=$master_log_pos
    
    • MASTER_LOG_FILE和MASTER_LOG_POS,表示从主库MASTER_LOG_FILE文件的MASTER_LOG_POS位置的日志同步数据
  • 同步位点寻找

    • 等待新主库A2将中转日志同步完
    • 在A2上执行show master status,得到当前A2上的最新File 和position
    • 取原主库的故障时间T
    • 使用mysqlbinlog工具,解析A2的File,得到T时刻的位点

      mysqlbinlog File --stop-datetime=T --start-datetime=T
      
      • 其中end_log_pos后面的值就是T时刻的位点
    • 但这个位点并不准确,因为这个时刻的binlog可能也已经同步到从库
    • 所以如果直接从这个位点同步,可能会出现“主键冲突”
    • 而主动跳过这个错误的方法有

      • 主动跳过一个事务

        set global sql_slave_skip_count=1;
        start slave
        
      • 通过slave_skip_errors参数,设置跳过指定的错误

        • 1062错误是插入数据时唯一键冲突
        • 1032错误是删除数据时找不到行
        • 因此将slave_skip_errors设置为“1032,1062”

          基于GTID的主备切换

  • 将节点B设置为节点A2的从库

    CHANGE MASTER TO
    MASTER_HOST=$host_name
    MASTER_PORT=$port
    MASTER_USER=$user_name
    MASTER_PASSWORD=$password
    master_auto_position=1
    
  • 其中master_auto_position=1,表示主备关系使用GTID协议

  • 主备切换逻辑
    • 将现在新主库A2的gtid集合记为set_a,从库B的gtid集合记为set_b
    • 实例B连接主库A2
    • 实例B将自己的set_b发送给A2
    • 实例A2计算出set_b和自己set_a的差集,也就是存在于set_a,但不存在set_b
      • 实例A2判断自己是否这些差集的binlog
      • 如果全部都有,则返回第一个不存在set_b的binlog日志,发送给实例B
      • 否则,说明自己日志不完整,返回错误
    • 然后实例B就按顺序读取binlog
  • 使用GTID,不需要寻找点位了,主库会判断从哪里开始同步日志给从库,如果主库的日志不完整,就拒绝同步给从库,保证了日志的完整性