MySQL 复制(Replication) 是官方提供的主从复制(源到副本的复制)方案,用于将一个 MySQL 的实例同步到另一个实例中。 这是使用最广泛的容灾方案(重点掌握)。
复制(Replication)
什么是复制
MySQL Replication是官方提供的主从同步方案,也是用的最广的同步方案。Replication(复制)使来自一个 MySQL数据库服务器(称为源(Source))的数据能够复制到一个或多个 MySQL 服务器(称为副本(Replica))。默认情况下,复制是异步的;副本不需要永久连接即可从源接收更新。根据配置,您可以复制所有数据库、指定数据库,甚至某个数据库中的指定表。
复制的优势:
- 高可用:通过配置一定的复制机制,MySQL 实现了跨主机的数据复制,从而获得一定的高可用能力,如果需要获得更高的可用性,只需要配置多个副本,或者进行级联复制就可以达到目的。
- 性能扩展:由于复制机制提供了多个数据备份,可以通过配置一个或多个副本,将读请求分发至副本节点,从而获得整体上读写性能的提升。
- 异地灾备:只需要将副本节点部署到异地机房,就可以轻松获得一定的异地灾备能力。实际当中,需要考虑网络延迟等可能影响整体表现的因素。
- 交易分离:通过配置复制机制,并将低频、大运算量的交易发送至副本节点执行,就可以避免这些交易与高频交易竞争运算资源,从而避免整体的性能问题。
缺点:
- 没有故障自动转移,容易造成单点故障
- 主库从库之间有主从复制延迟问题,容易造成最终数据的不一致
- 从库过多对主库的负载以及网络带宽都会带来很大的负担
应用场景
- 电子商务平台: 在电商平台中,主从复制可以用于实现读写分离,提高并发处理能力,同时确保数据的一致性。
- 社交网络: 在社交网络应用中,可以利用主从复制来提供快速的读取服务,同时将数据变更复制到从数据库以备份数据。
- 实时监控和报警系统: 在监控系统中,主从复制可以用于实现数据的分布式存储和快速数据查询。
- 新闻和媒体网站: 在高访问量的新闻网站中,可以使用主从复制来提供高可用性和快速的内容访问。
- 金融服务: 在金融行业,数据的安全性和可用性至关重要,主从复制可以用于数据备份和高可用性的实现。
复制的方式
MySQL 8.0支持多种复制方式:
- 传统的方法是基于从源的二进制日志(binlog)复制事件,并要求日志文件及其中的位置在源和副本之间进行同步。作为源(数据库更改发生的地方)的 MySQL 实例将更新和更改作为“事件”写入二进制日志。根据所记录的数据库更改,二进制日志中的信息以不同的日志格式存储。副本配置为从源中读取二进制日志,并在副本的本地数据库上执行二进制日志中的事件。
#获取binlog文件列表
mysql> show binary logs;
#查看指定binlog文件的内容
mysql> show binlog events in 'binlog.000003';
- 基于全局事务标识符(GTID)的方式。基于 GTID 的复制是完全基于事务的,所以很容易确定源和副本是否一致; 只要在源上提交的所有事务也在副本上提交,就可以保证两者之间的一致性。
复制的数据同步类型
MySQL 中的复制支持不同类型的同步。同步的原始类型是单向异步复制,其中一个服务器充当源,而一个或多个其他服务器充当副本。在 MySQL 8.0中,除了内置的异步复制之外,还支持半同步复制。使用半同步复制,在返回执行事务的会话之前,对源执行提交,直到至少有一个副本确认它已经接收并记录了事务的事件。MySQL 8.0还支持延迟复制,以使副本故意落后于源至少指定的时间。
异步复制
默认情况下,MySQL 采用异步复制的方式,执行事务操作的线程不会等复制 Binlog 的线程。具体的时序你可以看下面这个图:
MySQL 主库在收到客户端提交事务的请求之后,会先写入 Binlog,然后再提交事务,更新存储引擎中的数据,事务提交完成后,给客户端返回操作成功的响应。同时,从库会有一个专门的复制线程,从主库接收 Binlog,然后把 Binlog 写到一个中继日志里面,再给主库返回复制成功的响应。从库还有另外一个回放 Binlog 的线程,去读中继日志,然后回放 Binlog 更新存储引擎中的数据。
提交事务和复制这两个流程在不同的线程中执行,互相不会等待,这是异步复制。异步复制的劣势是,可能存在主从延迟,如果主节点宕机,可能会丢数据。
半同步复制
MySQL 从 5.7 版本开始,增加一种半同步复制(Semisynchronous Replication)的方式。这种机制与异步复制相比主要有如下区别:
- 主节点在收到客户端的请求后,必须在完成本节点日志写入的同时,还需要等待至少一个从节点完成数据同步的响应之后(或超时),才会响应请求。
- 从节点只有在写入 relay-log 并完成刷盘之后,才会向主节点响应。
- 当从节点响应超时时,主节点会将同步机制退化为异步复制。在至少一个从节点恢复,并完成数据追赶后,主节点会将同步机制恢复为半同步复制。
可以看出,相比于异步复制,半同步复制在一定程度上提高了数据的可用性,在未退化至异步复制时,如果主节点宕机,此时数据已复制至至少一台从节点。同时,由于向客户端响应时需要从节点完成响应,相比于异步复制,此时多出了主从节点上网络交互的耗时以及从节点写文件并刷盘的耗时,因此整体上集群对于客户端的响应性能表现必然有所降低。
半同步复制有两个重要的参数: - rpl_semi_sync_master_wait_slave_count(8.0.26之后改为rpl_semi_sync_source_wait_for_replica_count):至少等待数据复制到几个从节点再返回。这个数量配置的越大,丢数据的风险越小,但是集群的性能和可用性就越差。
- rpl_semi_sync_master_wait_point(8.0.26之后改为rpl_semi_sync_source_wait_point):这个参数控制主库执行事务的线程,是在提交事务之前(AFTER_SYNC)等待复制,还是在提交事务之后(AFTER_COMMIT)等待复制。默认是 AFTER_SYNC,也就是先等待复制,再提交事务,这样就不会丢数据。
设计理念:复制状态机——几乎所有的分布式存储都是这么复制数据的
在 MySQL 中,无论是复制还是备份恢复,依赖的都是全量备份和 Binlog,全量备份相当于备份那一时刻的一个数据快照,Binlog 则记录了每次数据更新的变化,也就是操作日志。这种基于“快照 + 操作日志”的方法,不是 MySQL 特有的。比如说,Redis Cluster 中,它的全量备份称为 Snapshot,操作日志叫 backlog,它的主从复制方式几乎和 MySQL 是一模一样的。Elasticsearch用的是 translog,它备份和恢复数据的原理和实现方式也是完全一样的。
任何一个存储系统,无论它存储的是什么数据,用什么样的数据结构,都可以抽象成一个状态机。存储系统中的数据称为状态(也就是 MySQL 中的数据),状态的全量备份称为快照(Snapshot),就像给数据拍个照片一样。我们按照顺序记录更新存储系统的每条操作命令,就是操作日志(Commit Log,也就是 MySQL 中的 Binlog)。
复制数据的时候,只要基于一个快照,按照顺序执行快照之后的所有操作日志,就可以得到一个完全一样的状态。在从节点持续地从主节点上复制操作日志并执行,就可以让从节点上的状态数据和主节点保持同步。
主从同步做数据复制时,一般可以采用几种复制策略。
- 性能最好的方法是异步复制,主节点上先记录操作日志,再更新状态数据,然后异步把操作日志复制到所有从节点上,并在从节点执行操作日志,得到和主节点相同的状态数据。异步复制的劣势是,可能存在主从延迟,如果主节点宕机,可能会丢数据。
- 另外一种常用的策略是半同步复制,主节点等待操作日志最少成功复制到 N 个从节点上之后,再更新状态,这种方式在性能、高可用和数据可靠性几个方面都比较平衡,很多分布式存储系统默认采用的都是这种方式。
基于binlog位点同步的主从复制原理
1、主库会生成多个 binlog 日志文件。
2、从库的 I/O 线程请求指定文件和指定位置的 binlog 日志文件(位点)。
3、主库 dump 线程获取指定位点的 binlog 日志。
4、主库按照从库发送给来的位点信息读取 binlog,然后推送 binlog 给从库。
5、从库将得到的 binlog 写到本地的 relay log (中继日志) 文件中。
6、从库的 SQL 线程读取和解析 relay log 文件。
7、从库的 SQL 线程重放 relay log 中的命令。
异步复制示例
1)快速创建mysql实例
参考:Docker 安装 MySQL8.0
利用Docker快速搭建Mysql8一主两从复制架构
2 ) 配置mysql主从复制
- 主节点
2.1)创建挂载目录
mkdir -p /mysql/replication/source/data /mysql/replication/source/conf /mysql/replication/source/log
2.2) 准备配置文件
vim /mysql/replication/source/conf/custom.cnf
[mysql]
# 设置mysql客户端默认编码
default-character-set=utf8
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
secure-file-priv= NULL
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
# 服务器唯一ID,默认是1
server-id=10
# 启用二进制日志
log-bin=mysql-bin
# 最大连接数
max_connections=1000
# 设置默认时区
default-time_zone='+8:00'
# 0:区分大小写
# 1:不区分大小写
lower_case_table_names=1
!includedir /etc/mysql/conf.d/
pid-file: 这是MySQL服务器的进程ID文件的位置。通过这个文件,您可以在系统上找到正在运行的MySQL服务器的进程。
socket: 这是MySQL服务器用于本地通信的Unix套接字文件的位置。
datadir: 这是MySQL服务器存储其数据文件的位置。
secure-file-priv: 这是一个用于限制LOAD_FILE()和SELECT … INTO OUTFILE命令的文件路径。如果此选项被设置,那么这两个命令只能用于读取在这个路径下的文件。设置为NULL表示禁用这个功能。
symbolic-links: 如果设置为0,MySQL服务器将不允许在数据目录中使用符号链接。这有助于防止安全风险。
server-id: 每个MySQL服务器实例在复制时需要有一个唯一的ID。这有助于区分不同的服务器,特别是在复制环境中。
log-bin: 启用二进制日志记录所有对数据库的更改,这对于复制和恢复操作是必要的。
max_connections: 这是MySQL服务器可以接受的最大并发连接数。
default-time_zone: 这设置了MySQL服务器的默认时区。
lower_case_table_names: 这决定了MySQL如何存储和比较表名。设置为1意味着表名不区分大小写(但在文件系统中它们仍然会区分大小写)。
!includedir /etc/mysql/conf.d/: 这告诉MySQL服务器从/etc/mysql/conf.d/目录中包含其他配置文件。这意味着该目录下的任何.cnf或.ini文件都会被合并到这个主配置文件中。
replicate_do_db : 待同步的数据库日志
replicate_ignore_db:不同步的数据库日志
2.3)运行mysql容器
# 创建主从复制的网络
docker network create --driver bridge mysql-source-replica
#运行mysql容器
docker run -d \
--name mysql-source \
--privileged=true \
--restart=always \
--network mysql-source-replica \
-p 3307:3306 \
-v /mysql/replication/source/data:/var/lib/mysql \
-v /mysql/replication/source/conf:/etc/mysql/conf.d \
-v /mysql/replication/source/log:/logs \
-e MYSQL_ROOT_PASSWORD=123456 \
-e TZ=Asia/Shanghai mysql:8.0.27 \
--lower_case_table_names=1
2.4)配置远程访问
docker exec -it mysql-source /bin/bash
mysql -u root -p
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
flush privileges;
- 从节点1
# 创建挂载目录
mkdir -p /mysql/replication/replica1/data /mysql/replication/replica1/conf /mysql/replication/replica1/log
#准备配置文件
vim /mysql/replication/replica1/conf/custom.cnf
[mysql]
# 设置mysql客户端默认编码
default-character-set=utf8
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
secure-file-priv= NULL
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
# 服务器唯一ID,默认是1
server-id=11
# 启用二进制日志
log-bin=mysql-bin
# 最大连接数
max_connections=1000
# 设置默认时区
default-time_zone='+8:00'
# 0:区分大小写
# 1:不区分大小写
lower_case_table_names=1
!includedir /etc/mysql/conf.d/
#运行mysql容器
docker run -d \
--name mysql-replica1 \
--privileged=true \
--restart=always \
--network mysql-source-replica \
-p 3308:3306 \
-v /mysql/replication/replica1/data:/var/lib/mysql \
-v /mysql/replication/replica1/conf:/etc/mysql/conf.d \
-v /mysql/replication/replica1/log:/logs \
-e MYSQL_ROOT_PASSWORD=123456 \
-e TZ=Asia/Shanghai mysql:8.0.27 \
--lower_case_table_names=1
#配置远程访问
docker exec -it mysql-replica1 /bin/bash
mysql -u root -p
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
flush privileges;
- 从节点2
mkdir -p /mysql/replication/replica2/data /mysql/replication/replica2/conf /mysql/replication/replica2/log
#准备配置文件
vim /mysql/replication/replica2/conf/custom.cnf
[mysql]
# 设置mysql客户端默认编码
default-character-set=utf8
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
secure-file-priv= NULL
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
# 服务器唯一ID,默认是1
server-id=12
# 启用二进制日志
log-bin=mysql-bin
# 最大连接数
max_connections=1000
# 设置默认时区
default-time_zone='+8:00'
# 0:区分大小写
# 1:不区分大小写
lower_case_table_names=1
!includedir /etc/mysql/conf.d/
#运行mysql容器
docker run -d \
--name mysql-replica2 \
--privileged=true \
--restart=always \
--network mysql-source-replica \
-p 3309:3306 \
-v /mysql/replication/replica2/data:/var/lib/mysql \
-v /mysql/replication/replica2/conf:/etc/mysql/conf.d \
-v /mysql/replication/replica2/log:/logs \
-e MYSQL_ROOT_PASSWORD=123456 \
-e TZ=Asia/Shanghai mysql:8.0.27 \
--lower_case_table_names=1
#配置远程访问
docker exec -it mysql-replica2 /bin/bash
mysql -u root -p
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
flush privileges;
3)主库配置复制用户
每个副本使用一个 MySQL 用户名和密码连接到源,因此在源上必须有一个用户帐户,副本可以使用该帐户进行连接。
# 连接主库mysql-source
CREATE USER 'fox'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
GRANT REPLICATION SLAVE ON *.* TO 'fox'@'%';
flush privileges;
4)查看 master 机器的状态
使用 SHOW MASTER STATUS 语句确定当前二进制日志文件的名称和位置
# 主库上执行
SHOW MASTER STATUS;
“文件”列显示日志文件的名称,“位置”列显示文件内的位置。
5) 从节点设置主库信息
文档:https://dev.mysql.com/doc/refman/8.0/en/replication-howto-slaveinit.html
在从库上执行 CHANGE REPLICATION SOURCE TO 语句(来自 MySQL 8.0.23)或 CHANGE MASTER TO语句(在 MySQL 8.0.23之前)
mysql> CHANGE MASTER TO
-> MASTER_HOST='source_host_name',
-> MASTER_USER='replication_user_name',
-> MASTER_PASSWORD='replication_password',
-> MASTER_LOG_FILE='recorded_log_file_name',
-> MASTER_LOG_POS=recorded_log_position;
Or from MySQL 8.0.23:
mysql> CHANGE REPLICATION SOURCE TO
-> SOURCE_HOST='source_host_name',
-> SOURCE_USER='replication_user_name',
-> SOURCE_PASSWORD='replication_password',
-> SOURCE_LOG_FILE='recorded_log_file_name',
-> SOURCE_LOG_POS=recorded_log_position;
从库1和从库2上执行
# from MySQL 8.0.23 执行下面的命令。
change replication source to source_host='192.168.65.185', source_user='fox', source_password='123456', source_port=3307, source_log_file='mysql-bin.000003', source_log_pos=1273, source_connect_retry=30;
source_host:主数据库的IP地址;
source_port:主数据库的运行端口;
source_user:在主数据库创建的用于同步数据的用户账号;
source_password:在主数据库创建的用于同步数据的用户密码;
source_log_file:指定从数据库要复制数据的日志文件,通过查看主数据的状态,获取File参数;
source_log_pos:指定从数据库从哪个位置开始复制数据,通过查看主数据的状态,获取Position参数;
source_connect_retry:连接失败重试的时间间隔,单位为秒。
6)开启从库
#开启从库
start slave; 或者 start replica;
#查看从库状态
show slave status \G; 或者 show replica status \G;
看到Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates基本说明配置成功了,已经开始了主从复制。
7)测试主从复制功能
测试脚本
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
CREATE DATABASE IF NOT EXISTS test;
USE test;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`name` varchar