Mysql 复制架构

数据拓展

  • 热备份:数据库在运行的过程中,对数据进行备份操作。相对的,还有冷备份,冷备份需要停机,然后对数据进行备份操作。
  • 多活:所谓的多活,就是让数据库机器节点会存在多个,避免单点情况的出现。
  • 故障切换:当一台数据库物理机出现异常状况时,可以自动的切换到其他物理机上。
  • 读写分离:当存在存在多台数据库物理机,将读写操作分别交给不同的机器完成。
  • 负载均衡:假设当存在多台数据库物理机接收读请求时,多个请求会均匀的分配到不同的机器上,避免大量请求压在某一台机器上。

常见架构

没有百分百的完美架构,只有适合的架构

理解 mysql 的分库分表,先了解 mysql 的架构设计

在 mysql 架构中,经常会使用到的就是读写分离,此设计理念的基础上常见架构有

  • 一主一从或多从: 一个 mysql 数据库主节点,一个或者多个从节点.主节点与从节点进行数据同步

  • 主主复制: 两个 mysql 主节点,主节点与主节点之间进行数据同步

  • 级联复制: 类似一主多从架构,但是从节点分级同步

  • 主主与级联复制结合: 双主节点同步,同时从节点分级同步

主从模式

主从简介

主从模式是使用的最多的 mysql 高可用架构。

存在一台 master 作为写机,一个或多个 slave 作为读机,实现读写分离。

之所以这么设计是因为在实际的情况下,读的请求量一般是远远大于写请求。架构图如下

优点:

读与写的节点分离,数据写入 master 节点后,再由 master 节点将数据复制到 slave 节点上

缺点:

  • master 是单点存在的,如果要对 master 进行停机维护,无法接收写请求
  • master 需要将写入数据复制到各个 slave 节点,复制是有一定的时间延迟的,因此有可能出现查询数据不一致
  • 对 master 进行停机维护,需将某一个 slave 提升为新的 master 节点,选举规则需要进行自定义
  • 当 slave 被提升为新的 master 后,可能会造成新的 master 节点与旧 master 的数据不一致

主从搭建

虚拟机中安装 Docker:Docker 安装

创建容器

下载 mysql 镜像

1
docker pull docker.io/mysql:5.7

复制两台虚拟机出来,分开创建两个 MySQL 容器,分别充当 master 和 slave

首先是 mysqlm1 容器,即 master 节点

1
2
3
4
5
6
7
8
9
10
11
docker run -d \
--name mysqlm1 \
-p 3306:3306 \
--privileged=true -it \
-e MYSQL_ROOT_PASSWORD=123456 \
-e MYSQL_USER=shiguang \
-e MYSQL_PASSWORD=123456 \
-v /home/mysql/docker-data/m1/conf:/etc/mysql/conf.d \
-v /home/mysql/docker-data/m1/data/:/var/lib/mysql \
-v /home/mysql/docker-data/m1/logs/:/var/log/mysql \
mysql:5.7

另一台虚拟机上的 mysqls1 容器,即 slave 节点

1
2
3
4
5
6
7
8
9
10
11
docker run -d \
--name mysqls1 \
-p 3306:3306 \
--privileged=true -it \
-e MYSQL_ROOT_PASSWORD=123456 \
-e MYSQL_USER=shiguang \
-e MYSQL_PASSWORD=123456 \
-v /home/mysql/docker-data/s1/conf:/etc/mysql/conf.d \
-v /home/mysql/docker-data/s1/data/:/var/lib/mysql \
-v /home/mysql/docker-data/s1/logs/:/var/log/mysql \
mysql:5.7

修改 master 配置

新增 MySQL 配置文件my.cnf,路径为创建容器时映射的路径

对于 mysqlm1 容器,路径为/home/mysql/docker-data/m1/conf目录内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html

