目录
🥝1.2.2、开启主库的Binlog,并设置server-id
🎽3.2. 4、获取到在执行slaveof命令之前master服务器的数据。
前言
主要介绍mysql的主从复制以及redis的主从复制
由浅入深的明白原理以及如何操作
并且参考了很多博主的文章
一、案例前置知识点
1、 mysq支持的复制类型
1) 基于语句的复制。在服务器上执行sql语句,在从服务器上执行同样的语句,mysql默认采用基于语句的复制,执行效率高。
2) 基于行的复制。把改变的内容复制过去,而不是把命令在从服务器上执行一遍。
3) 混合类型的复制。默认采用基于语句的复制,一旦发现基于语句无法精确复制时,就会采用基于行的复制。
2、 复制的工作过程
1) 在每个事务更新数据完成之前,master在二进制日志记录这些改变。写入二进制日志完成后,master通知存储引擎提交事务。
2) Slave将master的binary log复制到其中继日志。首先slave开始一个工作线程(I/O),I/O线程在master上打开一个普通的连接,然后开始binlog dump process。binlog dump process从master的二进制日志中读取事件,如果已经跟上master,它会睡眠并等待master产生新的事件,I/O线程将这些事件写入中继日志。
3) Sql slave thread(sql从线程)处理该过程的最后一步,sql线程从中继日志读取事件,并重放其中的事件而更新slave数据,使其与master中的数据一致,只要该线程与I/O线程保持一致,中继日志通常会位于os缓存中,所以中继日志的开销很小。
redis是在开头上主从复制,而mycat在接入点上主从复制,所以缺点是会导致延时性问题
一. 主从复制
主从复制是指:数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点
那么主从复制的原理对应什么 框架 呢?
对于主从复制的优缺点如下:
优点:
主从复制可以确保数据的安全,并且在主机宕机之后,从机也可以继续工作,提高了服务器整体性能。
1 、数据预热,假如主数据库宕机,可使用从数据库可继续工作,避免数据的丢失,更好的实现负载均衡。
2、降低磁盘访问的频率,提高服务器的 I/O 性能;
3、读写分离,使数据库能支持更大的并发;
1、主从复制方式
🤙1.1 同步复制
所谓的同步复制,意思是master的变化,必须等待slave-1,slave-2,...,slave-n完成后才能 返回。这样,显然不可取,也不是MYSQL复制的默认设置。比如,在WEB前端页面上,用户增加了条记录,需要等待很长时间。
使用主从同步的好处:
- 通过增加从服务器来提高数据库的性能,在主服务器上执行写入和更新,在从服务器上向外提供读功能,可以动态地调整从服务器的数量,从而调整整个数据库的性能。
- 提高数据安全-因为数据已复制到从服务器,从服务器可以终止复制进程,所以,可以在从服务器上备份而不破坏主服务器相应数据
- 在主服务器上生成实时数据,而在从服务器上分析这些数据,从而提高主服务器的性能
注意,mysql是异步复制的,而MySQL Cluster是同步复制的。有很多种主从同步的方法,但核心的方法有两种,Statement Based Replication(SBR)基于SQL语句的复制,另一种是Row Based Replication(RBR)基于行的复制,也可以使用Mixed Based Replication(MBR)。在mysql5.6中,默认使用的是SBR。而mysql 5.6.5和往后的版本是基于global transaction identifiers(GTIDs)来进行事务复制。当使用GTIDs时可以大大简化复制过程,因为GTIDs完全基于事务,只要在主服务器上提交了事务,那么从服务器就一定会执行该事务。
通过设置服务器的系统变量binlog_format来指定要使用的格式:
1.SBR:当使用二进制日志时,主服务器会把SQL语句写入到日志中,然后从服务器会执行该日志,这就是SBR,在mysql5.1.4之前的版本都只能使用这种格式。使用SBR会有如下
🎽长处:
- 日志文件更小
- 记录了所有的语句,可以用来日后审计
🤡弊端:
- 使用如下函数的语句不能被正确地复制:load_file(); uuid(), uuid_short(); user(); found_rows(); sysdate(); get_lock(); is_free_lock(); is_used_lock(); master_pos_wait(); rand(); release_lock(); sleep(); version();
- 在日志中出现如下警告信息的不能正确地复制:[Warning] Statement is not safe to log in statement format.
- 或者在客户端中出现show warnings
- Insert … select语句会执行大量的行级锁表
- Update语句会执行大量的行级锁表来扫描整个表
2.RBR:主服务器把表的行变化作为事件写入到二进制日志中,主服务器把代表了行变化的事件复制到从服务中,使用RBR的;
🎽长处:
- 所有的数据变化都是被复制,这是最安全的复制方式
- 更少的行级锁表
🤡弊端:
- 日志会很大
- 不能通过查看日志来审计执行过的sql语句,不过可以通过使用mysqlbinlog
- --base64-output=decode-rows --verbose来查看数据的 变动
- MBR:既使用SBR也使用RBR,默认使用SBR
🌈1.1.1、主从同步机制
Mysql服务器之间的主从同步是基于二进制日志机制,主服务器使用二进制日志来记录数据库的变动情况,从服务器通过读取和执行该日志文件来保持和主服务器的数据一致。
在使用二进制日志时,主服务器的所有操作都会被记录下来,然后从服务器会接收到该日志的一个副本。从服务器可以指定执行该日志中的哪一类事件(譬如只插入数据或者只更新数据),默认会执行日志中的所有语句。
每一个从服务器会记录关于二进制日志的信息:文件名和已经处理过的语句,这样意味着不同的从服务器可以分别执行同一个二进制日志的不同部分,并且从服务器可以随时连接或者中断和服务器的连接。
主服务器和每一个从服务器都必须配置一个唯一的ID号(在my.cnf文件的[mysqld]模块下有一个server-id配置项),另外,每一个从服务器还需要通过CHANGE MASTER TO语句来配置它要连接的主服务器的ip地址,日志文件名称和该日志里面的位置(这些信息存储在主服务器的数据库里);
🌈1.1.2、配置主从同步
有很多种配置主从同步的方法,可以总结为如下的步骤:
1.在主服务器上,必须开启二进制日志机制和配置一个独立的ID
2.在每一个从服务器上,配置一个唯一的ID,创建一个用来专门复制主服务器数据的账号
3.在开始复制进程前,在主服务器上记录二进制文件的位置信息
4.如果在开始复制之前,数据库中已经有数据,就必须先创建一个数据快照(可以使用mysqldump导出数据库,或者直接复制数据文件)
5.配置从服务器要连接的主服务器的IP地址和登陆授权,二进制日志文件名和位置
🌈1.1.3、配置主服务器
🍎1.更改配置文件,首先检查你的主服务器上的my.cnf文件中是否已经在[mysqld]模块下配置了log-bin和server-id;
[mysqld]
log-bin=mysql-bin
server-id=1
注意上面的log-bin和server-id的值都是可以改为其他值的,如果没有上面的配置,首先关闭mysql服务器,然后添加上去,接着重启服务器;
🍎2.创建用户,每一个从服务器都需要用到一个账户名和密码来连接主服务器,可以为每一个从服务器都创建一个账户,也可以让全部服务器使用同一个账户。下面就为同一个ip网段的所有从服务器创建一个只能进行主从同步的账户。
首先登陆mysql,然后创建一个用户名为rep,密码为123456的账户,该账户可以被192.168.253网段下的所有ip地址使用,且该账户只能进行主从同步
mysql > grant replication slave on *.* to ‘rep’@‘192.168.253.%’ identified by ‘123456’;
🍎3.获取二进制日志的信息并导出数据库,步骤:
首先登陆数据库,然后刷新所有的表,同时给数据库加上一把锁,阻止对数据库进行任何的写操作;
mysql > flush tables with read lock;
然后执行下面的语句获取二进制日志的信息:
mysql > show master status;
获得如图信息:
File的值是当前使用的二进制日志的文件名,Position是该日志里面的位置信息(不需要纠结这个究竟代表什么),记住这两个值,会在下面配置从服务器时用到。
注意:如果之前的服务器并没有配置使用二进制日志,那么使用上面的sql语句会显示空,在锁表之后,再导出数据库里的数据(如果数据库里没有数据,可以忽略这一步)。
[root@localhost backup]# mysqldump -uroot -p'123456' -S /data/3306/data/mysql.sock --all-databases > /server/backup/mysql_bak.$(date +%F).sql
如果数据量很大,可以在导出时就压缩为原来的大概三分之一:
[root@localhost backup]# mysqldump -uroot -p'123456' -S /data/3306/data/mysql.sock --all-databases | gzip > /server/backup/mysql_bak.$(date +%F).sql.gz
这时可以对数据库解锁,恢复对主数据库的操作:
mysql > unlock tables;
🌈1.1.4、配置从服务器
首先检查从服务器上的my.cnf文件中是否已经在[mysqld]模块下配置leserver-id
[mysqld]
server-id=2
注意上面的server-id的值都是可以改为其他值的(建议更改为ip地址的最后一个字段),如果没有上面的配置,首先关闭mysql服务器,然后添加上去,接着重启服务器。
如果有多个从服务器上,那么每个服务器上配置的server-id都必须不一致。从服务器上没必要配置log-bin,当然也可以配置log-bin选项,因为可以在从服务器上进行数据备份和灾难恢复,或者某一天让这个从服务器变成一个主服务器。
如果主服务器导出了数据,下面就导入该文件,如果主服务器没有数据,就忽略这一步。
[root@localhost ~]# mysql -uroot -p'123456' -S /data/3306/data/mysql.sock < /server/backup/mysql_bak.2015-07-01.sql
如果从主服务器上拿过来的是压缩文件,就先解压再导入
🍋配置同步参数,登陆mysql,输入如下信息:
mysql> CHANGE MASTER TO
-> MASTER_HOST='master_host_name',
-> MASTER_USER='replication_user_name',
-> MASTER_PASSWORD='replication_password',
-> MASTER_LOG_FILE='recorded_log_file_name',
如图所示:
🍋启动主从同步进程:
mysql > start slave;
🍋检查状态:
mysql > show slave status \G
如图所示:
上面的两个进程都显示YES则表示配置成功。
🌈1.1.4、使用主从同步来备份
使用mysqldump来备份,把主服务器的数据复制到从服务器上,然后备份从服务器的数据,在数据量不是很大的时候使用mysqldump命令,对于很大的数据库,就直接备份数据文件。
步骤:(以下的所有操作都在从服务器上进行)
🧣1.首先暂停从服务器的复制进程
shell > mysqladmin stop-slave
或者只是暂停SQL进程,从服务器仍然能接收二进制日志的事件,但不会执行这些事件,这样能在重启SQL进程时加快复制进度
shell > mysql -e ‘stop slave sql_thread;’
🧣2.使用mysqldump导出全部或部分的数据库
shell > mysqldump --all-databases > fulldb.dump
🧣3.在导出数据库后,重启复制进程
shell > mysqladmin start-slave
🌈1.1.5、备份原始文件
为了保证数据文件的完整性,在备份之前首先关闭从服务器,步骤:
🐳1.关闭从服务器:
shell > mysqladmin shutdown
🐳2.复制数据文件,可以使用压缩命令,假如当前目录就是数据库的数据目录,在my.cnf文件中的配置项datadir的值就是该目录的位置
shell > tar cf /tmp/dbbackup.tar ./data
🐳3.然后再启动mysql服务器
🌈1.1.6、主从同步的小技巧、排错
🎯1.主从同步的小技巧
主服务器第一次导入数据,如果你从其他地方拿来了要导入到主服务器中的数据,此时只要在主服务器中导入一次即可,因为这些数据会自动发送到从服务器中,在主服务器上使用命令
shell > mysql -h master < other_data.sql
增加从服务器,本来已经至少有一个从服务器时(暂时命名为slave1),决定再添加其余的从服务器(slave2),此时就不需要像上面那样去操作主服务器,只要复制一个已经存在的从服务器就可以了。
🎯2.排错
Slave_IO_Running: NO
这是一个很常见的错误(我也曾对这个错误咬牙切齿),总结起来就三个原因:
- 主服务器的网络不通,或者主服务器的防火墙拒绝了外部连接3306端口
- 在配置从服务器时,输错了ip地址和密码,或者主服务器在创建用户时写错了用户名和密码
- 在配置从服务器时,输错了主服务器的二进制日志信息
排错过程:(主服务器ip:192.168.12.139,从服务器ip:192.168.12.204)
第1步就是检查错误日志,如果不能快速排错,可以按我的步骤试试:
🍎1.首先在从服务器上执行ping程序,确定能ping通主服务器
在从服务器上执行mysq的远程连接:
[root@slave204 log]# mysql -urep -p -h 192.168.12.139 -P3306
如果显示ERROR 1045 (28000): Access denied for user 'test'@'192.168.12.204' (using password: YES)则跳转到第3
🍎2.登陆主服务器的mysql,查看所有的用户
mysql > select user,host from mysql.user;
上图就是我的错误根源,可以看到用户名完全写错了,先删除错误的用户:
mysql > drop user “rep@192.168.12.%”@”%”;
再重新创建用户:
mysql > grant replication slave on *.* to ‘rep’@‘192.168.12.%’ identified by ‘123456’;
mysql > flush privileges;
🍎3.假如用户名没有错,那么如何排除是否是输入的密码错误呢?
额,我也想知道方法。最好就是多输入几遍,或者重新创建用户名和密码来测试。问题还没有解决,转到4
🍎4.在你的防火墙中添加3306端口
[root@localhost mysql]# firewall-cmd --zone=public --add-port=3306/tcp --permanent
[root@localhost mysql]# firewall-cmd --reload
再关闭selinux:
[root@slave204 log]# vi /etc/sysconfig/selinux
把SELINUX=enforcing改为SELINUX=disabled
[root@slave204 log]# source /etc/sysconfig/selinux
登录主服务器,查看服务器状态
mysql > show master status \G
然后重新配置一次从服务器,在配置之前首先关闭主从同步进程
mysql > stop slave;
之外的方法,我也没试过了,请踊跃尝试其他方式;
🤙1.2异步复制
MySQL的复制默认是异步的,主从复制至少需要两个MYSQL服务,这些MySQL服务可以分布在不同的服务器上,也可以在同一台服务器上。
MySQL主从异步复制是最常见的复制场景。数据的完整性依赖于主库BINLOG的不丢失,只要主库的BINLOG不丢失,那么就算主库宕机了,我们还可以通过BINLOG把丢失的部分数据通过手工同步到从库上去。
注意:主库宕机的情况下,DBA可以通过mysqlbinlog工具手工访问主库binlog,抽取缺失的日志并同步到从库上去;也可以通过配置高可用MHA架构来自动抽取缺失的数据补全从库,或者启用Global Transaction Identifiers(GTID)来自动抽取缺失binlog到从库。
MySQL在BINLOG中记录事务(或SQL语句),也就是说对于支持事务的的引擎(例如InnoDB)来说,每个事务提交时都需要写BINLOG;对于不支持事务的引擎(例如MyISAM)来说,每个SQL语句执行完成时,都需要写BINLOG。为了保证Binlog的安全,MySQL引入sync_binlog参数来控制BINLOG刷新到磁盘的频率。
命令:
show variables like 'sync_binlog';
🥝1.2.1异步复制配置
前提:主从数据库的版本一致
主库:在主库上设置一个用于复制的账号,并赋予replication slave权限
命令:
>grant replication slave on *.* to 'test'@'192.168.142.176' identified by '123';
>flush privileges;
🥝1.2.2、开启主库的Binlog,并设置server-id
命令:
vim /mysql/3306/my.cnf
[mysqld]
log-bin = mysql-bin
server-id = 1
🥝1.2.3、设置读锁
在主库上设置读锁定有效,目的是确保在此过程中没有对数据的操作,避免主从数据不一致;
> flush tables with read lock;
🥝1.2.4、获取主库日志名和偏移量
获取主库上当前Binlog的日志名和偏移量,为了在从库中,从此位置开始复制;
命令:
>show master status;
🥝1.2.5、备份主库的数据
我这里的主从库是在一台服务器上,如果不在一台机器上,先将主库数据打包,到后面再传过去就可以了。这里我将主库的数据备份到了/test目录下,其实可以不用备份,后面直接cp到从库,但还是感受一下备份的安全感;
命令:
cp /mysql/3306/data /test
🥝1.2.6、恢复主库的写操作
命令:
> unlock tables;
从库:
🥝1.2.7、备份的主库数据拷贝到从库
将第5步中备份的主库数据拷贝到从库上命令:
cp /test /mysql/3307/data
🥝1.2.8、从库配置
从库配置文件中增加server-id,注意这个值是唯一的,要区别于主库和其他从库;
命令:
vim /mysql/3307/my.cnf
[mysqld]
server-id = 2
🥝1.2.9、启动从库
启动从库的时候,我们加一个--skip-slave-start参数,表示这个从库启动是不自动同步,也就是不启动主从复制进程,方便后面对从库的进一步配置
/usr/local/mysql/bin/mysqld_safe --defaults-file=/mysql/3308/my.cnf --skip-slave-start
注意:由于我这里用的是MySQL的多实例,所以启动的时候需要用--defaults-file参数指定一下具体实例的my.cnf文件,如果主从库不再同一台机器上,就不需要加这个参数了,默认是/etc/my.cnf
🥝1.2.10、从库设置主从信息
这里要注意的是配置要参照之前在主库的配置;
> change master to
-> master_host='192.168.142.176', #主库服务器的IP
-> master_port=3306, #主库端口
-> master_user='test', #主库用于复制的用户
-> master_password='123', #用户密码
-> master_log_file='mysql-bin.000002', #主库的Binlog日志名
-> master_log_pos=570; #Binlog日志的偏移量(从什么位置开始复制)
🥝1.2.11、从库启动slave线程,并检查
>start slave;
>show processlist \G
🥝1.2.12、测试主从复制
主库上创建一张表,并加入数据,看从库是否复制
主:
>use test
>create table emp (id int,name varchar(11));
>insert into emp values(1,’tom’),(2,’jerry’);
然后在从库上查看:
>use test
>show tables;
>select * from emp;
可以看到,已经成功复制过去了;
🥝1.2.13、排错
如果在从库发现没有复制过来数据,先看看slave的状态,试用一下命令:
>show slave status \G
- 上图中,前面几行是主库的信息,红色框内的两个参数必须要是yes,才能正确复制
- Slave_IO_Running表示从库的IO线程,Slave_SQL_running表示从库的SQL线程,两个必须都为yes。
- 如果为no,可以去从库的错误日志(log-err)中查看原因
到这里,MySQL的异步复制就配置完成了~
注意:
在异步复制中,主库执行完操作后,写入binlog日志后,就返回客户端,这一动作就结束了,并不会验证从库有没有收到,完不完整,所以这样可能会造成数据的不一致。
说到底,复制过程中数据是否一致,主要取决于Binlog日志的安全性与完整性
在MySQL中,有sync_binlog=n这一参数,他的值表示每进行n次事务提交,MySQL就将Binlog刷新到磁盘。如果这个值为1,就代表每提交一次事务(SQL),就将Binlog往磁盘刷新一次,这样一来,就算数据库宕机了,那么最多只能损失一次事务的数据。
但是,一旦多个事务并发提交时,由于受sync_binlog的限制,MySQL只能按顺序来处理这些请求,另外,高频率的刷新binlog对IO的影响也很大,进一步影响了数据库的性能,所以,一般这个值都设为0或者其他值,在数据的安全性和高并发下的性能之间取得一个平衡。
为了更加有效的保护Binlog的安全性和完整性,MySQL5 .5之后引入了半同步复制;
🤙1.3半同步复制
在异步复制中,我们遇到的一个主要问题就是,在复制过程当中,主库不会去验证Binlog有没有成功复制到从库,那如果主库提交一个事务并写入Binlog中后,当从库还没有从主库得到Binlog时,主库宕机了或因磁盘损坏等故障导致该事务的Binlog丢失了,那从库就不会得到这个事务,也就造成了主从数据的不一致。
而半同步复制,当主库每提交一个事务后,不会立即返回,而是等待其中一个从库接收到Binlog并成功写入Relay-log中才返回客户端,所以这样就保证了一个事务至少有两份日志,一份保存在主库的Binlog,另一份保存在其中一个从库的Relay-log中,从而保证了数据的安全性和一致性。
另外,在半同步复制时,如果主库的一个事务提交成功了,在推送到从库的过程当中,从库宕机了或网络故障,导致从库并没有接收到这个事务的Binlog,此时主库会等待一段时间(这个时间由rpl_semi_sync_master_timeout的毫秒数决定),如果这个时间过后还无法推送到从库,那MySQL会自动从半同步复制切换为异步复制,当从库恢复正常连接到主库后,主库又会自动切换回半同步复制。
半同步复制的“半”体现在,虽然主从库的Binlog是同步的,但主库不会等待从库执行完Relay-log后才返回,而是确认从库接收到Binlog,达到主从Binlog同步的目的后就返回了,所以从库的数据对于主库来说还是有延时的,这个延时就是从库执行Relay-log的时间。所以只能称为半同步。
配置半同步复制:
半同步模式是作为MySQL5.5的一个插件来实现的,主从库使用的插件不一样
🐳1.3.1、先确认MySQL服务器是否支持动态增加插件
mysql>select @@have_dynamic_loading
🐳1.3.2 、分别在主从库上安装对用插件
插件一般默认在MySQL安装目录/lib/plugin下,可以去查看一下是否存在
主库的插件是semisync_master.so,从库是semisync_slave.so
mysql>install plugin rpl_semi_sync_master soname ‘semisync_master.so’;
mysql>install plugin rpl_semi_sync_slave soname ‘semisync_slave.so’;
🐳1.3.3、安装完成后,在plugin表(系统表)中查看一下
mysql>select * from mysql.plugin;
🐳1.3.4、在主从库中开启半同步复制
主:
mysql>set global rpl_semi_sync_master_enabled=1;
mysql>set global rpl_semi_sync_master_timeout=30000;
从:
mysql>set global rpl_semi_sync_slave_enabled=1;
注意:如果之前配置的是异步复制,在这里要重启一下从库的IO线程,如果是全新的半同步则不用重启.
重启命令是:mysql>stop slave io_thread;start slave io_thread;
🐳1.3.5、在主库上查看半同步复制的状态
mysql>show status like '%semi_sync';
在输出信息中,我们重点关注三个参数:
rpl_semi_sync_master_status OFF/ON #ON表示半同步复制打开,OFF表示关闭
rpl_semi_sync_master_yes_tx [number] #这个数字表示主库当前有几个事务说通过半同步复制到从库的
rpl_semi_sync_master_no_tx [number] #表示有几个事务不是通过半同步复制到从库的
🤙1.4 增强半同步复制
前面介绍的复制是异步操作,主库和从库的数据之间难免会存在一定的延迟,这样存在一个隐患:当在主库上写入一个事务并提交成功,而从库尚未得到主库的BINLOG日志时,主库由于磁盘损坏、内存故障、断电等原因意外宕机,导致主库上该事务BINLOG丢失,此时从库就会损失这个事务,从而造成主从不一致。
为了解决这个问题,从MySQL5.5开始,引入了半同步复制,此时的技术暂且称之为传统的半同步复制,因该技术发展到MySQL5.7后,已经演变为增强半同步复制(也成为无损复制)。在异步复制时,主库执行Commit提交操作并写入BINLOG日志后即可成功返回客户端,无需等待BINLOG日志传送给从库,如图所示。
而半同步复制时,为了保证主库上的每一个BINLOG事务都能够被可靠地复制到从库上,主库在每次事务成功提交时,并不及时反馈给前端应用用户,而是等待至少一个从库(详见参数rpl_semi_sync_master_wait_for_slave_count)也接收到BINLOG事务并成功写入中继日志后,主库才返回Commit操作成功给客户端(不管是传统的半同步复制,还是增强的半同步复制,目的都是一样的,只不过两种方式有一个细微地方不同,将在下面说明)
半同步复制保证了事务成功提交后,至少有两份日志记录,一份在主库的BINLOG日志上,另一份在至少一个从库的中继日志Relay Log上,从而更进一步保证了数据的完整性。
在传统的半同步复制中,主库写数据到BINLOG,且执行Commit操作后,会一直等待从库的ACK,即从库写入Relay Log后,并将数据落盘,返回给主库消息,通知主库可以返回前端应用操作成功,这样会出现一个问题,就是实际上主库已经将该事务Commit到了事务引擎层,应用已经可以可以看到数据发生了变化,只是在等待返回而已,如果此时主库宕机,有可能从库还没能写入Relay Log,就会发生主从库不一致。增强半同步复制就是为了解决这个问题,做了微调,即主库写数据到BINLOG后,就开始等待从库的应答ACK,直到至少一个从库写入Relay Log后,并将数据落盘,然后返回给主库消息,通知主库可以执行Commit操作,然后主库开始提交到事务引擎层,应用此时可以看到数据发生了变化。增强半同步复制的大致流程如下图所示。
半同步复制模式下,假如在传送BINLOG日志到从库时,从库宕机或者网络延迟,导致BINLOG并没有即使地传送到从库上,此时主库上的事务会等待一段时间(时间长短由参数rpl_semi_sync_master_timeout设置的毫秒数决定),如果BINLOG在这段时间内都无法成功发送到从库上,则MySQL自动调整复制为异步模式,事务正常返回提交结果给客户端。
半同步复制很大程度上取决于主从库之间的网络情况,往返时延RTT越小决定了从库的实时性越好。通俗地说,主从库之间的网络越快,从库约实时。
注意:往返时延RTT(Round-Trip Time)在计算机网络中是一个重要的性能指标,它表示从发送端发送数据开始到发送端接收到接收端的确认,总共经历的时长(这里可能有点拗口,我们可以理解为TCP三次握手的前两次握手)。
问题:
- 一旦Ack超时,将退化为异步复制模式,那么异步复制的问题也将发送
- 性能下降,增多至少一个RTT时间
- 如果超时时间设置很大,然后因为网络原来长时间收不到ACK,用户提交是被挂起的,可用性收到打击(半同步一样存在)
🤙1.5全量复制
增量复制和全量复制主要涉及到redis的框架:
用于初次复制或其它无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个IE非常重型的操作,当数据量较大时,会对主从节点和网络造成很大的开销,而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
1、Redis 内部会发出一个同步命令,刚开始是 Psync 命令,Psync ? -1表示要求 master 主机同步数据;
2、主机会向从机发送 runid (redis-cli info server)和 offset,因为 slave 并没有对应的 offset,所以是全量复制;
3、从机slave会保存主机master的基本信息save masterinf;
4、主节点收到全量复制的命令后,执行bgsave(异步执行),在后台生成RDB文件(快照),并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有命令;
5、主机send RDB发送RDB文件给从机;
6、发送缓冲区数据
7、刷新旧的数据,从节点在载入主节点的数据之前要先将老数据清除;
8、加载RDB文件将数据库状态更新至主节点执行bgsave时的数据库状态和缓冲区数据加载;
全量复制开销的方面:
1、bgsave时间
2、RDB文件网络传输时间
3、从节点清空数据的时间
4、从节点加载RDB的时间
当决定使用Mysql主从复制的数据库架构时,可能你的工程已经运行了一段时间,这是很常见的场景,比如目前我负责的一个项目,我想改变生产环境的单节点数据库的现状,通过主从复制为读写分离作铺垫。那么就需要两台及以上的节点作为数据库节点,这里所谓的节点不仅仅指服务器,也可能是同一台服务器,不同端口,使用docker进行隔离。
可是我在配置完主从复制后发现,主节点已经存在的数据并没有同步到从节点,当主节点向数据库写数据时,从节点重复这些操作,可从节点本身根本没有创建那些主节点的库,导致运行出错、IO线程坏死,最终从节点挂掉;
🏓1.5.1、查看主库的已有数据库
show databases;
假设其中test数据库是我们想要主从同步的数据库,模拟生产环境,该数据库中已经有了很多数据。
use test;
select * from user;
🏓1.5.2、锁定主数据库
锁定主数据库,只允许读取不允许写入,这样做的目的是防止备份过程中或备份完成之后有新数据插入,导致备份数据和主数据数据不一致,同样,这样做也有弊端,可能在锁库期间会影响正常的业务流程,所以我们应使锁库的粒度尽可能小。
flush tables with read lock;
查询主数据库状态,并记下FILE及Position的值:
show master status;
🏓1.5.3、开始备份主数据库
✨1.退出mysql终端,执行docker mysql备份命令
docker exec [CONTAINER] /usr/bin/mysqldump -u username --password=xxx [DATABASE] > back.sql
[CONTAINER] 是你自己容器的名字, [DATABASE]是你自己数据库的名字,back.sql是临时产生的备份文件,里面是sql语句, username是你自己的用户名,一般是root,xxx则是你自己的用户密码。该命令的意思是执行docker容器内mysql相关命令:mysqldump,将指定的数据库导出到宿主机当中
我们这里只需要备份test数据库,若要备份全部数据库,[DATABASE]处使用--all-databases
✨2.在主机上执行下列语句
图中的warnming是因为我们在命令行输入了密码,所以会有安全警告信息。可以看到,已经在宿主机上生成了back.sql。
✨ 3.开始导入从数据库
在导入备份文件之前,需要在从库中手动建立相应的同名库CREATE DATABASE test;
,否则会出现如下报错:
找不到相关数据库?
手动创建完数据库后执行下列语句:
cat back.sql | docker exec -i [CONTAINER] /usr/bin/mysql -u username --password=xxx [DATABASE]
✨4、备份成功
unlock tables;
🤙1.6、增量复制
1、如果全量复制过程中,master-slave网络连接断掉,那么salve重新连接master时,会触发增量复制;2、master直接从自己的backlog中获取部分丢失的数据,发送给slave node,默认backlog就是1MB;
3、msater就是根据slave发送的psync中的offset来从backlog中获取数据的;
Master继续将新的所有收集到的修改命令依次传给slave,完成同步, 主机修改了数据会给予从机修改的数据同步,叫做增量复制;
🤙1.7、多线程复制
在MySQL5.7中,带来了全新的多线程复制技术,解决了当master同一个schema下的数据发生了变更,从库不能并发应用的问题,同时也真正将binlog组提交的优势充分发挥出来,保障了从库并发应用Relay Log的能力。
在MySQL8.0中,多线程复制又进行了技术更新,引入了writeset的概念,而在之前的版本中,如果主库的同一个会话顺序执行多个不同相关对象的事务,例如,先执行了Update A表的数据,又执行了Update B表的数据,那么BINLOG在复制到从库后,这两个事务是不能并行执行的,writeset的到来,突破了这个限制。
mysql 主从复制原理:
1. master 节点上的binlogdump 线程,在slave 与其正常连接的情况下,将binlog 发送到slave 上。
2. slave 节点的I/O Thread ,通过读取master 节点binlog 日志名称以及偏移量信息将其拷贝到本地relay log 日志文件。
3. slave 节点的SQL Thread,该线程读取relay log 日志信息,将在master 节点上提交的事务在本地回放,达到与主库数据保持一致的目的。
MySQL5.5 及以前的复制一般主从复制有三个线程且都是单线程:
Binlog Dump(主) ‐‐> IO Thread(从) ‐‐> SQL Thread(从)。
而master 这边是通过并发线程提交,事务通过LSN 写入binlog;但是Slave 只有一个IO 线程和SQL 线程,是单线程,所以在业务大的情况下就很容易造成主从延时.
如果在MySQL 5.6 版本开启并行复制功能(slave_parallel_workers > 0),那么SQL 线程就变为了coordinator 线程,coordinator 线程主要负责以下两部分内容:
Coordinator+worker(多个)
若判断可以并行执行,那么选择worker 线程执行事务的二进制日志。
若判断不可以并行执行,如该操作是DDL,亦或者是事务跨schema 操作,则等待所有的worker 线程执行完成之后,再执行当前的日志。
这意味着coordinator 线程并不是仅将日志发送给worker 线程,自己也可以回放日志,但是所有可以并行的操作交付由worker 线程完成。
上述机制实现的基于schema 的并行复制,存在的问题是:
这样设计的并行复制效果并不高,如果用户实例仅有一个库,那么就无法实现并行回放,甚至性能会比原来的单线程更差,而单库多表是比多库多表更为常见的一种情形。
MySQL5.7 的MTS(Enhanced Muti‐threadedslaves)MySQL 5.7 引入了新的机制来实现并行复制,不再有基于库的并行复制限制,主要思想就是slave 服务器的回放与主机是一致的,即master 服务器上是怎么并行执行,slave 上就怎样进行并行回放。
mysql v5.7.2 进行了优化,增加了参数slave_parallel_type,参数有两个选项:
LOGICAL_CLOCK:基于逻辑时钟 ,可以在一个DATABASE 中并发执行relay log 事务DATABASE: 基于数据库,v5.6 默认是这个参数,改参数每个库只能一个线程;
slave‐parallel‐type,其可以配置的值有:
DATABASE:默认值,基于库的并行复制方式
LOGICAL_CLOCK:基于组提交的并行复制方式
从库:
mysql> show variables like 'slave_parallel%';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| slave_parallel_type | DATABASE |
| slave_parallel_workers | 0 |
+------------------------+----------+
mysql> stop slave;
mysql> set global slave_parallel_type='LOGICAL_CLOCK';
Query OK, 0 rows affected (0.00 sec)
mysql> set global slave_parallel_type='LOGICAL_CLOCK';
Query OK, 0 rows affected (0.00 sec)
mysql> set global slave_parallel_workers=2;
Query OK, 0 rows affected (0.00 sec)
mysql> start slave;
Query OK, 0 rows affected (0.01 sec)
mysql> show processlist;
+-------+-------------+-----------+------+---------+------+--------------------------------------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-------+-------------+-----------+------+---------+------+--------------------------------------------------------+------------------+
| 26077 | root | localhost | NULL | Query | 0 | starting | show processlist |
| 26141 | system user | | NULL | Connect | 40 | Waiting for master to send event | NULL |
| 26142 | system user | | NULL | Connect | 40 | Slave has read all relay log; waiting for more updates | NULL |
| 26143 | system user | | NULL | Connect | 40 | Waiting for an event from Coordinator | NULL |
| 26144 | system user | | NULL | Connect | 40 | Waiting for an event from Coordinator | NULL |
+-------+-------------+-----------+------+---------+------+--------------------------------------------------------+------------------+
mysql> show variables like 'slave_parallel%';
+------------------------+---------------+
| Variable_name | Value |
+------------------------+---------------+
| slave_parallel_type | LOGICAL_CLOCK |
| slave_parallel_workers | 2 |
+------------------------+---------------+
master_info_repository=TABLE (开启MTS 功能后,会频繁更新master.info,设置为TABLE 减小开销)relay_log_info_repository=TABLE
slave_master_info 记录了首次同步master 的位置relay_log_recovery=ON (slave IO 线程crash,如果relay‐log损坏,则自动放弃所有未执行的relay‐log,重新从master 上获取日志,保证relay‐log 的完整性)
slave_preserve_commit_order=ON (保证提交的顺序性)在slave 上应用事务的顺序是无序的,和relay log 中记录的事务顺序不一样,这样数据一致性是无法保证的,为了保证事务是按照relay log 中记录的顺序来回放,就需要开启参数slave_preserve_commit_order。
虽然mysql5.7 添加MTS 后,虽然slave 可以并行应用relay log,但commit 部分仍然是顺序提交,其中可能会有等待的情况。
优化参数:
[mysqld]
bind-address=0.0.0.0
port=3306
datadir=/data/mysql
socket=/data/mysql/mysql.sock
user=mysql
skip-name-resolve
slow_query_log=on
long_query_time=1
slow_query_log_file=/data/mysql/mysql-slow.log
innodb-file-per-table=1
innodb_flush_log_at_trx_commit = 2
log_warnings = 1
connect_timeout = 60
net_read_timeout = 120
performance_schema_max_table_instances = 400
server-id = 2
relay-log = relay-log
relay-log-index = relay-log.index
slave_parallel_type=LOGICAL_CLOCK
slave_parallel_workers=2
master_info_repository=TABLE
relay_log_info_repository=TABLE
relay_log_recovery=ON
slave_preserve_commit_order=ON
log_bin=mysql‐bin
log_slave_updates=ON
[mysqld_safe]
log-error=/data/mysql/mysqld.log
pid-file=/data/mysql/mysqld.pid
重启mysqld进入数据库
mysql> show variables like '%slave_parallel_type%';
+---------------------+---------------+
| Variable_name | Value |
+---------------------+---------------+
| slave_parallel_type | LOGICAL_CLOCK |
+---------------------+---------------+
mysql> show variables like '%slave_parallel_worker%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| slave_parallel_workers | 2 |
+------------------------+-------+
mysql> show variables like '%master_info_repository%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| master_info_repository | TABLE |
+------------------------+-------+
mysql> set global relay_log_info_repository='TABLE';
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like '%relay_log_info_repository%';
+---------------------------+-------+
| Variable_name | Value |
+---------------------------+-------+
| relay_log_info_repository | TABLE |
+---------------------------+-------+
mysql> show variables like '%relay_log_recovery%';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| relay_log_recovery | ON |
+--------------------+-------+
1 row in set (0.00 sec)
mysql> show variables like '%slave_preserve_commit_order%';
+-----------------------------+-------+
| Variable_name | Value |
+-----------------------------+-------+
| slave_preserve_commit_order | ON |
+-----------------------------+-------+
开启slave
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)
mysql> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.132.121
Master_User: replication
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master-bin.000002
Read_Master_Log_Pos: 40870
Relay_Log_File: relay-log.000010
Relay_Log_Pos: 321
Relay_Master_Log_File: master-bin.000002
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: 40870
Relay_Log_Space: 689
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: 6b00724f-a094-11e9-8f47-000c2991dd19
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:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
主库写入数据
[root@master mysql]#mysql -uroot -p123456 -e 'use darren;insert into test values (1);
[root@master mysql]#mysql -uroot -p123456 -e 'use darren;insert into test values (1);
[root@master mysql]#mysql -uroot -p123456
mysql> mysql> show master status;
+-------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+-------------------+
| master-bin.000002 | 41388 | | | |
+-------------------+----------+--------------+------------------+-------------------+
从库
mysql> use mysql;
Database changed
mysql> show tables;
+---------------------------+
| Tables_in_mysql |
+---------------------------+
| columns_priv |
| db |
| engine_cost |
| event |
| func |
| general_log |
| gtid_executed |
| help_category |
| help_keyword |
| help_relation |
| help_topic |
| innodb_index_stats |
| innodb_table_stats |
| ndb_binlog_index |
| plugin |
| proc |
| procs_priv |
| proxies_priv |
| server_cost |
| servers |
| slave_master_info |
| slave_relay_log_info |
| slave_worker_info |
| slow_log |
| tables_priv |
| time_zone |
| time_zone_leap_second |
| time_zone_name |
| time_zone_transition |
| time_zone_transition_type |
| user |
+---------------------------+
mysql> select * from slave_master_info;
+-----------------+-------------------+----------------+-----------------+-------------+---------------+------+---------------+-------------+--------+------------+----------+------------+---------+------------------------+-----------+------+--------------------+--------------------------------------+-------------+---------+-------------+-----------------------+--------------+-------------+
| Number_of_lines | Master_log_name | Master_log_pos | Host | User_name | User_password | Port | Connect_retry | Enabled_ssl | Ssl_ca | Ssl_capath | Ssl_cert | Ssl_cipher | Ssl_key | Ssl_verify_server_cert | Heartbeat | Bind | Ignored_server_ids | Uuid | Retry_count | Ssl_crl | Ssl_crlpath | Enabled_auto_position | Channel_name | Tls_version |
+-----------------+-------------------+----------------+-----------------+-------------+---------------+------+---------------+-------------+--------+------------+----------+------------+---------+------------------------+-----------+------+--------------------+--------------------------------------+-------------+---------+-------------+-----------------------+--------------+-------------+
| 25 | master-bin.000002 | 40870 | 192.168.132.121 | replication | 1234567 | 3306 | 60 | 0 | | | | | | 0 | 30 | | 0 | 6b00724f-a094-11e9-8f47-000c2991dd19 | 86400 | | | 0 | | |
+-----------------+-------------------+----------------+-----------------+-------------+---------------+------+---------------+-------------+--------+------------+----------+------------+---------+------------------------+-----------+------+--------------------+--------------------------------------+-------------+---------+-------------+-----------------------+--------------+-------------+
mysql> select * from slave_relay_log_info;
+-----------------+--------------------+---------------+-------------------+----------------+-----------+-------------------+----+--------------+
| Number_of_lines | Relay_log_name | Relay_log_pos | Master_log_name | Master_log_pos | Sql_delay | Number_of_workers | Id | Channel_name |
+-----------------+--------------------+---------------+-------------------+----------------+-----------+-------------------+----+--------------+
| 7 | ./relay-log.000010 | 839 | master-bin.000002 | 41388 | 0 | 2 | 1 | |
+-----------------+--------------------+---------------+-------------------+----------------+-----------+-------------------+----+--------------+
查看线程
+----+-------------+-----------+-------+---------+------+--------------------------------------------------------+-------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-------------+-----------+-------+---------+------+--------------------------------------------------------+-------------------+
| 7 | root | localhost | mysql | Query | 0 | starting | show processlist |
| 36 | system user | | NULL | Connect | 762 | Waiting for master to send event | NULL |
| 37 | system user | | NULL | Connect | 567 | Slave has read all relay log; waiting for more updates | NULL |
| 38 | system user | | NULL | Connect | 567 | Waiting for an event from Coordinator | NULL |
| 39 | system user | | NULL | Connect | 762 | Waiting for an event from Coordinator | NULL |
+----+-------------+-----------+-------+---------+------+--------------------------------------------------------+-------------------+
二. Mysql的主从复制
1、master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events;
2、slave将master的binary log events拷贝到它的中继日志(relay log) ;
3、slave重做中继日志中的事件,将改变应用到自己的数据库中。MySQL复制是异步的且串行化的,这样从节点不用一直访问主服务器来更新自己的数据了。
🎯主从复制原理:
master将数据的改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events;当数据发生改变时,则将其改变写入二进制日志中;
slave会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/OThread请求master二进制事件
同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并保存至从节点本地的中继日志中,从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后I/OThread和SQLThread将进入睡眠状态,等待下一次被唤醒。
💎复制的原则:
1、每个slave只有一个master
2、每个slave只能有一个唯一的服务器ID
3、每个master可以有多个salve
🐳主从复制的缺点:
主要是延时
通过上面的机制,可以保证主从数据库数据一致,但是时间上肯定有延迟,即从机数据是滞后的。
由于sql thread也是单线程的,当主库的并发较高时,产生的DML数量超过slave的SQL thread所能处理的速度等的延时;
🤙解决方案:
1.业务的持久化层的实现采用分库架构,mysql服务可平行扩展,分散压力
2.单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库
3.服务的基础架构在业务和mysql之间加入memcache或者redis的cache层。降低mysql的读压力。
🏓小结:
1、从库会生成两个线程,一个I/O线程,一个SQL线程;
2、I/O线程会去请求主库的binlog。主库会生成一个log dump线程,用来给从库I/O线程传binlog。并将得到的binlog写到本地的relay-log(中继日志)文件中;
3、SQL线程,会读取relay log文件中的日志,并解析成sql语句逐一执行;
🌈2.1 一主一从
🍋2.1.1 window、linux之间的通讯
主从都配置在[mysqld]结点下,都是小写:
🐳GitHistory(可查看和搜索git日志以及图形和详细信息)
配置文件在window上修改,主机修改my.ini
- server-id=1 [必须]主服务器唯一ID
- log-bin=自己本地的路径/mysqlbin[必须]启用二进制日志
- log-err=自己本地的路径/mysqlerr[可选]启用错误日志
- basedir=“自己本地路径”[可选]根目录
- tmpdir=“自己本地路径”[可选]临时目录
- datadir=“自己本地路径/Data/”[可选]数据目录
- binlog-ignore-db=mysql[可选]设置不要复制的数据库
- binlog-do-db=需要复制的主数据库名字[可选]设置需要复制的数据库
🌈linux上作为从机,从机修改my.cnf配置文件
- [必须]从服务器唯一ID
- [可选]启用二进制日志
🥝在windows上授权 linux,只需要给予服务器ip:
GRANT REPLICATION SLAVE ON *.* TO ‘zuanshiwanglaowu’@‘从机器数据库IP’ IDENTIFIED BY ‘666666’;
show master status;
记录下File和Position的值
🥝在linux中授权window的IP地址:
CHANGE MASTER TO MASTER_HOST=’主机
IP’, MASTER_USER=‘wanglaowu’, MASTER_PASSWORD=’666666’, MASTER_LOG_FILE='File名字’, MASTER_LOG_POS=Position数字;
start slave; 启动服务器的slave
show slave status\G
stop slave;停止
🍋2.1.2 linux、linux之间的通讯
假如主从都在linux上,具体详细配置如下:
💎主机的设置:
主机上创建一个用户,给从机,专门让从机来负责主从复制的权限;
创建一个主从复制的权限 给s1用户,并且指定用户密码;
GRANT REPLICATION S1 ON *.* TO 'slave'@'%' IDENTIFIED BY '123456';
🎯查询master的状态,show master status;
显示日日志名称,接入点的位置开始复制,需要复制的数据库,以及不需要复制的数据库
修改配置文件:gedit或者vim /etc/my.cnf
- #主服务器唯一ID
- server-id=1
- #启用二进制日志
- log-bin=mysql-bin
- # 设置不要复制的数据库(可设置多个)
- binlog-ignore-db=mysql
- #设置需要复制的数据库
- binlog-do-db=需要复制的主数据库名字
- #设置logbin格式
- binlog_format=STATEMENT
🌈三种设置日志的格式:
1、binlog_format=statement ,master写入执行的SQL语句到binlog中,从库读取这些SQL语句并执行,主机和从机的时间不一样,导致执行某些函数时可能造成主从不一致的情况;
2、binlog_format=row,一行一句的复制,缺点执行 100 万次更新,会记录一百万次,效率更低;
3、binlog_format=mixed, 它记录日志取决于修改的类型,选择合适的格式来记录该修改,缺点识别不了特定的名称[系统变量] 如 @@host name;
💎从机配置:
再从机输入的时候,如果之前有主从的一些,可以通过重新配置主从stop slave;进行关闭服务器的复制,以及reset master;重置主机,通过主机的配置,再从机中输入主要是配置日志名称以及它的接入点位置等;
CHANGE MASTER TO MASTER_HOST='主机的IP地址',
MASTER_USER='s1',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.具体数字',MASTER_LOG_POS=具体值;
🎯从服务器复制功能 start slave;
🎯查看从服务器状态 show slave status\G;,字段太多,可以通过列管理\G的参数
有这两个参数代表成功了,主机增删改表格,从机可以查询到表格等信息;
停止从服务复制功能 stop slave;
id配置
# 从机服务器唯一 ID
server-id=1
# 启用中继日志
relay-log=mysql-relay
1、修改配置文件之后是要重启才可以使用
2、通过命令 service mysqld start或者systemctl等命令,以及关闭防火墙
3、查看其防火墙的状态 systemctl status firewalld
可查看以下文章
🌈2.2 双主双从
一个主机 m1 用于处理所有写请求,它的从机 s1 和另一台主机 m2 还有它的从机 s2 负责所有读请
求。当 m1 主机宕机后,m2 主机负责写请求,m1、m2 互为备机;
💎2.2.1、双主机的配置
第一个备份主机
- log-slave-updates 配置这个主要是因为主机可能成为从机,所以要配置
#主服务器唯一ID
server-id=1
#启用二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
#设置需要复制的数据库
binlog-do-db=需要复制的主数据库名字
#设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
#表示自增长字段每次递增的量,指自增字段的起始值,其默认值是1,取值范围是1 .. 99999
auto-increment-increment=2
# 表示自增长字段从哪个数开始,指字段一次递增多少,他的取值范围是1 .. 99999
auto-increment-offset=1
第二个备份主机
#主服务器唯一ID
server-id=3 #启用二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
#设置需要复制的数据库
binlog-do-db=需要复制的主数据库名字
#设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
#表示自增长字段每次递增的量,指自增字段的起始值,其默认值是1,取值范围是1 .. 99999
auto-increment-increment=2
# 表示自增长字段从哪个数开始,指字段一次递增多少,他的取值范围是1 .. 99999
auto-increment-offset=2
🏓从机的配置
#从服务器唯一ID
server-id=2
#启用中继日志
relay-log=mysql-relay
🏓第二个从机的配置
#从服务器唯一ID
server-id=4 #启用中继日志
relay-log=mysql-relay
重启mysql的服务以及关闭防火墙的基本操作,在两台主机上建立帐户并授权 slave
执行sql的命令GRANT REPLICATION SLAVE ON *.* TO 's1'@'%' IDENTIFIED BY '123456';
展示其状态,主要给从机使用以及备份的主机使用show master status;
第二个主机的查询状态同理
从机复制第一个主机的信息,第二个从机复制第二个主机的信息;
CHANGE MASTER TO MASTER_HOST='主机的IP地址',
MASTER_USER='s1',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.具体数字',MASTER_LOG_POS=具体值;
分别启动两个主机的复制功能start slave;
查看服务器的状态show slave status\G;,出现俩个yes的状态代表已经可以实现
两个主机如何备份,和上面一样,主机复制备份主机的信息,备份主机输入主机的信息
start slave;以及查看从服务器状态show slave status\G;
本身是testdb的数据库,所以建立这个数据库;
三. Redis的主从复制
主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主;
🌈主要的功能有:
- 读写分离,性能扩展
- 容灾快速恢复
🌈具体主从的复制原理:
从机主动发送:
- Slave启动成功连接到master后,从机slave会发送一个sync命令
- Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步
🎯3.1、主从复制方式
🍎3.1.1、全量复制
如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个PSYNC命令给master请求复制数据。
master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。
当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave。
当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分数据复制(断点续传)。
master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。
🍎3.1.2、增量复制
复制过程:
- 从节点执行 slaveof [masterIP] [masterPort],保存主节点信息。
- 从节点中的定时任务发现主节点信息,建立和主节点的 Socket 连接。
- 从节点发送 Ping 信号,主节点返回 Pong,两边能互相通信。
- 连接建立后,主节点将所有数据发送给从节点(数据同步)。
- 主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性
具体的数据同步过程:
Redis结点间的复制由从库主动发起。Redis2.8前由sync命令执行,sync命令只能支持全量复制,Redis2.8及其以后,使用psync命令执行,该命令即支持全量复制,又支持部分复制。
对于主从间的复制,当从库第一次复制数据时,使用全量复制,后面过程当主库处理了写数据后,会将数据自动同步给从库(异步执行)。而部分复制的时机是在从库复制主库的数据时发生异常,待恢复后,发起的一次补救复制,由于这时只涉及部分数据,所以数据同步时间会较短。
下面的主从复制原则分析,也是基于psync来说明的。
async的三要素:
psync执行中,需要使用到 主从结点的复制偏移量、主节点的运行时ID以及主节点复制积压缓存区三部分。
1. 复制偏移量:
- 主从结点都维护有各自的复制偏移量。
- 主节点在处理了新的写数据后,会更新自己的偏移量。即主偏移量+=写数据的字节长度。
- 从结点每隔1秒向主节点发送自己的偏移量,即主节点也会记录从结点的偏移量。
- 从结点接收到主节点的数据后,也会像主节点一样,更新自己的偏移量。
2. 主节点复制积压缓存区:
- 主节点维护了一个有限长度的队列,在与从结点连接时建立,默认为1M大小。主节点在响应写数据时,在向从结点发数据同时,也写入复制积压缓存区。这样当发送给从节点的数据丢失时,能够有一个补救机制。
3. 主节点运行时ID:
- Redis每次启动都会对应一个48位的运行时ID。从节点进行复制时,发送的是psync 主节点运行时ID 从节点复制偏移量。之所以不用主节点IP+主节点端口号 来识别是因为,当主节点重启后更新了数据(RDB或AOF),此时从节点在基于已有的复制偏移量来同步是不安全的。
补充:
上面的描述信息,都可以通过info replication命令查看到。
🌈如何搭建redis.conf文件
本身redis.conf文件中就有include的代码模块
所以在命令行中直接输入include 绝对路径,就可引入
具体文件的内容修改如下
开启daemonize yes 后台启动
Appendonly 关掉或者换名字,也就是不要AOF的追加,只要RDB持久化
🍎3.1.3、部分复制
①部分复制主要是 Redis 针对全量复制的过高开销做出的一种优化措施,也是使用 psync runId offset命令实现。
当从节点正在复制主节点时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,主节点的复制积压缓冲区将这部分数据直接发送给从节点。
这样就可以保持主从节点复制的一致性。补发的这部分数据一般远远小于全量数据。②主从连接中断期间主节点依然响应命令,但因复制连接中断命令无法发送给从节点,不过主节点内的复制积压缓冲区依然可以保存最近一段时间的写命令数据。
③当主从连接恢复后,由于从节点之前保存了自身已复制的偏移量和主节点的运行 ID。因此会把它们当做 psync 参数发送给主节点,要求进行部分复制。
④主节点接收到 psync 命令后首先核对参数 runId 是否与自身一致,如果一致,说明之前复制的是当前主节点。
之后根据参数 offset 在复制积压缓冲区中查找,如果 offset 之后的数据存在,则对从节点发送+COUTINUE 命令,表示可以进行部分复制。因为缓冲区大小固定,若发生缓冲溢出,则进行全量复制。⑤主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。
🎯3.2、Redis主从复制
因为演示,lz没有把slaveof 命令配置进redis配置文件中
步骤如下:
🎽3.2.1、启动redis服务端
分别启动3台服务器的redis服务端,并查看3台服务器的角色信息
- 启动6379端口的master服务器并测试是否连接成功,如下图:
- 启动6380端口的slave1服务器并测试是否连接成功,如下图:
- 启动6381端口的slave2服务器并测试是否连接成功,如下图:
- 输入info replication命令,查看3台服务器的角色信息,由下图可知,3台服务器均为master服务器,如下图:
🎽3.2. 2、服务器ip、端口命令
- 由下图可知,slave1和slave2服务器的角色都为slave;
🎽3.2.3、获取到添加的数据。
-
由下图可知,成功获取到master服务器中键为k4的数据。
🎽3.2. 4、获取到在执行slaveof命令之前master服务器的数据。
对于slave1和slave2服务器再执行slaveof命令之前,master服务器中键为k1、k2、k3的数据,再slave1和slave2服务器是否能成功获取到数据。由下图可知,slave1和slave2服务器可以获取到在执行slaveof命令之前master服务器的数据。
🎽3.2.5、测试所有服务器是否都可以添加数据
-
由下图可知,master服务器添加数据成功而slave1和slave2服务器添加数据失败,因为只读。
🤙情况1:一主两从
master服务器宕机后,slave1和slave2服务器角色是否能升级为master
如果从机挂掉,执行shutdown主机开始写数据,从机在开启的时候,恢复数据的时候是从主机从头开始追加。
master服务器宕机恢复后,slave1和slave2服务器是否还可以获取到master服务器的数据;
🤙情况2:薪火相传
上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化降低风险。
从机的首机是另一台从机的意思
用 slaveof <ip><port> ,中途变更转向:会清除之前的数据,重新建立拷贝最新的
风险是一旦某个slave宕机,后面的slave都没法备份
主机挂了,从机还是从机,无法写数据了
🤙情况3:反客为主
当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改
可以使用命令:slaveof no one 将从机变为主机;
🎯3.3、 哨兵模式
哨兵二字可以让我们联想到监察员一类的职务,它的角色在应该是来监控我们的redis主从服务,当发现我们的redis主从服务出现问题了,比如主机下线了一类的,它就要发挥它的作用,将其他的从机扶持成主机,达到系统正常运行的效果。
一般而言,我们会用多个实例来组成sentinel集群(哨兵集群)来监视一组的一主多从或者是多组的一主多从。在这里提一句,哨兵模式也遵循一个选举机制和过半原则,只有当一半以上的sentinel认为某台机器出问题了,这个决议才会被通过进行之后的操作。
哨兵模式的部署示意图:
主要是为了监控主机宕机之后,从机可以立马变为主机,就和上面的反客为主一样,不用手动设置
能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库;
在目录中新建一个文件sentinel.conf,文件格式不能出错;
内容:
sentinel monitor mymaster 127.0.0.1 9699 1
代码的含义为 sentine l哨兵,监控,一个id(别名),ip加端口号
其中mymaster为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。
启动哨兵模式通过redis的bin目录下
命令如下:redis-sentinel /sentinel.conf
具体哪个从机会变成主机
其判定规则主要为:顺序依次往下,优先级》偏移量》runid
- 优先级在redis.conf中默认:slave-priority 100,值越小优先级越高
- 偏移量是指获得原主机数据最全的,也就是数据越多,变主机的机会越大
- 每个redis实例启动后都会随机生成一个40位的runid
这里也有个缺点就是复制会有延时,由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
哨兵配置具体步骤可参考以下连接:
🎯3.4 java代码结合
主要是ip端口号的连接
以及主机名称:new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
private static JedisSentinelPool jedisSentinelPool=null;
public static Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
Set<String> sentinelSet=new HashSet<>();
sentinelSet.add("192.168.11.135:20079");
JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong
jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
return jedisSentinelPool.getResource();
}else{
return jedisSentinelPool.getResource();
}
}
四、mysql读写分离原理
读写分离就是在主服务器上修改,数据会同步到从服务器,从服务器只能提供读取数据,不能写入,实现备份的同时也实现了数据库性能的优化,以及提升了服务器安全。
前较为常见的Mysql读写分离分为以下两种:
🐳1、基于程序代码内部实现
在代码中根据select 、insert进行路由分类,这类方法也是目前生产环境下应用最广泛的。优点是性能较好,因为程序在代码中实现,不需要增加额外的硬件开支,缺点是需要开发人员来实现,运维人员无从下手。
🐳2、 基于中间代理层实现
代理一般介于应用服务器和数据库服务器之间,代理数据库服务器接收到应用服务器的请求后根据判断后转发到,后端数据库,有以下代表性的程序。
(1)mysql_proxy。mysql_proxy是Mysql的一个开源项目,通过其自带的lua脚本进行sql判断。
(2)Atlas。是由 Qihoo 360, Web平台部基础架构团队开发维护的一个基于MySQL协议的数据中间层项目。它是在mysql-proxy 0.8.2版本的基础上,对其进行了优化,增加了一些新的功能特性。360内部使用Atlas运行的mysql业务,每天承载的读写请求数达几十亿条。支持事物以及存储过程。
(3)Amoeba。由阿里巴巴集团在职员工陈思儒使用序java语言进行开发,阿里巴巴集团将其用户生产环境下,但是他并不支持事物以及存数过程。
经过上述简单的比较,不是所有的应用都能够在基于程序代码中实现读写分离,像一些大型的java应用,如果在程序代码中实现读写分离对代码的改动就较大,所以,像这种应用一般会考虑使用代理层来实现,那么今天就使用Amoeba为例,完成主从复制和读写分离。
🐳3、MySQLProxy介绍
下面使用MySQL官方提供的数据库代理层产品MySQLProxy搭建读写分离。
MySQLProxy实际上是在客户端请求与MySQLServer之间建立了一个连接池。所有客户端请求都是发向MySQLProxy,然后经由MySQLProxy进行相应的分析,判断出是读操作还是写操作,分发至对应的MySQLServer上。对于多节点Slave集群,也可以起做到负载均衡的效果。
五、主从复制可能出现的问题
🏓问题1:master的写操作,slaves被动的进行一样的操作,保持数据一致性,那么slave是否可以主动的进行写操作?
假设slave可以主动的进行写操作,slave又无法通知master,这样就导致了master和slave数据不一致了。因此slave不应该进行写操作,至少是slave上涉及到复制的数据库不可以写。实际上,这里已经揭示了读写分离的概念。
🏓问题2:主从复制中,可以有N个slave,可是这些slave又不能进行写操作,要他们干嘛?
可以实现数据备份。
类似于高可用的功能,一旦master挂了,可以让slave顶上去,同时slave提升为master。
异地容灾,比如master在北京,地震挂了,那么在上海的slave还可以继续。
主要用于实现scale out,分担负载,可以将读的任务分散到slaves上。
【很可能的情况是,一个系统的读操作远远多于写操作,因此写操作发向master,读操作发向slaves进行操作】
🏓问题3:主从复制中有master,slave1,slave2,...等等这么多MYSQL数据库,那比如一个JAVA WEB应用到底应该连接哪个数据库?
当然,我们在应用程序中可以这样,insert/delete/update这些更新数据库的操作,用connection(for master)进行操作,select用connection(for slaves)进行操作。那我们的应用程序还要完成怎么从slaves选择一个来执行select,例如简单的轮循算法。
这样的话,相当于应用程序完成了SQL语句的路由,而且与MYSQL的主从复制架构非常关联,一旦master挂了,某些slave挂了,那么应用程序就要修改了。能不能让应用程序与MYSQL的主从复制架构没有什么太多关系呢?
找一个组件,application program只需要与它打交道,用它来完成MYSQL的代理,实现SQL 语句的路由。
mysql proxy并不负责,怎么从众多的slaves挑一个?可以交给另一个组件(比如haproxy)来完 成。这就是所谓的MYSQL READ WRITE SPLITE,MYSQL的读写分离。
🏓问题4:如果mysql proxy , direct , master他们中的某些挂了怎么办?
总统一般都会弄个副总统,以防不测。同样的,可以给这些关键的节点来个备份。
🏓问题5:当master的二进制日志每产生一个事件,都需要发往slave,如果我们有N个slave,那是发N次,还是只发一次?
如果只发一次,发给了slave-1,那slave-2,slave-3,...它们怎么办?
显 然,应该发N次。实际上,在MYSQL master内部,维护N个线程,每一个线程负责将二进制日志文件发往对应的slave。master既要负责写操作,还的维护N个线程,负担会很重。可 以这样,slave-1是master的从,slave-1又是slave-2,slave-3,...的主,同时slave-1不再负责select。 slave-1将master的复制线程的负担,转移到自己的身上。这就是所谓的多级复制的概念。
🏓问题6:当一个select发往mysql proxy,可能这次由slave-2响应,下次由slave-3响应,这样的话,就无法利用查询缓存了。
应该找一个共享式的缓存,比如memcache来解决。将slave-2,slave-3,...这些查询的结果都缓存至mamcache中。
🏓问题7:随着应用的日益增长,读操作很多,我们可以扩展slave,但是如果master满足不了写操作了,怎么办呢?
scale on ?更好的服务器? 没有最好的,只有更好的,太贵了。。。
scale out ? 主从复制架构已经满足不了。
无情的代码机器,记录代码生活。。。。。。。。。