mysql的主从复制与读写分离
一、案例概述
在实际的生产环境中,如果对数据库的读和写都在同一个数据库服务器中操作,无论是 在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。因此,一般来说都是通过主从复制(Master-Slave)来同步数据,再通过读写分离来提升数据库并发负载能力的方案来进行部署与实施
1、案例场景
一台主 MySQL 服务器带两台从 MySQL 服务器做数据复制,前端应用 在进行数据库写操作时,对主服务器进行操作,在进行数据库读操作时,对两台从服务器进行操作,这样大量减轻了对主服务器的压力
MySQL 的主从复制和 MySQL 的读写分离两者有着紧密联系,首先要部署主从复制, 只有主从复制完成了,才能在此基础上进行数据的读写分离!
2、主从复制
2.1 复制类型
- 基于语句的复制。在主服务器上执行的 SQL 语句,在从服务器上执行同样的语句。 MySQL 默认采用基于语句的复制,效率比较高。
- 基于行的复制。把改变的内容复制过去,而不是把命令在从服务器上执行一遍。
- 混合类型的复制。默认采用基于语句的复制,一旦发现基于语句无法精确复制时,就 会采用基于行的复制。
2.2 工作流程
- 在每个事务更新数据完成之前,Master 将这些改变记录进二进制日志。写入二进制 日志完成后,Master 通知存储引擎提交事务。
- Slave 将 Master 的 Binary log 复制到其中继日志(Relay log)。首先,Slave 开始
一个工作线程——I/O 线程,I/O 线程在 Master 上打开一个普通的连接,然后开始 Binlog dump process。Binlog dump process 从 Master 的二进制日志中读取事件,如果已经跟上Master,它会睡眠并等待 Master 产生新的事件。I/O 线程将这些事件写入中继日志。 - SQL slave thread(SQL 从线程)处理该过程的最后一步。SQL 线程从中继日志读 取事件,并重放其中的事件而更新Slave 数据,使其与 Master 中的数据保持一致。只要该 线程与 I/O 线程保持一致,中继日志通常会位于 OS的缓存中,所以中继日志的开销很小
3、读写分离
3.1 原理
简单来说,读写分离就是只在主服务器上写,只在从服务器上读。基本的原理是让主数据库处理事务性查询,而从数据库处理 select 查询。数据库复制被用来把主数据库上事务性查询导致的变更同步到集群中的从数据库
3.2 实现方式
-
基于程序代码内部实现 在代码中根据 select、insert 进行路由分类,这类方法也是目前生产环境应用最广泛的。
优点是性能较好,因为在程序代码中实现,不需要增加额外的设备作为硬件开支;缺点是需要开发人员来实现,运维人员无从下手。 -
基于中间代理层实现 代理一般位于客户端和服务器之间,代理服务器接到客户端请求后通过判断后转发到后 端数据库,有两个代表性程序
(1)MySQL-Proxy。MySQL-Proxy 为 MySQL 开源项目,通过其自带的 lua 脚本进行 SQL 判断,虽然是 MySQL 官方产品,但是 MySQL 官方并不建议将 MySQL-Proxy 用到生产环境。
(2)Amoeba。由陈思儒开发,作者曾就职于阿里巴巴。该程序由 Java 语言进行开发, 阿里巴巴将其用于生产环境。它不支持事务和存储过程。
本案例用Amoeba来实现读写分离
二、案例部署
1、部署环境
amoeba:192.168.245.203
master:192.168.245.204
slave1:192.168.245.205
slave2:192.168.245.206
client:192.168.245.207
2、准备工作
- 每台机器全部安装mysql 5.7
- 每台机器查看时间是否同步(如果不同步,需要安装ntp服务,开启ntp时间同步)
[root@amoeba ~]# date
2020年 08月 28日 星期五 22:36:37 CST
[root@master ~]# date
2020年 08月 28日 星期五 22:36:41 CST
[root@slave1 ~]# date
2020年 08月 28日 星期五 22:36:44 CST
[root@slave2 ~]# date
2020年 08月 28日 星期五 22:36:47 CST
3、主从复制
3.1 配置master
1、mysql配置文件里插入以下内容,开启二进制日志并允许从服务器复制
[root@master ~]# vim /etc/my.cnf
server-id = 11
log-bin=master-bin
log-slave-updates=true
2、重启mysql服务
[root@master ~]# systemctl restart mysqld
3、登陆mysql,给从服务器授权可以复制的权限
[root@master ~]# mysql -uroot -p
mysql> GRANT REPLICATION SLAVE ON *.* TO 'myslave'@'192.168.245.%' IDENTIFIED BY '123456'; //让从服务器可以复制
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
4、查看主服务器的日志文件和状态点,记住他们之后会用到
mysql> show master status;
+-------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+-------------------+
| master-bin.000001 | 604 | | | |
+-------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
3.2 配置两台slave
1、mysql配置文件里插入以下内容,server-id不能和其他机器相同
[root@slave1 ~]# vim /etc/my.cnf
server-id = 22
relay-log=relay-log-bin //从主服务器上同步日志文件记录到本地
relay-log-index=slave-relay-bin.index //定义relay-log的位置和名称
2、重启mysql服务
[root@salve1 ~]# systemctl restart mysqld
3、登陆数据库,设置复制同步的masterip地址,用户名密码和日志文件、状态点
[root@slave1 ~]# mysql -uroot -p
mysql> change master to master_host='192.168.245.204',master_user='myslave',master_password='123456',master_log_file='master-bin.000001',master_log_pos=604;
Query OK, 0 rows affected, 2 warnings (0.02 sec)
mysql> start slave; <------开启slave
Query OK, 0 rows affected (0.00 sec)
mysql> show slave status \G; <------查看slave状态
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.245.204
Master_User: myslave
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master-bin.000001
Read_Master_Log_Pos: 604
Relay_Log_File: relay-log-bin.000002
Relay_Log_Pos: 321
Relay_Master_Log_File: master-bin.000001
Slave_IO_Running: Yes <------yes为正常状态
Slave_SQL_Running: Yes <------yes为正常状态
4、在第二台slave服务器上也做相同操作,注意server-id不能相同
[root@slave2 ~]# vim /etc/my.cnf
server-id = 23
relay-log=relay-log-bin
relay-log-index=slave-relay-bin.index
[root@salve2 ~]# systemctl restart mysqld
mysql> change master to master_host='192.168.245.204',master_user='myslave',master_password='123456',master_log_file='master-bin.000001',master_log_pos=604;
Query OK, 0 rows affected, 2 warnings (0.02 sec)
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.245.204
Master_User: myslave
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master-bin.000001
Read_Master_Log_Pos: 604
Relay_Log_File: relay-log-bin.000002
Relay_Log_Pos: 321
Relay_Master_Log_File: master-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
3.1 测试同步
master:新建shool数据库
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
mysql> create database school;
Query OK, 1 row affected (0.00 sec)
两个slave:都能查看到shool数据库即为主从同步成功!
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| school |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| school |
| sys |
+--------------------+
5 rows in set (0.01 sec)
4、读写分离
4.1 所需软件
jdk-6u14-linux-x64.bin
amoeba-mysql-binary-2.2.0.tar.gz
在amouba服务器上做如下操作:
1、安装jdk1.6
[root@amoeba opt]# cp jdk-6u14-linux-x64.bin /usr/local/
[root@amoeba opt]# cd /usr/local/
[root@amoeba local]# chmod +x jdk-6u14-linux-x64.bin
[root@amoeba local]# ./jdk-6u14-linux-x64.bin
一直按空格,中间有个地方需要输入yes or no
输入yes
Press Enter to continue.....
回车
Done.
[root@amoeba local]# mv jdk1.6.0_14/ /usr/local/jdk1.6
[root@amoeba local]# vim /etc/profile
export JAVA_HOME=/usr/local/jdk1.6
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$JAVA_HOME/lib:$JAVA_HOME/jre/bin:$PATH:$HOME/bin
export AMOEBA_HOME=/usr/local/amoeba
export PATH=$PATH:$AMOEBA_HOME/bin
[root@amoeba local]# source /etc/profile
2、安装amoeba
[root@amoeba local]# mkdir /usr/local/amoeba
[root@amoeba local]# cd /opt
[root@amoeba opt]# tar zxvf amoeba-mysql-binary-2.2.0.tar.gz -C /usr/local/amoeba/
[root@amoeba opt]# cd /usr/local/
[root@amoeba local]# chmod -R 755 amoeba/
[root@amoeba local]# /usr/local/amoeba/bin/amoeba <-----测试安装成功
amoeba start|stop
3、三台主从服务器上添加用户授权给amoeba来访问
mysql> grant all on *.* to test@'192.168.245.%' identified by '123.com';
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
4、回到amoeba服务器,修改amoeba的两个配置文件
- amoeba.xml
- dbServers.xml
[root@amoeba local]# cd amoeba/
[root@amoeba amoeba]# ls
benchmark bin changelogs.txt conf lib LICENSE.txt README.html
[root@amoeba amoeba]# vim conf/amoeba.xml
30 <property name="user">amoeba</property>
32 <property name="password">123456</property>
115 <property name="defaultPool">master</property>
116
117
118 <property name="writePool">master</property>
119 <property name="readPool">slaves</property>
[root@amoeba amoeba]# vim conf/dbServers.xml
22 <!-- mysql schema -->
23 <property name="schema">mysql</property>
24
25 <!-- mysql user -->
26 <property name="user">test</property>
27
28 <!-- mysql password -->
29 <property name="password">123.com</property>
45 <dbServer name="master" parent="abstractServer">
48 <property name="ipAddress">192.168.245.204</property>
52 <dbServer name="slave1" parent="abstractServer">
55 <property name="ipAddress">192.168.245.205</property>
59 <dbServer name="slave2" parent="abstractServer">
62 <property name="ipAddress">192.168.245.206</property>
66 <dbServer name="slaves" virtual="true">
72 <property name="poolNames">slave1,slave2</property>
启动amoeba:前台会显示详细信息
[root@amoeba amoeba]# /usr/local/amoeba/bin/amoeba start&
[1] 96536
[root@amoeba amoeba]# log4j:WARN log4j config load completed from file:/usr/local/amoeba/conf/log4j.xml
2020-08-29 00:05:09,619 INFO context.MysqlRuntimeContext - Amoeba for Mysql current versoin=5.1.45-mysql-amoeba-proxy-2.2.0
log4j:WARN ip access config load completed from file:/usr/local/amoeba/conf/access_list.conf
2020-08-29 00:05:09,740 INFO net.ServerableConnectionManager - Amoeba for Mysql listening on 0.0.0.0/0.0.0.0:8066.
2020-08-29 00:05:09,744 INFO net.ServerableConnectionManager - Amoeba Monitor Server listening on /127.0.0.1:264.
另外再开一个终端,查看服务是否成功,监听端口为8066即为成功启动
[root@amoeba ~]# netstat -anpt | grep java
tcp6 0 0 127.0.0.1:264 :::* LISTEN 96536/java
tcp6 0 0 :::8066 :::* LISTEN 96536/java
4.2 测试读写分离
客户机:启动mysql数据库,用amoeba用户和密码123456可以连接到主服务器即为成功
[root@client ~]# systemctl start mysqld
[root@client ~]# netstat -anpt | grep 3306
tcp6 0 0 :::3306 :::* LISTEN 39057/mysqld
[root@client ~]# mysql -u amoeba -p123456 -h 192.168.245.203 -P8066
mysql> use school;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql>
master上在shool数据库中新建一个info表,slave服务器也能同步这个表
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| school |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> use school;
Database changed
mysql> show tables;
Empty set (0.00 sec)
mysql> create table info(id int(3) primary key,name char(20) not null,address varchar(50));
Query OK, 0 rows affected (0.01 sec)
两台从服务器上查看到info表,但此时还没有内容
mysql> use school;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+------------------+
| Tables_in_school |
+------------------+
| info |
+------------------+
1 row in set (0.00 sec)
mysql> select * from info;
Empty set (0.00 sec)
这时让两台从服务器停止同步
mysql> stop slave;
Query OK, 0 rows affected (0.00 sec)
在主服务器info表中插入第一条记录
mysql> insert into info values(1,'zhangsan','nanjing');
Query OK, 1 row affected (0.03 sec)
但从服务器没有同步了,所以查询不到记录
mysql> select * from info;
Empty set (0.00 sec)
这时再两台从服务器上分别插入2,3条记录
mysql> insert into info values(2,'lisi','beijing');
Query OK, 1 row affected (0.00 sec)
mysql> insert into info values(3,'wangwu','hangzhou');
Query OK, 1 row affected (0.00 sec)
客户机上查询记录,轮询显示2,3条记录,但没有第一条记录,证明读的操作是给slave做了
mysql> select * from info;
+----+------+---------+
| id | name | address |
+----+------+---------+
| 2 | lisi | beijing |
+----+------+---------+
1 row in set (0.01 sec)
mysql> select * from info;
+----+--------+----------+
| id | name | address |
+----+--------+----------+
| 3 | wangwu | hangzhou |
+----+--------+----------+
1 row in set (0.01 sec)
mysql> select * from info;
+----+------+---------+
| id | name | address |
+----+------+---------+
| 2 | lisi | beijing |
+----+------+---------+
1 row in set (0.00 sec)
mysql> select * from info;
+----+--------+----------+
| id | name | address |
+----+--------+----------+
| 3 | wangwu | hangzhou |
+----+--------+----------+
1 row in set (0.00 sec)
这时在客户机上插入一条记录
mysql> insert into info values(4,'zhaoliu','suzhou');
Query OK, 1 row affected (0.04 sec)
而客户机本身始终读不到该记录
mysql> select * from info;
+----+------+---------+
| id | name | address |
+----+------+---------+
| 2 | lisi | beijing |
+----+------+---------+
1 row in set (0.01 sec)
mysql> select * from info;
+----+--------+----------+
| id | name | address |
+----+--------+----------+
| 3 | wangwu | hangzhou |
+----+--------+----------+
1 row in set (0.01 sec)
但是在master上查看到了该数据,说明了相当于是master在做写的操作
mysql> select * from info;
+----+----------+---------+
| id | name | address |
+----+----------+---------+
| 1 | zhangsan | nanjing |
| 4 | zhaoliu | suzhou |
+----+----------+---------+
2 rows in set (0.00 sec)
主从复制与读写分离部署成功!