---
# System prepended metadata

title: mysql复制的原理与流程
tags: [mysql]

---

# mysql复制的原理与流程
---
1. **什么是mysql复制？**

    简单来说就是保证主服务器（Master）和从服务器（Slave）的数据是一致性的，向Master插入数据后，Slave会自动从Master把修改的数据同步过来（有一定的延迟），通过这种方式来保证数据的一致性，就是Mysql复制。
    
2. **mysql复制原理**

    三个线程：主库线程，从库I/O线程，从库SQL线程
    复制流程：
        （1）I/O 线程向主库发出请求
        （2）主库线程响应请求，并推BINLOG 日志到从库
        （3）I/O 线程收到线程并记入中继日志
        （4）SQL线程从中继日志读取SQL，并记入从库binlog日志，FLUSH进硬盘

3. **mysql一致性,延时性，数据恢复**

    - 主从复制一致性由BINLOG执行顺序保证（TIMESPAN+POS)
        日志越详细，主从一致性越容易保证
    - 延时性：延时表现为 延迟时间=从库执行SQL完成的时刻-主库开始执行SQL时间
    - 数据恢复 备份时记录的BINLOG位置点
    - showslave status\G
    
4. **Mysql复制能解决什么问题**

    - 高可用和故障切换
    
        复制能够帮避免MySql单点失败，因为数据都是相同的，所以当Master挂掉后，可以指定一台Slave充当Master继续保证服务运行，因为数据是一致性的（如果当插入Master就挂掉，可能不一致，因为同步也需要时间），当然这种配置不是简单的把一台Slave充当Master，毕竟还要考虑后续的Salve同步Master
        
    - 负载均衡
    
        因为读写分离也算是负载均衡的一种，所以就不单独写了，因为一般都是有多台Slave的，所以可以将读操作指定到Slave服务器上（需要代码控制），然后再用负载均衡来选择那台Slave来提供服务，同时也可以吧一些大量计算的查询指定到某台Slave，这样就不会影响Master的写入以及其他查询
        
    - 数据备份

        一般我们都会做数据备份，可能是写定时任务，一些特殊行业可能还需要手动备份，有些行业要求备份和原数据不能在同一个地方，所以主从就能很好的解决这个问题，不仅备份及时，而且还可以多地备份，保证数据的安全
        
    - 业务模块化
    
        可以一个业务模块读取一个Slave，再针对不同的业务场景进行数据库的索引创建和根据业务选择MySQL存储引擎， 不同的slave可以根据不同需求设置不同索引和存储引擎

5. **主从配置需要注意的点**

    (1)主从服务器操作系统版本和位数一致；
    (2) Master和Slave数据库的版本要一致；
    (3) Master和Slave数据库中的数据要一致；
    (4) Master开启二进制日志，Master和Slave的server_id在局域网内必须唯一。
    
