一、前言
项目初期,很多都是单体架构,业务代码写在一起,数据表也都在一个数据库中;
随着项目的发展,访问量越来越大、数据量越来越多,数据库大概率会首先成为性能的瓶颈;这个时候一般都会升级项目架构为读写分离,以缓解主库的压力,提高系统的性能。MySQL 的主从复制是读写分离的前提。
二、原理
可以看到,当主库接收到客户端的更新(insert/update/delete)请求后,执行内部事务的更新逻辑,同时写 binlog。
备库 slave 跟主库 master 之间维持了一个长连接。主库 master 内部有一个线程,专门用于服务备库 slave 的这个长连接。一个事务日志同步的完整过程是这样的:
- 在备库 slave 上通过 change master 命令,设置主库 master 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量;
- 在备库 slave 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接;
- 主库 master 校验完用户名、密码后,开始按照备库 slave 传过来的位置,从本地读取 binlog,发给 slave;
- 备库 slave 拿到 binlog 后,写到本地文件,称为中转日志(relay log);
- sql_thread 读取中转日志,解析出日志里的命令,并执行。这里需要说明,后来由于多线程复制方案的引入,sql_thread 演化成为了多个线程。
三、实现
以下方法是基于 Docker 搭建的 MySQL 8.0 一主两从环境。容器名分别为 mysql-master、mysql-slave1、mysql-slave2。
创建 mysql master 服务
打开命令行,使用 docker 创建 mysql master 服务:
$ docker run -p 3300:3306 --name mysql-master -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0
进入 docker 容器:
$ docker exec -it mysql-master /bin/bash
在 /etc/mysql/my.cnf
文件中 [mysqld]
下添加如下配置(需安装 vim,不会的拉到最后):
[mysqld]
# 同一局域网内唯一
server-id=3300
# binlog 文件名
log-bin=master-bin
# 同步的数据库
binlog-do-db=shop
# binlog 格式 statement、mixed、row,默认为 statement,议为 mixed、row
binlog_format=row
# 身份验证插件、和主从无关
default_authentication_plugin=mysql_native_password
重启 mysql 服务(退出并重启 docker 容器):
$ exit
$ docker restart mysql-master
# 非 docker 环境用如下命令重启即可
$ service mysql restart
进入 docker 容器,登录 mysql ,在 master 数据库创建一个用户 slave,授予 REPLICATION SLAVE 和 REPLICATION CLIENT 权限,用于在主从库之间同步数据:
$ docker exec -it mysql-master /bin/bash
$ mysql -uroot -p123456
# 创建一个账号
mysql> CREATE USER 'slave'@'%' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.00 sec)
# 授予这个账号权限
mysql> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';
Query OK, 0 rows affected (0.00 sec)
# 刷新权限
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
通过命令行 show master status
查看当前 binlog 日志的信息,后面有用:
mysql> show master status;
+-------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+-------------------+
| master-bin.000001 | 848 | shop | | |
+-------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
创建 mysql slave1 服务
打开新的命令行,使用 docker 创建 mysql slave1 服务:
$ docker run -p 3301:3306 --name mysql-slave1 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0
进入 docker 容器:
$ docker exec -it mysql-slave1 /bin/bash
在 /etc/mysql/my.cnf
文件中 [mysqld]
下添加如下配置:
[mysqld]
# 同一局域网内唯一
server-id=3301
# 从库只读 (1.只读 0.读写)
read_only=1
# 身份验证插件、和主从无关
default_authentication_plugin=mysql_native_password
重启 mysql 服务(退出并重启 docker 容器):
$ exit
$ docker restart mysql-slave1
# 非 docker 环境用如下命令重启即可
$ service mysql restart
进入 docker 容器,登录 mysql ,设置主库信息:
master_host:master 服务的地址,使用容器独立的 ip ,用 docker inspect --format='{{.NetworkSettings.IPAddress}}' mysql-master
查询
$ docker exec -it mysql-slave1 /bin/bash
$ mysql -uroot -p123456
# 设置主库信息
mysql> change master to
master_host='172.17.0.2', -- master mysql 服务地址
master_port=3306, -- master mysql 服务端口,使用容器的端口,即:3306
master_user='slave', -- master mysql 提供的账号
master_password='123456', -- master mysql 提供的密码
master_log_file='master-bin.000001', -- master mysql 服务 show master status 的 File 字段值
master_log_pos=848, -- master mysql 服务 show master status 的 Position 字段值
master_connect_retry=30;
# 开启主从复制
mysql> start slave;
用 show slave status
检测是否配置成功,Slave_IO_Running
、Slave_SQL_Running
为 Yes 说明配置成功:
mysql> show slave status \G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 172.17.0.2
Master_User: slave
Master_Port: 3306
Connect_Retry: 30
Master_Log_File: master-bin.000001
Read_Master_Log_Pos: 848
Relay_Log_File: 5180d9b88f01-relay-bin.000002
Relay_Log_Pos: 325
Relay_Master_Log_File: master-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
创建 mysql slave2 服务
再打开一个新的命令行,使用 docker 创建 mysql slave2 服务:
$ docker run -p 3302:3306 --name mysql-slave2 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0
进入 docker 容器:
$ docker exec -it mysql-slave2 /bin/bash
在 /etc/mysql/my.cnf
文件中 [mysqld]
下添加如下配置:
[mysqld]
# 同一局域网内唯一
server-id=3302
# 从库只读 (1.只读 0.读写)
read_only=1
# 身份验证插件、和主从无关
default_authentication_plugin=mysql_native_password
重启 mysql 服务(退出并重启 docker 容器):
$ exit
$ docker restart mysql-slave2
# 非 docker 环境用如下命令重启即可
$ service mysql restart
进入 docker 容器,登录 mysql ,设置主库信息:
$ docker exec -it mysql-slave2 /bin/bash
$ mysql -uroot -p123456
# 设置主库信息
mysql> change master to
master_host='172.17.0.2', -- master mysql 服务地址
master_port=3306, -- master mysql 服务端口,使用容器的端口,即:3306
master_user='slave', -- master mysql 提供的账号
master_password='123456', -- master mysql 提供的密码
master_log_file='master-bin.000001', -- master mysql 服务 show master status 的 File 字段值
master_log_pos=848, -- master mysql 服务 show master status 的 Position 字段值
master_connect_retry=30;
# 开启主从复制
mysql> start slave;
用 show slave status
检测是否配置成功,Slave_IO_Running
、Slave_SQL_Running
为 Yes 说明配置成功:
mysql> show slave status \G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 172.17.0.2
Master_User: slave
Master_Port: 3306
Connect_Retry: 30
Master_Log_File: master-bin.000001
Read_Master_Log_Pos: 848
Relay_Log_File: e34e0c0fcd86-relay-bin.000002
Relay_Log_Pos: 325
Relay_Master_Log_File: master-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
创建 mysql slave N 服务
如需创建更多从库,只需更改 slave1 配置步骤中容器的端口、名称以及 server-id 即可。
测试主从复制
主从复制环境是否真正搭建成功,只需在 master 服务创建 shop 库,执行 insert/update/delete 语句,看从库是否同步这些操作即可,如果同步说明搭建成功。
四、杂记
安装 vim
$ apt-get update
$ apt-get install vim
本文首发于马燕龙个人博客,欢迎分享,转载请标明出处。
马燕龙个人博客:https://www.mayanlong.com
马燕龙个人微博:http://weibo.com/imayanlong
马燕龙Github主页:https://github.com/yanlongma