为什么需要 Mysql 主从复制
谈起为什么在大多数情况下部署 Mysql 常常使用 Mysql 主从模式进行部署这个问题,本来也去网上搜寻了一些答案,其中原因主要有以下几点:
- 做数据的热备,主库宕机后备库能够及时替换主库,保证业务可用性,能一定程度避免数据丢失。
- 实现读写分离,主库写,从库读,减小主库的读写压力。当主库执行写过程加锁时,不会堵塞从库读操作,从而提高了数据的查询效率。
- 应对业务量越来越大,I/O 访问频率过高,单机无法满足的问题。增加多个从库做负载,能够降低整体 I/O 访问频率,提高单个机器 I/O 性能。
数据库常用的主从复制方式
- 基于 Binlog 复制模式
MySQL 主从复制默认是异步的模式。MySQL增删改操作会全部记录在 Binlog 中,当 slave 节点连接 master 时,会主动从 master 处获取最新的 Binlog 文件。并把 Binlog 存储到本地的 relay log 中,然后去执行 relay log 的更新内容。
- GTID 复制模式
在传统的复制里面,当发生故障,需要主从切换,需要找到 Binlog 和 位点信息,恢复完成数据之后将主节点指向新的主节点。在 MySQL 5.6 里面,提供了新的数据恢复思路,只需要知道主节点的 IP、端口以及账号密码就行,因为复制是自动的,MySQL 会通过内部机制 GTID 自动找点同步。接下来我们要部署的主从也是基于这种模式。
GTID 概念相关介绍
GTID 是什么
GTID
指的是全局事务 ID,全程是 Global Transaction Identifier
,在整个事务流程中每一个事务 ID 是全局唯一的,且在整个主从复制架构中该 ID 都不会相同。
GTID 主从复制方式
基于 GTID
的主从复制方式的出现,主要是用于替换传统的 日志点
复制方式。通过 GTID
可以保证每个主库提交的事务在集群中都有 唯一
的一个事务 ID。强化了数据库主从的一致性和故障恢复数据的容错能力,在 主库
宕机发生 主从切换
的情况下,GTID
方式可以让其他从库自动找到新主库复制的位置。而且 GTID
可以忽略已经执行过的事务,减少了数据发生错误的概率。
GTID 的组成
GTID 由 server_uuid
+ tid
组成,其中:
- server_uuid:
server_uuid
是在 Mysql 首次启动过程中自动生成的一个uuid(128位)
随机值,生成后会将该值存储到数据目录的auto.cnf
中。因为是随机值,所以不同服务器的 Mysql 的 server_uuid 都是不相同的。 - tid: 代表了该实例上已经提交的事务数量,是一个整数,初始值是
1
,每次提交事务的时候分配给这个事务并加1
。
其组成样式如下:
fb90fba5-60cf-11eb-b5fa-000c295fbc5f:21
GTID 复制工作原理
假设从库开启了 binlog,那么执行流程如下:
- ① 主节点执行事务提交前会产生一个
GTID
,其会随着事务一起记录到binlog
日志中。 - ② 从节点
I/O Thread
会读取主节点的binlog
日志文件并存储在从节点的relaylog
日志中。从节点将主节点的GTID
这个值配置到gtid_next
中,即下一个要读取的 GTID 值。 - ③ 从节点读取
gtid_next
中的值,然后查找自己的binlog
日志中是否有这个GTID
。 - ④ 如果有这个记录,说明这个
GTID
的事务已经执行过了,就忽略掉。 - ⑤ 如果没有这个记录,从节点就会执行该 GTID 事务,并记录到自己的
binlog
日志中。在读取执行事务前会先检查其他session
中是否持有该GTID
,确保不被重复执行。 - ⑥ 在解析过程中会判断是否有主键,如果没有就用二级索引,如果没有就用全部扫描。
GTID 使用中的限制条件
GTID 复制是针对事务来说的,一个事务只对应一个 GTID,好多的限制就在于此。其中主要限制如下:
- 不能使用
create table table_name select * from table_name
。 - 在一个事务中既包含事务表(使用
InnoDB
存储引擎的表)的操作又包含非事务表(使用MyISAM
存储引擎的表)。 - 不支持创建或删除临时表操作,如
CREATE TEMPORARY TABLE or DROP TEMPORARY TABLE
语句操作。 - 使用 GTID 复制从库跳过错误时,不支持执行该
ql_slave_skip_counter
参数的语法。
如何开启 GTID 模式
需要在 Mysql 配置文件中添加下面几条配置:
## 开启 gtid 模式
gtid_mode=on
## 配置不允许任何事务违反 GTID 一致性,用于保证数据一致性
enforce_gtid_consistency=on
## 开启 Binlog 并指定名称(可选)
log_bin=binlog
## 从节点从主节点接收到更新且执行,是否将记录存到从节点的 binlog 日志中(可选)
log-slave-updates=1
## 当从数据库启动的时候,从节点不会启动复制(可选)
skip-slave-start=1
准备部署环境
| 服务器IP | 角色 | 配置文件目录 | 数据存储目录 | |--------------|-----|-------------|------------------| | 192.168.2.11 | 主节点 | /apps/mysql | /data/mysql/data | | 192.168.2.12 | 从节点 | /apps/mysql | /data/mysql/data |
系统环境
- Mysql 版本:8.0.23
- Docker 版本:19.03.13
Docker 部署主节点
创建外挂的数据存储目录
创建 Mysql 的 Docker 镜像的外挂数据存储目录:
$ mkdir -p /apps/mysql/data
这个目录是本人创建的数据存储目录,实际部署需要修改为需要部署应用的服务器的存储目录。
创建主节点配置文件
在目录 /apps/mysql
中创建 Mysql 主节点的配置文件 my.cnf
,内容如下:
[mysqld]
port = 3306
max_connections = 2000
default-time_zone='+8:00'
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
#gtid:
server_id = 1 #服务器id
gtid_mode = on #开启gtid模式
enforce_gtid_consistency = on #强制gtid一致性,开启后对于特定create table不被支持
#binlog
log_bin = mysql-binlog
log_slave_updates = on
binlog_format = row #强烈建议,其他格式可能造成数据不一致
#relay log
skip_slave_start = 1
default_authentication_plugin = 'mysql_native_password' #更改加密方式
MySQL8.0 之前 mysql 密码加密方式为 mysql_native_password,而 MySQL8.0 版本默认新添加的用户密码默认使用的 caching_sha2_password,因此进行使用主从复制时可能会遇到错误,需要将加密方式改成 mysql_native_password。
部署 Mysql 主节点
部署 Mysql 的主节点,执行的 Docker 命令如下:
$ docker run --name mysql-a -d \
--restart=always \
--network=host \
-v /apps/mysql/my.cnf:/etc/mysql/conf.d/my.cnf \
-v /apps/mysql/data:/var/lib/mysql \
-v /etc/localtime:/etc/localtime \
-e MYSQL_ROOT_PASSWORD=123456 \
mysql:8.0.23
- 配置 restart=always,设置容器挂掉后总是重启。
- 配置 network=host,设置容器使用宿主机的网络。
- 挂载 /apps/mysql/my.cnf 来配置 Mysql 配置文件。
- 挂载 /apps/mysql/data 来配置 Mysql 数据存储。
- 挂载 /etc/localtime 来配置 Docker 运行容器的时区。
- 配置 MYSQL_ROOT_PASSWORD 环境变量,设置 Mysql 数据库的 root 密码。
创建鉴权用户
进入 MySQL 的 Master 容器中,使用客户端工具连接 MySQL Server:
$ docker exec -it mysql-a mysql -uroot -p123456
输入下面命令创建鉴权用户并赋与权限:
## 创建用户 repl 密码为 123456
$ create user 'repl'@'%' identified by '123456';
## 赋予用户 repl 权限
$ grant replication slave,replication client on *.* to 'repl'@'%';
## 刷新权限
$ flush privileges;
Docker 部署从节点
创建存储数据的目录
$ mkdir -p /apps/mysql/data
创建从节点配置文件
在目录 /apps/mysql
中创建 Mysql 从节点的配置文件,内容如下:
[mysqld]
port = 3306
max_connections = 2000
default-time_zone = '+8:00'
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
#GTID:
server_id = 2 #服务器id
gtid_mode = on #开启gtid模式
enforce_gtid_consistency = on #强制gtid一致性,开启后对于特定create table不被支持
#binlog
log_bin = mysql-binlog
log_slave_updates = on
binlog_format = row #强烈建议,其他格式可能造成数据不一致
#relay log
skip_slave_start = 1
default_authentication_plugin = 'mysql_native_password' #更改加密方式
read_only = on #设置只读
部署 Mysql 从节点
执行下面 Docker 命令,部署 Mysql 从节点:
$ docker run --name mysql-b -d \
--restart=always \
--network=host \
-e MYSQL_ROOT_PASSWORD=123456 \
-v /apps/mysql/my.cnf:/etc/mysql/conf.d/my.cnf \
-v /apps/mysql/data:/var/lib/mysql \
-v /etc/localtime:/etc/localtime \
mysql:8.0.23
从节点连接主节点
进入 Mysql Slave 镜像内部,使用客户端工具连接该 Msyql Server:
$ docker exec -it mysql-b mysql -uroot -p123456
配置从库连接主库:
$ CHANGE MASTER TO
master_host='192.168.2.11',
master_port=3306,
master_user='repl',
master_password='123456',
master_auto_position=1;
注:master_host 需要改为你的主节点 IP 地址,用户名密码要和上面创建的用户密码一致。
启动从库:
$ start slave;
查看连接状态:
$ show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.2.11
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-binlog.000003
Read_Master_Log_Pos: 886
Relay_Log_File: node-02-relay-bin.000003
Relay_Log_Pos: 1107
Relay_Master_Log_File: mysql-binlog.000003
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 886
Relay_Log_Space: 3129553
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1
Master_UUID: f5c69d56-694c-11eb-800e-000c29e6fc4e
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: f5c69d56-694c-11eb-800e-000c29e6fc4e:1-8
Executed_Gtid_Set: 13f0092c-694d-11eb-aba6-000c29262359:1-5,
f5c69d56-694c-11eb-800e-000c29e6fc4e:1-8
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
进行测试
主库创建测试库、表并插入数据
在主节点服务器上执行下面命令,进入 mysql 客户端:
$ docker exec -it mysql-master mysql -uroot -p123456
然后执行下面命令创建测试库、表并插入测试数据:
##创建名为 test 的数据库
$ CREATE DATABASE test;
##使用 test 数据库
$ USE test;
##创建 user 表
$ CREATE TABLE `user` (
`id` int(0) NOT NULL,
`name` varchar(20) NULL DEFAULT NULL
);
##插入测试数据
$ INSERT INTO `user` VALUES (1, 'mydlq');
从库查看是否存在库与表和对应数据
在从节点服务器上执行下面命令,进入 mysql 客户端:
$ docker exec -it mysql-slave mysql -uroot -p123456
然后执行下面命令
##查看全部数据库
$ SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
##使用 test 数据库
$ USE test;
##查看全部表
$ SHOW TABLES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
## 查看 user 表中数据
$ SELECT * FROM `user`;
+----+-------+
| id | name |
+----+-------+
| 1 | mydlq |
+----+-------+
如果从库中能正常执行上面命令且存在测试数据,则证明主从配置没有问题。
参考地址
- Mysql 5.7 Gtid内部学习(一) 导读
- MySQL的GTID复制比传统复制的优势
- Replication with Global Transaction Identifiers
本文转载自:「 小豆丁个人博客 」,原文:http://t.cn/A6tMD6yD ,版权归原作者所有。
推荐阅读 点击标题可跳转
《Docker中Image、Container与Volume的迁移》
免责声明:本文内容来源于网络,所载内容仅供参考。转载仅为学习和交流之目的,如无意中侵犯您的合法权益,请及时联系Docker中文社区!