6. **mysql复制的步骤**

    - Master将数据改变记录到二进制日志(binary log)中，也就是配置文件log-bin指定的文件，这些记录叫做二进制日志事件(binary log events)
    - Slave通过I/O线程读取Master中的binary log events并写入到它的中继日志(relay log)
    - Slave重做中继日志中的事件，把中继日志中的事件信息一条一条的在本地执行一次，完成数据在本地的存储，从而实现将改变反映到它自己的数据(数据重放)
    ![](https://i.loli.net/2019/01/25/5c4aaf506c4f7.jpg)
    
        - 第一步Master记录二进制日志， 每次提交事务完成数据更新前，Master将数据更新的时间记录到二进制日志中，MySql会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志。再记录二进制日志后，主库会告诉存储引擎可以提交事务了。
        
        - 第二步，Slave将Master的二进制日志复制到本地的中继日志中，首先，Slave会启动一个工作线程，成为I/O线程， I/O线程跟Master建立一个普通的客户端链接，然后再Master上启动一个特殊的二进制转储（binlog dump）线程（该线程没有对应的SQL命令），这个二进制转储线程会读取主库上的二进制日志中的事件。从库I/O线程将接受到时间记录到中继日志中。
        
        - 第三步从库的SQL线程执行最后异步，该线程的从中继日志中读取事件并在从库执行，从而实现从库数据更新。

        - 这种复制架构实现了获取事件和重放事件的解偶，允许这两个过程异步进行。也就是说I/O线程能够独立于SQL线程之外工作。但这种架构页限制了复制的过程，其中最重要的一点是主库上并发运行的查询再从库只能串行化执行，因为只有一个SQL线程重放中继日志中的事件。这是很多工作负载的性能瓶颈所在。因为始终受限于单线程。

7. **mysql复制类型**

    - 基于语句的复制
        在Master上执行的SQL语句，在Slave上执行同样的语句。MySQL默认采用基于语句的复制，效率比较高。一旦发现没法精确复制时，会自动选着基于行的复制
    - 基于行的复制
        把改变的内容复制到Slave，而不是把命令在Slave上执行一遍。从MySQL5.0开始支持
    - 混合类型的复制
        默认采用基于语句的复制，一旦发现基于语句的无法精确的复制时，就会采用基于行的复制
        
    相应地，binlog的格式也有三种：STATEMENT，ROW，MIXED。
    
8. mysql 主从复制实例

我们在本地搭建主从服务器，首先得启动多个Mysql实例
默认安装的Mysql配置再/etc/mysql/my.cnf，端口3306
那下面我们在启动一个3301端口的实例

复制一份配置

cp /etc/mysql/my.cnf my_3301.cnf


创建一个给3301实例使用的文件夹

mkdir /home/mysql/3301
chown mysql:mysql  /home/mysql/ -R


修改配置

[client]
port        = 3301
socket      = /home/mysql/3301/mysqld.sock

[mysqld_safe]
socket      = /home/mysql/3301/mysqld.sock

[mysqld]
user        = mysql
pid-file    = /home/mysql/3301/mysqld.pid
socket      = /home/mysql/3301/mysqld.sock
port        = 3301
basedir     = /usr
datadir     = /var/lib/mysql_3301
tmpdir      = /tmp


初始化数据库

sudo mysql_install_db  --defaults-file=/etc/mysql/my_3301.cnf  --basedir=/usr/  --datadir=/var/lib/mysql_3301  --user=mysql

5.启动MySql, 要指定.cnf文件启动
 sudo mysqld_safe --defaults-file=/etc/mysql/my_3301.cnf --user=mysql

看看我们的成果：
[/home/mysql]$ ps aux|grep 3301
root     14554  0.0  0.0  75556  1232 ?        S    11:26   0:00 sudo mysqld_safe --defaults-file=/etc/mysql/my_3301.cnf --user=mysql
root     14555  0.0  0.0   4440   628 ?        S    11:26   0:00 /bin/sh /usr/bin/mysqld_safe --defaults-file=/etc/mysql/my_3301.cnf --user=mysql
mysql    15159  0.0  0.2 698928 42592 ?        Sl   11:28   0:14 /usr/sbin/mysqld --defaults-file=/etc/mysql/my_3301.cnf --basedir=/usr --datadir=/var/lib/mysql_3301 --plugin-dir=/usr/lib/mysql/plugin --user=mysql --log-error=/home/mysql/3301/log/error.log --pid-file=/home/mysql/3301/mysqld.pid --socket=/home/mysql/3301/mysqld.sock --port=3301
lidongw+ 15189  0.0  0.0 107352  1900 pts/1    S+   11:28   0:00 mysql -S /home/mysql/3301/mysqld.sock -uroot -px xxxx
lidongw+ 27141  0.0  0.0  18304  1024 pts/13   S+   18:42   0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=.cvs --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn 3301


登录mysql的shell

mysql -S /home/mysql/3301/mysqld.sock -uroot -p123456
远程的加上ip 端口
mysql -S /home/mysql/3301/mysqld.sock -uroot -p123456 -h localhost  -P 3307


如果上面启动有问题，那就是apparmor的配置问题:

vim /etc/apparmor.d/usr.sbin.mysqld

添加新的Mysql实例上面配置的datadir目录进去，这样才能让实例正确找到目录
  /var/lib/mysql_3301/ r,
  /var/lib/mysql_3301/** rwk,

保存重启
service apparmor restart

至此，已经成功运行多一个监听3301的MySql实例，可以为Mysql复制做准备

作者：大富帅
链接：https://www.jianshu.com/p/c2159fa9e948
來源：简书
简书著作权归作者所有，任何形式的转载都请联系作者获得授权并注明出处。

```bash
```
启动多个Mysql实例
要配置主从复制，我们在本机开多个Mysql实例来操作就可以了，让他们监听不同端口
多开实例可以看我另一篇教程：同一台Ubuntu 启动多个mysql
主从复制配置
现在我们两个实例Mysql
server1 : 127.0.0.1   3306 master
server2 : 127.0.0.1   3301 slave
配置master
在主库创建一个复制帐号，这个帐号是给从库的IO线程建立连接到主库时用的，从库会用这个帐号连接主库并读取主库的二进制日志：
grant replication slave, replication client on *.* to 'repl'@'localhost' identified by '123456';

主库添加配置：
# 设置server_id，一般设置为IP, 要独一无二的
server-id       = 10
# 开启二进制日志功能，最好是绝对路径
log_bin         = /var/log/mysql/mysql-bin.log
# 主从复制的格式（mixed,statement,row，默认格式是statement）
binlog_format=mixed
# 二进制日志自动删除/过期的天数。默认值为0，表示不自动删除。
expire_logs_days=7
# 为每个session 分配的内存，在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
# 复制过滤：不需要备份的数据库，不输出（mysql库一般不同步）
binlog-ignore-db=mysql

启用二进制日志后，重启后，  show master status; 可以看到二进制相关信息
mysql> show master status;
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000008 |      107 |              |                  |
+------------------+----------+--------------+------------------+

添加从库配置
# 设置server_id，一般设置为IP, 要独一无二的
server-id       = 10
log_bin         = /var/log/mysql/mysql-bin.log
# 中继日志路径
relay_log       = /home/mysql/3301/mysql-relay-bin
# 允许从库将其重放的事件也记录到自身的二进制日志中
log_slave_updates = 1
read_only = 1

从库开启复制
mysql>  CHANGE MASTER TO
MASTER_HOST='$host',
MASTER_USER='repl',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='msyql-bin.00001',
MASTER_LOG_POS=0;

MASTER_LOG_POS设为0，是从日志开头开始复制，MASTER_LOG_FILE是master的二进制文件
# 启动复制
mysql> start slave;
 
# 查看复制状态
mysql> show slave status;

*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 127.0.0.1
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000008
          Read_Master_Log_Pos: 107
               Relay_Log_File: mysql-relay-bin.000020
                Relay_Log_Pos: 253
        Relay_Master_Log_File: mysql-bin.000008
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
        Seconds_Behind_Master: 0
             Master_Server_Id: 10

# Slave_IO_Running: Yes，Slave_SQL_Running: Yes 说明同步正常进行
# Seconds_Behind_Master: 0 就是完全同步了

这时就完成了主从复制的配置，当主服务器有更新，从库也会更新。
我们还可以从线程列表看出复制线程，主库上可以看到由从库I/O线程向主库发起的连接。
mysql> show processlist \G
*************************** 1. row ***************************
     Id: 44
   User: repl
   Host: localhost:32866
     db: NULL
Command: Binlog Dump
   Time: 73032
  State: Master has sent all binlog to slave; waiting for binlog to be updated
   Info: NULL

同样，我们看看从库的线程，有两个，一个I/O线程，一个SQL线程：
mysql> show processlist \G
*************************** 1. row ***************************
     Id: 4
   User: system user
   Host: 
     db: NULL
Command: Connect
   Time: 73422
  State: Waiting for master to send event
   Info: NULL
*************************** 2. row ***************************
     Id: 5
   User: system user
   Host: 
     db: NULL
Command: Connect
   Time: 72417
  State: Slave has read all relay log; waiting for the slave I/O thread to update it
   Info: NULL

这两个线程都是再system user 帐号下运行，I/O线程是写日志到中继日志的线程， SQL线程是重放SQL的线程。
从已经运行已久的服务器开始复制
那么，至此我们已经完成了Mysql的主从配置。
但是上面是配置两台刚好安装号的服务器，数据相同，并且知道当前主库二进制日志。
更典型的案例是，一个运行已经一段时间的主库，要用一台新安装的从库与之同步，此时这台从库还没有数据。
所以我们得想办法，线初始化从库： 从主库复制数据、使用最近依次备份来启动从库。
这需要三个条件来让主库和从库保持同步：
复制数据到从库
mysqldump  --single-transaction --all-databases --master-data=1 -uroot -p1234

###### tags: `mysql`