目录
案例概述
- 在企业应用中,成熟的业务通常数据量都比较大
- 单台MySQL在安全性、高可用性和高并发方面都无法满足实际需求
- 配置多台主从数据库服务器来实现读写分离
案例前置知识点
主服务器可以有多个从服务器,一对多
MySQL主从复制原理
-
MySQL复制类型
- 基于语句(SQL语句)的复制:STATMENT
- 要经过很多环节,可能会出错。效率高
- 基于行的复制:ROW
- 复制记录本身,而不是产生记录的语句。效率低
- 混合类型的复制:MIXED
- 把上面两个合并就是混合类型复制
- 复制时优先用基于语句的复制,然后再用基于行的复制
-
MySQL主从复制的过程
- 基于语句(SQL语句)的复制:STATMENT
- 当主服务器数据改动后,有dump线程将执行的事务或语句写入二进制日志中
- 只要有新数据,从服务器会通过的I/O线程去主服务器读取二进制日志文件
- 从服务器将新增加的数据写入到本地的中继日志,这个日志只保存主服务器同步过来的新日志内容
- 只要中继日志内新增了日志信息,再被从服务器的SQL线程进行重放(重新执行新增数据的SQL语句)
- 这样就可以实现主从复制,主节点和从节点的服务器数据就可以达到一致
当从服务器的数据和主服务器的数据实现同步的时候,此时主服务器的二进制日志和从服务器的中继日志两个文件内的position信息是一致的
I/O线程怎么知道有新数据增加的?
当从服务器的中继日志内的信息和主服务器的二进制日志文件不一致了,那么I/O线程就会感知到主服务器有新数据增加,就会去读取
主从复制的缺点
构建主从复制时,最好在业务数据生成之前就构建好主从复制
假如一个主服务器已经运行了一段时间了,主服务器内存储的有业务数据了,这时候如果要实现主从复制的话,只能同步添加从服务器之后新增的数据,而构建主从复制之前的数据就同步不了了
解决的方法
对于上面的场景,可以在实现主从复制之前,把主服务器的数据通过mysqldump导出出来,再拷贝到从服务器导入进去,这个时候两个服务器的数据就一致了,然后再构建主从复制数据就同步了
导出的时候,最好把程序和数据库隔离开,不然在导出的时候,程序添加了新的数据,又要重新导出
案例步骤
本案例的目标是实现主从复制和读写分离,所以需要三台虚拟机来实现本案例
主机角色 | IP地址 |
主服务器 | 192.168.10.101 |
从服务器1 | 192.168.10.102 |
从服务器2 | 192.168.10.103 |
主从复制
初步设置
MySQL数据库
本案例需要三台主机都安装了MySQL数据库,这里就略过不再演示安装步骤,之前的文章有写:http://t.csdnimg.cn/5jnIy
防火墙
把三台主机的防火墙都关闭
[root@localhost ~]# systemctl stop firewalld
时间设置
首先要保证三台主机的时间一致,在主从复制时,如果时间偏差太大,接收方会认为接收的数据和自己的时间不一致,就不会接受数据了
# 101
[root@localhost ~]# date
2024年 07月 30日 星期二 20:36:19 CST
# 102
[root@localhost ~]# date
2024年 07月 30日 星期二 20:36:21 CST
# 103
[root@localhost ~]# date
2024年 07月 30日 星期二 20:36:23 CST
如果时间不一致的话,要么手动修改,要么使用ntp服务来确保主机上的时间和指定服务器时间一致
ntp服务
通过ntp服务将一台主机设置为时间服务器,然后让另外两台主机和该时间服务器同步
在101主机上安装ntp服务的软件包,打开ntp服务的配置文件,在server 3.centos.pool.ntp.org iburst下方添加以下内容,最后重启该服务并设为开机自启
或者让三台主机使用网络上的时间服务器,比如阿里云、腾讯云、百度云的时间服务器
[root@localhost ~]# yum -y install ntp
[root@localhost ~]# vim /etc/ntp.conf
server 127.127.1.0
fudge 127.127.1.0 stratum 8
[root@localhost ~]# systemctl restart ntpd
[root@localhost ~]# systemctl enable ntpd
在另外的主机上也安装ntp服务,并且进行时间同步
[root@localhost ~]# yum -y install ntp
[root@localhost ~]# ntpdate 192.168.10.101
虚拟机设置时间同步
或者在虚拟机设置中打开VMware tools功能,开启虚拟机与宿主机时间的同步
这里的图片只是演示步骤,并不代表最终设置的结果,请自行设置
开启二进制日志记录功能
因为从服务器要通过主服务器的二进制日志文件来进行数据的复制更新,所以在配置文件开启这个功能(默认是关闭的)
首先打开mysql的配置文件
[root@localhost ~]# vim /etc/my.cnf
在[mysqld]单元追加以下内容
[mysqld]
# 主从复制必要参数
server-id=101
log-bin=master-bin
binlog-format=MIXED
# 附加优化参数,可写可不写
binlog-ignore-db=test # 忽略该库的日志记录
binlog-cache-size=1M # 日志缓存大小
expire-logs-days=3 # 自动清理过期日志的天数
log-slave-updates=true # 接收从服务器的更改
- server-id:表示指定服务器的标识,如果要构建一个主从架构的环境,设备之间的标识不能相同,不然会出错
- log-bin=master-bin:指定日志文件生成的前缀
- binlog-format:指定复制方式
-
- STATMENT:基于语句的复制
- ROW:基于行的复制
- MIXED:混合复制
log-slave-updates
这个选项开启后,允许一个MySQL服务器接收上游的数据并扩展给下游的服务器,可以利用这个选项实现主主架构
修改完配置文件后,需要重启服务
[root@localhost ~]# systemctl restart mysqld
授权复制权
要实现主从复制,101需要授权给102和103允许复制的权限
授权所有来自192.168.10.0网段的主机,使用myslave用户允许访问所有库的所有表,密码为123456。
然后刷新权限
[root@localhost ~]# mysql -uroot -ppwd123
mysql> grant replication slave on *.* to 'myslave'@'192.168.10.%' identified by '123456';
mysql> flush privileges;
再使用show命令查看主节点的当前状态
当前位置日志文件名称,和最新位置603
mysql> show master status;
+-------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+-------------------+
| master-bin.000001 | 603 | | test | |
+-------------------+----------+--------------+------------------+-------------------+
此时再来到102主机
打开配置文件,在[mysqld]单元内添加以下内容,注意唯一标识不要重复,然后保存并退出,重启服务
[root@localhost ~]# vim /etc/my.cnf
[mysqld]
server-id=102
relay-log=relay-log-bin
relay-log-index=slave-relay-bin.index
replicate-ignore-db=test # 忽略复制的数据库
[root@localhost ~]# systemctl restart mysqld
再来到103主机
打开配置文件,在[mysqld]单元内添加以下内容,注意唯一标识不要重复,然后保存并退出,重启服务
[root@localhost ~]# vim /etc/my.cnf
[mysqld]
server-id=103
relay-log=relay-log-bin
relay-log-index=slave-relay-bin.index
replicate-ignore-db=test # 忽略复制的数据库
[root@localhost ~]# systemctl restart mysqld
建立连接
此时再来到102主机
在102主机的XShell终端内的任意位置右键选择下图选项,开启输入同步
需要在101主机关闭输入同步,确保只有102和103开启
首先在102主机登录进mysql
[root@localhost ~]# mysql -uroot -ppwd123
使用change语句,修改主服务器的IP为101主机,用来连接的用户和密码改为刚刚在101设置的'myslave'和'123456'
要读取的日志文件指定为刚刚在101使用show master status命令显示的master-bin.000001,日志的位置也指定为刚刚使用show命令显示的603
mysql> change master to master_host='192.168.10.101', master_user='myslave', master_password='123456', master_log_file='master-bin.000001', master_log_pos=603;
然后开启从节点的相关进程,然后使用show slave status;命令查看从节点当前状态
确保IO进程和SQL进程是开启状态(YES),还可以看到其他关于主从节点的信息
只要两个进程都是YES状态,说明主从架构已经构建完成了
如果显示:Slave_IO_Running: Connecting 需要去change语句检查看看是不是密码错了什么的
如果找到问题需要执行change语句,需要先stop slave,然后使用change语句,最后再start slave,再show slave status;检查状态信息
mysql> start slave;
mysql> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.10.101
Master_User: myslave
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master-bin.000001
Read_Master_Log_Pos: 603
Relay_Log_File: relay-log-bin.000001
Relay_Log_Pos: 321
Relay_Master_Log_File: master-bin.000001
Slave_IO_Running: Yes # IO进程状态
Slave_SQL_Running: Yes # SQL进程状态
Replicate_Do_DB:
Replicate_Ignore_DB: test # 不进行复制的库
# ...
关闭输入同步
此时可以关闭输入同步的功能了
验证测试
来到101主机
先创建一个auth库,然后使用show命令查看一下,可以看到列出的数据库里有auth库
mysql> create database auth;
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| auth |
| mysql |
| performance_schema |
| sys |
+--------------------+
102和103主机
在102和103主机直接使用show命令查看所有数据库,在没有创建过的情况下也可以看到auth库了
mysql> show databases;
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| auth |
| mysql |
| performance_schema |
| sys |
+--------------------+
再来到101主机
在auth库内创建一个表,然后插入一条数据
mysql> select * from auth.users;
+------+------+
| id | name |
+------+------+
| 1 | tom |
+------+------+
102主机和103主机
102和103主机此时应该都能查询到这一条数据
mysql> select * from auth.users;
+------+------+
| id | name |
+------+------+
| 1 | tom |
+------+------+
但是如果在从服务器写入数据,除了写入数据的从服务器,其他不管是主服务器还是从服务器都不会同步从服务器写入的数据
所以在主从架构中,只有主服务器写入的数据会被其他从服务器同步
再来到101主机
再创建一个test库,然后进入该库,创建一个表和一条数据
mysql> create database test;
mysql> use test
mysql> create table t1 (id int(10), name char(20));
mysql> insert into t1 values (1, 'tom');
102主机和103主机
102和103主机此时还都能查询到这一条数据
mysql> select * from test.t1;
ERROR 1146 (42S02): Table 'test.t1' doesn't exist
以为我们在配置102和103主机的MySQL配置文件的时候,设置了:replicate-ignore-db=test 选项,忽略复制了test的库,也就是不同步这个库的数据
读写分离
在上面的主从复制的基础上再构建读写分离,增强性能
比如主服务器只负责数据的写入,两个从服务器只负责数据的读取,分担压力
读写分离原理
- 只在主服务器上写,只在从服务器上读
- 主数据库处理事务性查询,从数据库处理select查询
- 数据库复制用于将事务性查询的变更同步到集群中的从数据库
- 读写分离方案
- 基于程序代码内部实现
- 基于中间代理层实现
- MySQL-Proxy
- Amoeba
代理的作用:把接收到的SQL语句分类,哪些是读取的哪些是写入的,再把读取的语句转发给用于读取的服务器处理,将写入的语句转发给用于写入的服务器处理
主机结构
除了三台主从服务器,还需要一台amoeba主机作为代理服务器和一台客户端主机作为测试机
这里再打开两台虚拟机,一共就是5台主机
操作步骤
基本配置
安装jdk
来到104主机
因为amoeba是java开发的,所以导入amoeba的tar包和jdk的二进制文件
由于这里的jdk二进制文件是编译过后的代码,需要执行该文件来安装,所以需要使用chmod命令添加执行权
执行jdk文件,同意条款,按回车继续,安装完成
[root@localhost ~]# chmod +x jdk-6u14-linux-x64.bin
[root@localhost ~]# ./jdk-6u14-linux-x64.bin
Do you agree to the above license terms? [yes or no]
yes
Press Enter to continue..... # 按回车键继续
然后将安装出的目录移动到/usr/local/下 重命名为jdk1.6
[root@localhost ~]# mv jdk1.6.0_14/ /usr/local/jdk1.6
为了能够方便地使用jdk,需要在系统变量中加入jdk的路径,由于jdk不仅仅是提供给系统使用,还有其他程序基于jdk运行,所以不创建软链接,而是直接在变量中写入路径
这里打开全局变量配置文件,在末尾添加以下内容,保存退出然后使用source重新加载该文件
最后可以使用java -version命令来检测jdk是否安装成功
[root@localhost ~]# vim /etc/profile
export JAVA_HOME=/usr/local/jdk1.6
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin
[root@localhost ~]# source /etc/profile
[root@localhost ~]# java -version
java version "1.6.0_14"
安装amoeba
来到104主机
amoeba是一个免安装的软件,中间不需要编译,直接解压就可以使用
[root@localhost ~]# mkdir /usr/local/amoeba
[root@localhost ~]# chmod -R 755 /usr/local/amoeba/
[root@localhost ~]# tar zxvf amoeba-mysql-binary-2.2.0.tar.gz -C /usr/local/amoeba/
添加amoeba的系统变量和命令路径
这里打开全局变量配置文件,在末尾添加以下内容,保存退出然后使用source重新加载该文件
最后使用amoeba命令,查看变量是否配置正常
[root@localhost ~]# vim /etc/profile
export AMOEBA_HOME=/usr/local/amoeba
export PATH=$PATH:$AMOEBA_HOME/bin
[root@localhost ~]# source /etc/profile
[root@localhost ~]# amoeba
amoeba start|stop
来到101主机(主服务器)
amoeba在代理时,访问数据库的不一定是用户,也可能是管理员或开发人员,因此amoeba的访问权限要大一点,除了grant语句的权限其他的语句都能执行
先登录进MySQL,授权给amoeba主机all权限
这里的test用户是提供给代理服务器,前端的用户只能通过代理服务器来连接数据库,而代理服务器只能通过这里的test用户来连接数据库
[root@localhost ~]# mysql -uroot -ppwd123
mysql> grant all on *.* to 'test'@'192.168.10.%' identified by '123.com';
mysql> flush privileges;
修改配置文件
来到104主机
因为amoeba有两个连接,一个连接前端的用户端,一个连接后端的数据库,所以amoeba针对两个连接也提供了两个对应配置文件
- amoeba.xml
- 连接前端客户端的配置文件
- dbServers.xml
- 连接后端数据库的配置文件
修改连接客户端配置文件
打开前端配置文件
[root@localhost conf]# vim /usr/local/amoeba/conf/amoeba.xml
在第30行和32行,修改客户端用于连接代理服务器时的账号和密码
这里的用户和密码只是前端的用户连接amoeba时用到的账户和密码,而不是连接数据库的
<property name="user">amoeba</property>
<property name="password">123456</property>
在第115行修改默认连接的主机
此时这里指定的仅仅是一个名字,master到底是哪个主机?我们还没有定义
<property name="defaultPool">master</property>
在第118和119行,修改负责写入数据的主机和负责读取数据的主机,并把这两行的注释去掉
这里也仅仅是指定的主机名,最终连接的主机我们还没有定义
<property name="writePool">master</property>
<property name="readPool">slaves</property>
保存并退出
修改连接后端数据库配置文件
打开连接后端数据库的配置文件
[root@localhost conf]# vim /usr/local/amoeba/conf/dbServers.xml
先修改第23行的选项,指定连接amoeba默认登录MySQL连接的库为auth(必须已存在的库)
主服务器的数据库里必须要有这个auth库,否则会出错
如果没有这个库,去101主服务器数据库使用create database test;创建该库
<property name="schema">test</property>
再修改第26行和29行的账号密码,根据在101主服务器数据库中授权时创建的用户和密码来修改
29行周围的注释要去掉,并且使用的账号和密码都是必须存在的
<property name="user">test</property>
<property name="password">123.com</property>
在第45行,定义前面编写连接前端客户端配置文件里的master主机和slaves主机,分别修改对应的IP地址
但是我们的架构是2个从服务器,也就是两个slave,所以这里要复制一个dbServer块,再添加一个slave2,然后再组合命名为slaves
<dbServer name="master" parent="abstractServer">
<factoryConfig>
<!-- mysql ip -->
<property name="ipAddress">192.168.10.101</property> # 修改对应IP
</factoryConfig>
</dbServer>
<dbServer name="slave1" parent="abstractServer">
<factoryConfig>
<!-- mysql ip -->
<property name="ipAddress">192.168.10.102</property> # 修改对应IP
</factoryConfig>
</dbServer>
# 添加的slave2 ↓↓↓
<dbServer name="slave2" parent="abstractServer">
<factoryConfig>
<!-- mysql ip -->
<property name="ipAddress">192.168.10.103</property> # 修改对应IP
</factoryConfig>
</dbServer>
# 把slave1和slave2共同命名为slaves
<dbServer name="slaves" virtual="true">
<poolConfig class="com.meidusa.amoeba.server.MultipleServerPool">
<!-- Load balancing strategy: 1=ROUNDROBIN , 2=WEIGHTBASED , 3=HA-->
<property name="loadbalance">1</property>
<!-- Separated by commas,such as: server1,server2,server1 -->
<property name="poolNames">slave1,slave2</property>
</poolConfig>
</dbServer>
注意,需要关闭防火墙或设置相关策略来允许流量通过,这里为了实验方便直接关闭防火墙
保存并退出,开启amoeba
命令结尾加的&符号表示在后台运行
后台运行:开启的程序或服务,在后台运行不会占用前台终端,如果在前台运行的话,终端就不能输入命令了,一直被该程序占用
[root@localhost conf]# systemctl stop firewalld
[root@localhost conf]# amoeba start&
# 回车
然后使用netstat命令查看该程序的状态,如果显示出主从服务器的3个连接,并且显示ESTABLISHED(已建立连接),就说明amoeba启动正常了
如果没有显示主从服务器的3个连接,只显示两个,或少了某几个,就说明配置文件有错误,可能有错别字、注释没删除
[root@localhost conf]# netstat -anpt | grep java
tcp6 0 0 127.0.0.1:1449 :::* LISTEN 1610/java
tcp6 0 0 :::8066 :::* LISTEN 1610/java
tcp6 0 0 192.168.10.104:49672 192.168.10.102:3306 ESTABLISHED 1610/java
tcp6 0 0 192.168.10.104:33674 192.168.10.103:3306 ESTABLISHED 1610/java
tcp6 0 0 192.168.10.104:60556 192.168.10.101:3306 ESTABLISHED 1610/java
这里的8066端口是提供给客户端连接amoeba代理服务器的端口,其余的4个端口都是随机端口
确保正确启动后就可以去105主机进行测试了
测试
数据同步测试
来到105主机(客户端测试机)
因为需要登录MySQL,所以需要先安装一个MySQL的客户端
然后使用mysql命令登录MySQL
这里使用的账户和密码都是刚才在104代理服务器的amoeba连接前端客户端的配置文件中定义的账户和密码,如下图↓
-h:指定连接的IP为192.168.10.104,因为我们要通过代理服务器来访问数据库,而不是直接连接MySQL数据库
-P 8066:如果要连接MySQL数据库本身,就要指定3306,但是我们要通过代理来连接MySQL,所以这里要写成代理的端口号8066(之前在104代理服务器的配置文件中查看 或者 使用netstat查询)
[root@localhost ~]# yum -y install mysql
[root@localhost ~]# mysql -uamoeba -p123456 -h 192.168.10.104 -P 8066
通过代理服务器登录进数据库后,进入auth库,创建一个test表用来测试,然后添加一条数据
MySQL [(none)]> use auth MySQL [auth]> create table test (id int(10), name char(20)); MySQL [auth]> insert into test values (1, 'tom');
在101、102、103主机查询
此时测试机通过代理服务器增加的表和数据都能被3台主从服务器查询到了,数据都被同步了
mysql> use auth
mysql> show tables;
+----------------+
| Tables_in_auth |
+----------------+
| test |
| users |
+----------------+
mysql> select * from test;
+------+------+
| id | name |
+------+------+
| 1 | tom |
+------+------+
在102主机添加
来到102主机添加一条数据
mysql> insert into test values (2, '102');
在103主机添加
再来到103主机添加一条数据
mysql> insert into test values (3, '103');
在105测试机查询
此时查询test表,可以看到表内的信息,是轮流被测试机查询到的
这说明:select的读取操作是轮流发送给2个从服务器的,这种行为可以被称为轮询负载均衡
MySQL [auth]> select * from test;
+------+------+
| id | name |
+------+------+
| 1 | tom |
| 2 | 102 |
+------+------+
MySQL [auth]> select * from test;
+------+------+
| id | name |
+------+------+
| 1 | tom |
| 3 | 103 |
+------+------+
数据查询测试
在102、103从服务器操作
假如2个从服务器发生故障了,这里使用stop slave;命令将两个从服务器停止支持
mysql> stop slave;
在105测试机查询
此时在105测试机查询该表的内容,还是可以查询到102和103两个从服务器插入的数据
这是因为Amoeba通常具有负载均衡功能,即使你停止了从服务器的复制支持,代理服务器仍然会将查询请求发送到多个数据库实例,这样可以避免单点故障并提高响应速度。
MySQL [auth]> select * from test;
+------+------+
| id | name |
+------+------+
| 1 | tom |
| 2 | 102 |
+------+------+
2 rows in set (0.00 sec)
MySQL [auth]> select * from test;
+------+------+
| id | name |
+------+------+
| 1 | tom |
| 3 | 103 |
+------+------+
在101主服务器添加
如果此时在101主服务器添加一条数据然后再去105测试机查询会怎么样呢?
mysql> insert into test values (4, '101');
在105测试机查询
再次使用select语句查询test表的所有内容,可以发现查询的结果还是两个从服务器刚才添加的数据
因为在关闭了两个从服务器的复制支持后,主服务器的新增数据无法再同步给两个从服务器,而从服务器是被我们用来读取数据的服务器,所以只能查询到从服务器同步主服务器数据之前的数据
MySQL [auth]> select * from test;
+------+------+
| id | name |
+------+------+
| 1 | tom |
| 2 | 102 |
+------+------+
2 rows in set (0.02 sec)
MySQL [auth]> select * from test;
+------+------+
| id | name |
+------+------+
| 1 | tom |
| 3 | 103 |
+------+------+