文章目录
1.案例分析
1.1 案例概述
- 在企业应用中,成熟的业务通常数据量都比较大
- 单台MySQL在安全性、高可用性和高并发方面都无法满足实际的需求
- 配置多台主从数据库服务器以实现读写分离
1.2 案例前置知识点
1.2.1 MySQL主从复制原理
- MySQL的复制类型:
基于语句的复制:在主服务器上执行的SQL语句,在从服务器上执行同样的语句。MySQL默认采用基于语句的复制,效率比较高。
基于行的复制:把改变的内容复制过去,而不是把命令在从服务器上执行一遍。
混合类型的复制:默认采用基于语句的复制,一旦发现基于语句无法精确复制时,就会采用基于行的复制。
- MySQL主从复制的工作过程
复制的基本过程如下:
1.Master将用户对数据库更新的操作以二进制格式保存到 Binary Log 日志文件中;
2.Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;
3.Master接收到来自Slave的IO进程的请求后,通过负责复制的IO进程根据请求信息读取制定日志指定位置之后的日志信息,
返回给Slave的IO进程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到Master端的bin-log文件的
名称以及bin-log的位置;
4.Slave的IO进程接收到信息后,将接收到的日志内容依次添加到Slave端的relay-log文件的最末端,并将读取到的Master
端的bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚的告诉Master "我需要从某个
bin-log的哪个位置开始往后的日志内容,请发给我";
5.Slave的Sql进程检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在Master端真实执行时候的那些
可执行的内容,并在自身执行。
注意:复制过程有个很重要的限制,即复制在Slave上是串行化的,也就是说Master上的并行更新操作不能在Slave上并行操作
1.2.2 MySQL读写分离原理
- 只在主服务器上写,只在从服务器上读
- 主数据库处理事务性查询,从数据库处理SELECT查询
- 数据库复制用于将事务性查询的变更同步到集群中的从数据库
- 读写分离方案
基于程序代码内部实现
基于中间代理层实现:代理一般位于客户端与服务器之间,代理服务器接到客户端请求后通过判断转发到后端数据库,
有两个代表性程序 "MySQL-Proxy Amoeba"
- 读写分离过程
1.3 案例环境
主机 | 操作系统 | IP地址 | 主要软件 |
---|---|---|---|
Master | CentOS-7-x86_64 | 192.168.140.20 | cmake-2.8.6.tar/mysql-boost-5.7.20.tar |
Slaverl | CentOS-7-x86_64 | 192.168.140.21 | cmake-2.8.6.tar/mysql-boost-5.7.20.tar |
Slaver2 | CentOS-7-x86_64 | 192.168.140.22 | cmake-2.8.6.tar/mysql-boost-5.7.20.tar |
Amoeba | CentOS-7-x86_64 | 192.168.140.14 | jdk-8u91-linux-x64.tar/amoeba-mysql-3.0.5-RC-distribution |
客户端 | CentOS-7-x86_64 | 192.168.140.13 |
2.案例实施
2.1 搭建MySQL主从复制
2.1.1 建立时间同步环境
- 确保在能上网的环境下
- 主节点上搭建时间同步服务器
[root@master ~]# rpm -qa | grep ntpdate '//查询软件包是否安装'
ntpdate-4.2.6p5-25.el7.centos.2.x86_64
[root@master ~]# yum -y install ntp ntpdate '//确认已安装'
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
* base:
软件包 ntp-4.2.6p5-25.el7.centos.2.x86_64 已安装并且是最新版本
软件包 ntpdate-4.2.6p5-25.el7.centos.2.x86_64 已安装并且是最新版本
无须任何处理
[root@master ~]# ntpdate ntp.aliyun.com
28 Dec 14:01:33 ntpdate[73315]: adjust time server 203.107.6.88 offset 0.391783 sec
[root@master ~]# vi /etc/ntp.conf
...
restrict 192.168.140.0 mask 255.255.255. 0 nomodify notrap //修改第17行设置自己的网段
//删除21-24行,添加以下配置
server 127.127.1.0
fudge 127.127.1.0 stratum 8 //设置时间服务器的层级为8级,顶级是0
[root@master ~]# systemctl restart ntpd
[root@master ~]# netstat -anptu | grep ntpd
udp 0 0 192.168.122.1:123 0.0.0.0:* 73375/ntpd
udp 0 0 192.168.140.20:123 0.0.0.0:* 73375/ntpd
udp 0 0 127.0.0.1:123 0.0.0.0:* 73375/ntpd
udp 0 0 0.0.0.0:123 0.0.0.0:* 73375/ntpd
udp6 0 0 ::1:123 :::* 73375/ntpd
udp6 0 0 fe80::a5ca:bfa5:47e:123 :::* 73375/ntpd
udp6 0 0 :::123 :::* 73375/ntpd
- 从服务器上
[root@slaver1 ~]# rpm -qa | grep ntpdate
ntpdate-4.2.6p5-25.el7.centos.2.x86_64
[root@slaver1 ~]# ntpdate 192.168.140.20
28 Dec 14:09:06 ntpdate[74804]: adjust time server 192.168.140.20 offset 0.223987 sec
[root@slaver2 ~]# rpm -qa | grep ntpdate
ntpdate-4.2.6p5-25.el7.centos.2.x86_64
[root@slaver2 ~]# ntpdate 192.168.140.20
28 Dec 14:09:13 ntpdate[72436]: step time server 192.168.140.20 offset 1245572.722417 sec
- 为了保证ntp时间同步时刻保持同步,需要使用crontab计划任务
[root@master ~]# which ntpdate
/usr/sbin/ntpdate
[root@master ~]# crontab -e
*/30 * * * * /usr/sbin/ntpdate ntp.aliyun.com
[root@slaver1 ~]# crontab -e
*/3 * * * * /usr/sbin/ntpdate 192.168.140.20
[root@slaver2 ~]# crontab -e
*/3 * * * * /usr/sbin/ntpdate 192.168.140.20
发送键入所有会话
先关闭防火墙
systemctl stop firewalld
setenforce 0
[root@master ~]# date
2020年 12月 28日 星期一 14:15:03 CST
[root@slaver1 ~]# date
2020年 12月 28日 星期一 14:15:03 CST
[root@slaver2 ~]# date
2020年 12月 28日 星期一 14:15:03 CST
- 此刻表示时间同步搭建完成
2.1.2 配置master主服务器
[root@master ~]# vi /etc/my.cnf '//在mysqld下面配置'
...
server_id =1
log_bin=master_bin '//设置二进制日志的抬头'
log_slave_updates=true '//允许从库更新'
[root@master ~]# systemctl restart mysqld.service
[root@master ~]# mysqladmin -uroot password 'abc123'
[root@master ~]# mysql -uroot -pabc123
...
mysql> grant replication slave on *.* to 'myslave'@'192.168.140.%' identified by 'abc123';
mysql> flush privileges;
mysql> show master status;
+-------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+-------------------+
| master_bin.000001 | 864 | | | |
+-------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
2.1.3 配置slaver从服务器
- 从slaver1上的配置
[root@slaver1 ~]# vi /etc/my.cnf
...
server_id = 2 '//该值不能有重复'
relay_log=relay-log-bin '//设置中继日志的抬头'
relay_log_index=slave-relay-bin.index '//设置索引'
[root@master ~]# systemctl restart mysqld.service
[root@slaver1 ~]# mysqladmin -uroot password 'abc123'
[root@slaver1 ~]# mysql -uroot -pabc123
...
mysql> change master to master_host='192.168.140.20',master_user='myslave',master_password='abc123',master_log_file='master_bin.000001',master_log_pos=864;
mysql> start slave;
mysql> show slave status \G
- 从slaver2上的配置
修改server-id其余配置与1上一样
最后查看IO和SQL线程是否为YES
[root@slaver2 ~]# vi /etc/my.cnf
...
server_id = 3
2.1.4 验证主从复制
- 在主服务器上
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
mysql> create database test; '//主服务器上创建好数据库,从服务器上查看'
- 从服务器上
[root@slave1 ~]# mysql -uroot -pabc123
...
mysql> show databases;
2.2 搭建MySQL读写分离
2.2.1 在前端服务器上搭建NTP时间同步
[root@amoeba ~]# ntpdate 192.168.140.20
28 Dec 15:23:42 ntpdate[55032]: adjust time server 192.168.140.20 offset 0.454341 sec
[root@amoeba ~]# crontab -e
*/3 * * * * /usr/sbin/ntpdate 192.168.140.20
2.2.2 安装JDK环境
- 导入软件包
[root@amoeba ~]# tar zxvf jdk-8u91-linux-x64.tar.gz //解压
[root@amoeba ~]# cp -rv jdk1.8.0_91/ /usr/local/java //将加压后的文件夹复制到java目录下
[root@amoeba ~]# vi /etc/profile //设置环境变量
...//末尾添加以下配置
export JAVA_HOME=/usr/local/java
export JRE_HOME=/usr/local/java/jre
export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
export CLASSPATH=./:$JAVA_HOME/lib:$JRE_HOME/lib
[root@amoeba ~]# source /etc/profile //让环境变量立即生效
[root@amoeba ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/usr/local/java/bin:/usr/local/java/jre/bin
[root@amoeba ~]# java -version
openjdk version "1.8.0_131"
OpenJDK Runtime Environment (build 1.8.0_131-b12)
OpenJDK 64-Bit Server VM (build 25.131-b12, mixed mode)
2.2.3 配置amoeba代理服务器
- 导入软件包
[root@amoeba ~]# unzip amoeba-mysql-3.0.5-RC-distribution.zip
[root@amoeba ~]# mv amoeba-mysql-3.0.5-RC/ /usr/local/amoeba
[root@amoeba ~]# chmod -R 755 /usr/local/amoeba/
[root@amoeba ~]# vi /usr/local/amoeba/jvm.properties
//注释掉第32行,并添加以下配置
#JVM_OPTIONS="-server -Xms256m -Xmx1024m -Xss196k -XX:PermSize=16m -XX:MaxPermSize=96m"
JVM_OPTIONS="-server -Xms1024m -Xmx1024m -Xss256k"
- 设置启动脚本
[root@amoeba ~]# vi /etc/init.d/amoeba
#!/bin/bash
#chkconfig: 35 20 90
export JAVA_HOME=/usr/local/java
export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin
NAME=Amoeba
AMOEBA_BIN=/usr/local/amoeba/bin/launcher
SHUTDOWN_BIN=/usr/local/amoeba/bin/shutdown
PIDFILE=/usr/local/amoeba/Amoeba-MySQL.pid
SCRIPTNAME=/etc/init.d/amoeba
case "$1" in
start)
echo -n "Starting $NAME..."
$AMOEBA_BIN
echo "done"
;;
stop)
echo -n "Stopping $NAME..."
$SHUTDOWN_BIN
echo "done"
;;
restart)
$SHUTDOWN_BIN
sleep 1
$AMOEBA_BIN
;;
*)
echo "Usage:$SCRIPTNAME {start|stop|restart}"
exit 1
;;
esac
- 设置启动权限,并启动服务
[root@amoeba ~]# chmod 755 /etc/init.d/amoeba
[root@amoeba ~]# chkconfig --add amoeba
[root@amoeba ~]# chkconfig --list
注:该输出结果只显示 SysV 服务,并不包含
原生 systemd 服务。SysV 配置数据
可能被原生 systemd 配置覆盖。
要列出 systemd 服务,请执行 'systemctl list-unit-files'。
查看在具体 target 启用的服务请执行
'systemctl list-dependencies [target]'。
amoeba 0:关 1:关 2:关 3:开 4:关 5:开 6:关
netconsole 0:关 1:关 2:关 3:关 4:关 5:关 6:关
network 0:关 1:关 2:开 3:开 4:开 5:开 6:关
[root@amoeba ~]# systemctl start amoeba //若启动失败可用脚本启动,service amoeba start
[root@amoeba ~]# netstat -anpt | grep 8066
tcp6 0 0 :::8066 :::* LISTEN 56062/java
- 在三台服务器上为amoeba授权(master和slave服务器上)
mysql> grant all privileges on *.* to 'test'@'192.168.140.%' identified by '123.com';
mysql> flush privileges;
- 修改amoeba配置文件
[root@amoeba ~]# cd /usr/local/amoeba
[root@amoeba amoeba]# vi conf/amoeba.xml
...//第28行设置客户端连接amoeba
<property name="user">amoeba</property>
<property name="password">123456</property>
...
//去掉83行注释内容,修改以下配置。将默认的server1改成master,把默认的servers改成slaves
<property name="defaultPool">master</property>
<property name="writePool">master</property>
<property name="readPool">slaves</property>
[root@amoeba amoeba]# vi conf/dbServers.xml
...//修改第26行开始的两行配置,设置授权账户和密码
<property name="user">test</property>
<property name="password">123.com</property>
...
<dbServer name="master" parent="abstractServer"> //第43行,设置为主服务器主机名
<property name="ipAddress">192.168.140.20</property> //第46行,设置主服务器的IP
<dbServer name="slave1" parent="abstractServer"> //第50行,设置为从服务器主机名
<property name="ipAddress">192.168.140.21</property> //第53行,设置从服务器地址
//接着复制slave1模块,修改为slave2的主机名和地址
<dbServer name="slaves" virtual="true"> //第63行,设置修改为slaves
<property name="poolNames">slave1,slave2</property> //第69行,设置为从服务器名
[root@amoeba ~]# service amoeba restart //若启动失败可用脚本启动
[root@amoeba ~]# netstat -anpt | grep 8066
用脚本命令启动amoeba服务
[root@amoeba ~]# cd /usr/local/amoeba/
[root@amoeba amoeba]# cd bin/
[root@amoeba bin]# ll
总用量 32
-rwxr-xr-x. 1 root root 2071 7月 5 2013 benchmark
-rwxr-xr-x. 1 root root 1452 12月 29 2012 benchmark.bat
-rwxr-xr-x. 1 root root 145 12月 27 2012 benchmark.classpath
-rwxr-xr-x. 1 root root 2114 7月 5 2013 launcher
-rwxr-xr-x. 1 root root 1579 12月 27 2012 launcher.bat
-rwxr-xr-x. 1 root root 134 12月 23 2012 launcher.classpath
-rwxr-xr-x. 1 root root 1032 7月 5 2013 mkdirhier
-rwxr-xr-x. 1 root root 1615 7月 5 2013 shutdown
[root@amoeba bin]# ./shutdown //或者用/usr/local/amoeba/bin/shutdown停止服务
[root@amoeba bin]# ./launcher
- 客户机上
[root@client ~]# systemctl stop firewalld
[root@client ~]# setenforce 0
[root@client ~]# yum -y install mariadb*
[root@client ~]# systemctl start mariadb.service
[root@client ~]# mysql -uamoeba -p123456 -h 192.168.140.14 -P8066 //通过代理访问mysql
2.2.4 验证结果
- 在客户机上写入数据
MySQL [(none)]> use test;
Database changed
MySQL [test]> create table t1(id int(10),name varchar(64),address varchar(20));
Query OK, 0 rows affected (0.02 sec)
MySQL [test]> insert into t1 values(1,'luchen','This is Master');
Query OK, 1 row affected (0.01 sec)
- 在主从服务器上查看结果
- 当停掉两台slave从服务器后
mysql> stop slave;
在从slave1上删除原有数据,写入新的数据
mysql> delete from test.t1; //或者用 truncate test.t1 也能删除
mysql> use test;
mysql> insert into t1 values(5,'zuoyou','This is Slave1');
mysql> select * from test.t1;
+------+--------+----------------+
| id | name | address |
+------+--------+----------------+
| 5 | zuoyou | This is Slave1 |
+------+--------+----------------+
1 row in set (0.00 sec)
在从slave2上删除原有数据,写入新的数据
mysql> truncate test.t1;
mysql> use test;
mysql> insert into t1 values(10,'aliang','This is Slave2');
Query OK, 1 row affected (0.01 sec)
mysql> select * from test.t1;
+------+--------+----------------+
| id | name | address |
+------+--------+----------------+
| 10 | aliang | This is Slave2 |
+------+--------+----------------+
1 row in set (0.00 sec)
在客户机上查询test.t1表,可以轮询查看slave1和slave2上表的内容
MySQL [test]> select * from test.t1;
+------+--------+----------------+
| id | name | address |
+------+--------+----------------+
| 5 | zuoyou | This is Slave1 |
+------+--------+----------------+
1 row in set (0.01 sec)
MySQL [test]> select * from test.t1;
+------+--------+----------------+
| id | name | address |
+------+--------+----------------+
| 10 | aliang | This is Slave2 |
+------+--------+----------------+
1 row in set (0.00 sec)
结论:amoeba能读取slave从服务器上写入的数据,具有读的功能
- 当在amoeba代理服务器上写入数据时
MySQL [test]> insert into t1 values(17,'forlo','This is Master2');
在master主服务器上查看发现有数据写入,从slave服务器上并没有数据写入
mysql> select * from test.t1;
+------+--------+-----------------+
| id | name | address |
+------+--------+-----------------+
| 1 | luchen | This is Master |
| 17 | forlo | This is Master2 |
+------+--------+-----------------+
2 rows in set (0.00 sec)
结论:ameoba代理服务器写入的内容会同步到master主服务器,但不会同步到slave从服务器上;
同时master写入的数据,ameoba代理服务器也能读取到。
- 此时表示,MySQL读写分离搭建完成。
补充
在主服务器上查看状态发现position改变
mysql> show master status;
+-------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+-------------------+
| master_bin.000001 | 2232 | | | |
+-------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
结论,当我们在写入数据时,发现位置会更改,再次设置主从复制时,需要修改pos的值为当前的值。