[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M

character_set_server=utf8
init_connect='SET NAMES utf8'

# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

lower_case_table_names=1
#指定主机号,不允许出现重复
server-id=129
#开启binlog
log-bin=mysql-bin
auto_increment_increment=2
auto_increment_offset=1

#rpl_semi_sync_master_enabled=1
#rpl_semi_sync_master_timeout=10000

在 master 的 docker 容器中添加 mysql 权限,开启备份机复制,并且设置备份用户信息

进入 mysqlm1 容器

1
docker exec -it mysqlm1 /bin/bash

创建一个从服务,为从服务设置一个用户repluser 并添加权限

1
GRANT REPLICATION SLAVE,FILE,REPLICATION CLIENT ON *.* TO 'repluser'@'%' IDENTIFIED BY '123456';

刷新权限

1
FLUSH PRIVILEGES;

退出容器,并重启容器

1
2
exit
docker restart mysqlm1

查看容器的的 binlog 信息

binlog,即二进制日志,它记录了数据库上的所有改变,并以二进制的形式保存在磁盘中;

它可以用来查看数据库的变更历史、数据库增量备份和恢复、Mysql 的复制(主从数据库的复制)。

Position 代表现在的数据已经写到了 154 行,下一行是 155 行

1
show master status;

修改 slave 配置

新增 MySQL 配置文件my.cnf,路径为创建容器时映射的路径

对于 mysqls1 容器,路径为/home/mysql/docker-data/s1/conf目录内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html

[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M

character_set_server=utf8
init_connect='SET NAMES utf8'

# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

lower_case_table_names=1
#指定主机号,不允许出现重复
server-id=130
#开启binlog
log-bin=mysql-bin
auto_increment_increment=2
auto_increment_offset=1

#rpl_semi_sync_master_enabled=1
#rpl_semi_sync_master_timeout=10000[mysqld]

character_set_server=utf8
init_connect='SET NAMES utf8'

# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

lower_case_table_names=1
#指定主机号,不允许出现重复
server-id=130
#开启binlog
log-bin=mysql-bin
auto_increment_increment=2
auto_increment_offset=1

重启容器,接着进入到 mysqls1 容器,设置 master 信息,用于标注当前 slave 的 master 是谁

1
docker exec -it mysqls1 /bin/bash

格式

1
2
3
4
5
6
7
change master to
master_host='master的ip',
master_port=master的端口号,
master_user='repluser',
master_password='123456',
master_log_file='master中的binlob文件名',
master_log_pos=master中的position位置信息,从哪一行开始监听;

根据上面的信息,我输入的是

1
2
3
4
5
6
7
change master to
master_host='192.168.200.15',
master_port=3306,
master_user='repluser',
master_password='123456',
master_log_file='mysql-bin.000001',
master_log_pos=154;

完成后,还需要开启 slave 中的IOSQL线程,这两个线程主要用于 slave 中进行数据备份,可以先查看 slave 中这两个线程的状态

1
show slave status\G;

我们发现在 slave 中,这两个线程是关闭的,需要将这两个线程进行开启

1
start slave;

到此,mysql 主从复制就已经搭建完毕

注意:如果搭建成功后却无法同步,可以通过 reset slave 重新设置主从关系。

测试

case1: 查看主从相关信息

查看 slave 中的 binlog 是否已经开启

1
show global variables like "%log%";

查看 master、slave 中的进程信息

1
show processlist;

master 节点信息

slave 节点信息

case2: 数据库同步测试

结论: 从服务中新增数据无法同步到主服务中去, 主从同步是单向的

主从复制原理

异步复制

Mysql 在进行复制操作时,默认是基于异步复制完成的。

  1. 事务提交到 master
  2. master 接收到应用事务提交请求后,更新内部的 binlog 日志,让 mysql 引擎执行事务操作,并返回给客户端执行结果信息。同时在 master 中会存在一个事件监听,其会一直监听着 master 中 binlog 日志文件的改变,一旦发现日志文件发生改变,触发 dump 线程
  3. dump 线程被触发后,通知 slave 中的 IO 线程现在有事务操作要进行同步
  4. slave 中 IO 线程接收到通知后,会从 slave 中relay-log.info文件中获取 slave 中的 binlog 日志文件和 pos 位置信息。接着会把这部分信息发送给 master 的 dump 线程
  5. master 的 dump 线程收到这些信息后,会根据 slave 发送的 binlog 日志文件和 pos 位置,将最新的 binlog 日志和 pos 位置后面的内容同步给 slave 的 IO 线程
  6. slave 的 IO 线程接收到这些信息后,会将这部分内容同步到 slave 中的 relay-bin 文件中
  7. 当 relay-bin 文件发生改变后,触发 slave 线程执行 sql 操作【异步】
  8. 当 slave 向 relay-bin 写入完成后,会向 master 返回一个 ACK 消息,同步成功。

对于这一系列的操作,可以发现 master 和 slave 在进行同步时是以异步的方式完成的,master 写入完 binlog 后,会马上通过引擎进行事务提交并向客户端返回响应,对于与 slave 同步的操作,则是异步完成的。

优点:

效率高

缺点:

可能出现数据不一致

半同步复制

半同步复制与异步复制的工作流程大体相似

不同点: 当 master 中的 binlog 日志写入完成后,其不会马上通过引擎进行事务提交,而会处于等待,等到 slave 同步完成向 master 返回 ACK 通知后,才会唤醒等待,继续向下执行。

  • 等待的时长,默认为 10 秒,但该时间可以配置

  • 尽量的避免了主从数据不一致,但造成吞吐量的降低

  • mysql 兜底方案: 使用半同步复制进行备份时 slave 节点挂掉了,那么当 master 等待 10 秒后,仍然会进行引擎提交,同时会将半同步复制切换为异步复制。等到 slave 节点重启后,又会自动的从异步复制切换到半同步复制。

半同步复制实现

异步复制(Asynchronous replication)

MySQL 默认的复制即是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果 crash 掉了,此时主上已经提交的事务可能并没有传到从上,如果此时,强行将从提升为主,可能导致新主上的数据不完整。

全同步复制(Fully synchronous replication)

指当主库执行完一个事务,所有的从库都执行了该事务才返回给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响。

半同步复制(Semisynchronous replication)

介于异步复制和全同步复制之间,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到 relay log 中才返回给客户端。相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,这个延迟最少是一个 TCP/IP 往返的时间。所以,半同步复制最好在低延时的网络中使用。

1、分别进入两个 mysql 容器,加载 lib,主从节点都要配置,因为主从节点间会存在切换。

1
2
install plugin rpl_semi_sync_master soname 'semisync_master.so';
install plugin rpl_semi_sync_slave soname 'semisync_slave.so';

master 和 slave 都查看一下插件信息

1
show plugins;

2、启用半同步(务必先启用从库,再启用主库)

从库(1:启用,0:禁止)

1
set global rpl_semi_sync_slave_enabled= 1;

主库

1
2
3
set global rpl_semi_sync_master_enabled= 1;
# 单位为ms
set global rpl_semi_sync_master_timeout=10000;

3、从库重启 IO Thread

1
2
stop slave io_thread;
start slave io_thread;

4、在 master 节点上查看启动状态

1
show global status like "%sync%";

在 master 节点上查询参数信息

1
show global variables like '%sync%';

事实上,半同步复制并不是严格意义上的半同步复制

当半同步复制发生超时时(由 rpl_semi_sync_master_timeout 参数控制,单位是毫秒,默认为 10000,即 10s),会暂时关闭半同步复制,转而使用异步复制。当 master dump 线程发送完一个事务的所有事件之后,如果在 rpl_semi_sync_master_timeout 内,收到了从库的响应,则主从又重新恢复为半同步复制。

效果测试

  • 正常的向 master 中添加数据,slave 可以进行正常数据更新

  • 关闭 slave 的 IO,再次向 master 中添加数据
1
2
stop slave io_thread;
INSERT INTO `t_user` VALUES ('3', 'ding', '111', '武汉');

此时复制机制会由半同步复制转换为异步复制,当再次向 master 中添加数据,不会再次出现等待

  • slave 中重新开启 IO Thread,异步复制会再次转换为半同步复制
1
start slave io_thread;

在 slave IO Thread 关闭这段时间内的数据,会同步到 slave 中,不会出现数据丢失

主主复制

简介

对于主从复制来说,其内部会存在一台 master 以及一台或多台 slave。但有一个非常明显的问题,master 是单点存在。一旦 master 宕机,则无法进行数据的写入。为了解决这个问题,可以使用主主复制架构。

在主主复制架构中,会存在两台 master,没有 slave。并且会对这两台 master 进行读写分离,两台 master 会进行相互的复制, 架构图如下:

在此架构中,两台 master 会进行双向复制,为什么这么做呢? 因为假设现在负责写的 master 宕机了,那么写的工作则会交给之前负责读的服务器来完成,相当于它即负责写又负责读。等到原先负责写的 master 恢复了,其在继续负责写工作。 反之亦然。因此才需要两者间进行双向复制。

缺点: 读请求的并发量过大,服务可能产生宕机, 主主复制架构直接使用的情况较少。

主主搭建

因为在 master 和 slave 虚拟机上,已经创建了主从模式。

所以我在两台虚拟机上再各自创建一个容器,用于测试搭建主主模式。

分别在 master 和 slave 虚拟机上创建一个名为 mysqlm2 的容器。

1
2
3
4
5
6
7
8
9
10
11
docker run -d \
--name mysqlm2 \
-p 3307:3306 \
--privileged=true -ti \
-e MYSQL_ROOT_PASSWORD=123456 \
-e MYSQL_USER=shiguang \
-e MYSQL_PASSWORD=123456 \
-v /home/mysql/docker-data/m2/conf:/etc/mysql/conf.d \
-v /home/mysql/docker-data/m2/data/:/var/lib/mysql \
-v /home/mysql/docker-data/m2/logs/:/var/log/mysql \
mysql:5.7

修改配置文件

1
vi /home/mysql/docker-data/m2/conf/my.cnf

分别指定不同的主机号,我指定为 131 和 132

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html

[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M

character_set_server=utf8
init_connect='SET NAMES utf8'

# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

lower_case_table_names=1
#指定主机号,不允许出现重复
server-id=131
#开启binlog
log-bin=mysql-bin
auto_increment_increment=2
auto_increment_offset=1

#rpl_semi_sync_master_enabled=1
#rpl_semi_sync_master_timeout=10000

在主机号为 130 和 131 的容器内添加相关配置,虽然是主主模式也要添加从用户,(互为对方的从用户)

1
2
docker exec -it mysqlm2 /bin/bash
mysql -uroot -p123456
1
2
3
4
#添加权限
GRANT REPLICATION SLAVE,FILE,REPLICATION CLIENT ON *.* TO 'repluser'@'%' IDENTIFIED BY '123456';
#刷新权限
FLUSH PRIVILEGES;

从新启动两个容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#分别在在主机号为130131的容器上运行,查看File和Position
show master status;

#在主机号为131的容器上执行主主关联到130的容器
change master to
master_host='192.168.200.15',
master_port=3307,
master_user='repluser',
master_password='123456',
master_log_file='mysql-bin.000001',
master_log_pos=154;

#在主机号为130的容器上执行主主关联到131的容器
change master to
master_host='192.168.200.25',
master_port=3307,
master_user='repluser',
master_password='123456',
master_log_file='mysql-bin.000001',
master_log_pos=154;

#130131分别执行一次,让主主同步生效
start slave;

查看主机号为 130 的进程列表

1
show processlist;

查看主机号为 131 的进程列表

级联复制(了解)

写请求的入口为一个,但当 master 向 slave 进行复制时,对于 slave 可以分为多层, master 只要向其中两台 slave 复制即可,然后再由 slave 将其数据复制到后面更多的 slave 中。通过这种方式可以减轻 master 向 slave 复制的 IO 压力。但是这种架构会使 slave 的延迟会加大,架构如下图:

双主与级联复制(了解)

对于 master 在前面几种架构设计中,都存在单点问题, 对于 master 单点问题的解决,可以采用当前的架构。

通过这种架构不仅可以解决 master 单点的问题,也可以解决 slave 延迟的问题, 架构图如下: