随着业务的增加,数据量逐渐扩大,数据的安全性就变得尤为重要。虽然我们可以定期备份数据来达到保护的效果,但是 MySQL 提供了 复制 功能可以有效的实现这个目的。

主从复制

复制 顾名思义,就是定期的将一个 MySQL 上的变更,同步到其他的 MySQL 上。这不仅解决了因为单个数据库崩溃而导致数据丢失的问题。也给数据库的 读写分离 提供了方便,避免了单个数据库性能的降低而产生对外服务能力不足的问题。

主从复制 的原理:主节点的所有变更会记录到一个二进制日志(binlog)中,从节点会启动两个线程:I/O,SQL。I/O 用于请求主节点的二进制文件,同步变化到从节点的中继日志(relay log)中。SQL 线程则读取中继文件(relay log),并解析成集体步骤,完成数据的一致。如下图:

http://cdn.xdbin.com/pics/20180731155535

二进制日志 作为数据同步的载体,在这个过程中尤为重要。二进制日志只记录数据的改动,不改变数据的语句则不会被记录。MySQL 的复制除了基于 SQL,还有一种是基于,这里不作讨论。二进制日志并不是一个单独的文件,而是由一系列易于管理的文件组成。

下面,我们介绍一下使用 Docker 搭建一个主从复制的示例。

Docker 启动多个 MySQL 容器

Docker 的安装这里不多作介绍。

获取镜像

首先我们先去镜像仓库拉取一个 MySQL 的镜像。

docker pull hub.c.163.com/library/mysql:latest

完成之后,可以通过 docker images 查看本地的镜像。

生成容器并启动

接下来启动容器,关于 镜像和容器的区别 可以网上查询资料详细了解。

1
2
3
4
# 主节点 master
docker run --name master -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d 9e641
# 从节点 slave
docker run --name slave -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 --link master:master -d 9e641

这里简单说明一下这两个命令:

docker run [imageId] 是启动一个包含指定镜像的容器。这个命令会建立一个新的容器,如果我们只需要启动,重启,关闭已有的容器。可以使用 docker start/restart/stop container

--name 给容器指定名称

--link 进行容器间的通信。docker 的容器之间默认是隔离的。master:master 是指在 slave 容器中,可以通过 master:3306 直接访问 master 容器。根据之前原理的介绍,这在主从复制中是必须的,因为从节点要启动一个 I/O 线程去同步二进制日志的变更。这点需要特别注意。

-p 进行本机和容器的端口映射。访问本地的 3306 端口直接映射到 master 的 3306 端口。

-e 指定容器的环境变量。MYSQL_ROOT_PASSWORD=123456 指定 MySQL root 用户的密码是 123456。

-d 容器后台运行。

9e641 MySQL 镜像 id。

Master Slave 配置

首先进入到容器内,修改 MySQL 的配置文件。

修改主节点配置

docker exec -it master /bin/bash 进入容器。
vim /etc/mysql/mysql.conf.d/mysql.conf 修改配置。

1
2
3
4
# 追加下面配置
server-id = 1
log-bin = master-bin # 二进制日志产生所有文件的基本名
log-bin-index = master-bin.index # 二进制索引文件的文件名,这个文件保留了所有 binlog 文件的列表

在主节点添加用户

mysql -uroot -p123456 登录。然后执行下列语句:

1
2
3
create user repl_user; # 创建用户 repl_user
grant replication slave on *.* to repl_user identified by 'repl_user'; # 给用户指定权限和密码
flush privileges;

修改从节点配置

docker exec -it slave/bin/bash 进入容器。
vim /etc/mysql/mysql.conf.d/mysql.conf 修改配置。

1
2
3
4
# 追加下面配置
server-id = 2 # 不能和主节点一致
relay-log = slave-relay-bin
relay-log-index = slave-relay-bin.index

连接 Master 和 Slave

在从节点 Slave 的 MySQL 控制台执行:

1
2
3
change master to master_host = 'master', master_port = 3306, master_user = 'repl_user', master_password = 'repl_user'; # 指定主节点
start slave; # 打开同步
stop slave; # 关闭同步

完成上面的步骤之后,我们重启 docker 容器,使上面的 MySQL 配置生效。

1
2
docker restart master
docker restart slave

主节点 Master 可以在 MySQL 控制台执行 show master status; 查看当前状态信息。
从节点 Slave 可以在 MySQL 控制台执行 show slave status; 查看当前状态信息。其中,Slave_IO_RunningSlave_SQL_Running 分别对应 slave 的两个工作线程。都为 Yes 表示配置成功。Slave_IO_StateWaiting for master to send event 表示等待主节点的变更。

现在大功告成,我们可以在 Master 执行以下测试语句。

1
2
3
4
create database Hello; # 创建数据库 Hello
use Hello;
create table `student` (sid int primary key auto_increment, name varchar(50)) engine=InnoDB default charset = utf8;
insert into student (name) values ('Bao');

不出意外,从节点 Slave 上应该会同步出 数据库 Hello

遇到的问题

This operation cannot be performed with a running slave io thread; run STOP SLAVE IO_THREAD FOR CHANNEL ‘’ first.

执行 change master to ... 时没有关闭同步,需要先执行 stop slave;

Slave_IO_Running 为 No

Slave 的 I/O 进程出现错误,应先验证节点之间是否可以正常通信。这个示例中,docker 的容器之间未配置 link 会出现这个问题。