一、MySQL读写分离
1.概念:
读写分离,基本的原理是让主数据库处理事务性增、改、删操作(insert、update、delete),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。
2.为什么要读写分离呢?
因为数据库的“写”(写10000条数据可能要3分钟)操作是比较耗时的。
但是数据库的“读”(读10000条数据可能只要5秒钟)。
所以读写分离,解决的是:数据库的写入,影响了查询的效率。
3.什么时候要读写分离?
数据库不一定要读写分离,如果程序使用数据库较多时,而更新少,查询多的情况下会考虑使用。利用数据库主从同步,再通过读写分离可以分担数据库压力,提高性能。
4.主从复制与读写分离
在实际的生产环境中,对数据库的读和写都在同一个数据库服务器中,是不能满足实际需求的。无论是在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。因此,通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力。有点类似于rsync,但是不同的是rsync是对磁盘文件做备份,而mysql主从复制是对数据库中的数据、语句做备份。
二、MySQL主从复制原理
2.1 MySQL复制类型
MySQL的主从复制类型是基于二进制日志类型来实现的。
- STATEMENT:基于语句的复制。在服务器上执行sql语句,在从服务器上执行同样的语句,mysql默认采用基于语句的复制,执行效率高。但是高并发情况下,会出现SQL语句执行顺序紊乱的错误。
- ROW:基于行的复制。把改变的内容复制过去,而不是把命令在从服务器上执行一遍。
- MIXED:混合类型的复制。默认采用基于语句的复制,一旦发现基于语句无法精确复制时,就会采用基于行的复制。
2.2 主从复制工作过程
两个日志(主:binlog二进制日志,从:relay-log中继日志)
四个线程(主:dump线程和ack collector线程,从:IO线程和SQL线程)
- 主库(master)如果发生数据更新,会将写入操作记录到二进制日志(bin log)里
- 从库(slave)探测到主库的二进制日志发生了更新,就会开启IO线程向主库请求二进制日志事件
- 主库会为每个从库IO线程的请求开启DUMP线程,并发送二进制日志事件给从库
- 从库接收到二进制日志事件后会保存到自己的中继日志(relay log)中
附:在半同步模式下从库会返回确认信息给主库,主库会用ack收集线程接收从库反馈的确认信息(5.7版本开始支持) - 从库还会开启SQL线程读取中继日志里的事件,并在本地重放(将二进制日志事件解析成sql语句逐一执行),从而实现主库和从库的数据一致
注:
- 中继日志通常会位于 OS 缓存中,所以中继日志的开销很小。
- 复制过程有一个很重要的限制,即复制在 Slave上是串行化的,也就是说 Master上的并行更新操作不能在 Slave上并行操作。
2.3 MySQL 读写分离原理
读写分离就是只在主服务器上写,只在从服务器上读。基本的原理是让主数据库处理事务性操作,而从数据库处理select查询。数据库复制被用来主数据库上事务性操作导致的变更,同步到集群中的从数据库。
2.4 读写分离的两种实现方式
(1)基于程序代码内部实现
在代码中根据 select、insert 进行路由分类,这类方法也是目前生产环境应用最广泛的。
优点是性能较好,因为在程序代码中实现,不需要增加额外的设备为硬件开支;缺点是需要开发人员来实现,运维人员无从下手。
但是并不是所有的应用都适合在程序代码中实现读写分离,像一些大型复杂的Java应用,如果在程序代码中实现读写分离对代码改动就较大。
(2)基于中间代理层实现
代理一般位于客户端和服务器之间,代理服务器接到客户端请求后通过判断后转发到后端数据库,有以下代表性程序。
(1)MySQL-Proxy。MySQL-Proxy 为 MySQL 开源项目,通过其自带的 lua 脚本进行SQL 判断。
(2)Atlas。是由奇虎360的Web平台部基础架构团队开发维护的一个基于MySQL协议的数据中间层项目。它是在mysql-proxy 0.8.2版本的基础上,对其进行了优化,增加了一些新的功能特性。360内部使用Atlas运行的mysql业务,每天承载的读写请求数达几十亿条。支持事物以及存储过程。
(3)Amoeba。由陈思儒开发,作者曾就职于阿里巴巴。该程序由Java语言进行开发,阿里巴巴将其用于生产环境。但是它不支持事务和存储过程。
(4)Mycat。是一款流行的基于Java语言编写的数据库中间件,是一个实现了MySql协议的服务器,其核心功能是分库分表。配合数据库的主从模式还可以实现读写分离。
三、实验:实现MySQL主从复制
首先装mysqlLNMP网站架构分布式搭建部署-CSDN博客
1 搭建MySQL主从复制步骤
开启三台虚拟机:
主:192.168.10.16 centos7
从1:192.168.10.18 centos7
从2:192.168.10.19 centos7
服务端: 192.168.10.20 centos7 为主从服务器提供时钟源
初始化操作:
(1)开启同步,看看四台MySQL是否同一版本
###关闭和禁止防火墙开机自启功能和关闭安全模块
systemctl disable --now firewalld.service
setenforce 0
vim /etc/selinux/config
SELINUX=disabled
(2)时间同步
我的四台服务器都为nat模式(但其实只要服务端联网即可),都要开chrony服务用于时间同步,MHA服务端 配置chrony与阿里云时间服务器同步,其他三台数据库服务器server指向服务端ip
[root@localhost ~]# rpm -q ntp
ntp-4.2.6p5-29.el7.centos.2.x86_64
[root@localhost ~]# rpm -qa chrony
chrony-3.4-1.el7.x86_64
[root@localhost ~]# systemctl start chronyd #开启chronyd服务
[root@localhost ~]# vim /etc/chrony.conf #配置chrony
[root@localhost ~]# systemctl restart chronyd #配置完重启服务
[root@localhost ~]# timedatectl #查看是否时间同步Local time: 三 2024-06-26 20:18:35 CSTUniversal time: 三 2024-06-26 12:18:35 UTCRTC time: 三 2024-06-26 12:18:34Time zone: Asia/Shanghai (CST, +0800)NTP enabled: yes #yes
NTP synchronized: yes #yesRTC in local TZ: noDST active: n/a
[root@localhost ~]# vim /etc/selinux/config
注释原本官方源,添加本地提供时钟源的IP,
使用timedatectl查看三台服务器时间是否一致
以上方法是可以让数据库服务器不联外网,也可实现时间同步。
第二种方法就是 三台机全部直接server ntp.aliyun.com iburst 全部都和阿里云时间同步。
(3)服务器修改主机名
hostnamectl set-hostname 主机名
(3)主从服务器启动mysql
systemctl start mysqld
(4)设置参数实现通过主机名管理这些主机(四台服务器同时修改,操作一致)
[root@mysql1 ~]# vim /etc/hosts
(5)配置主从服务器(三台同样操作,id要改)
修改/etc/my.cnf文件,开启二进制日志和中继日志
log-bin=mysql-bin
binlog_format=mixed
relay-log=relay-log-bin
relay-log-index=relay-log-bin.index
log-slave-updates = 1
配置完重启服务
systemctl restart mysqld
(6)主从服务器创建mysql和mysqlbinlog的软链接(三台同样操作)
ln -s /usr/local/mysql/bin/mysql /usr/sbin/
ln -s /usr/local/mysql/bin/mysqladmin /usr/sbin/
(7)主从服务器创建并授权数据库主从复制和MHA用户
主服务器(192.168.10.16)
mysql -u root -pabc123
create user 'myslave'@'192.168.10.%' identified by 'myslave123'; #创建用户
grant replication slave on *.* to 'myslave'@'192.168.10.%'; #给权限create user 'manger'@'192.168.10.%' identified by 'manger123'; #MHA用户
grant all on *.* to 'manger'@'192.168.10.%'; #如果单纯做主从复制可忽略这两行flush privileges; #刷新
show master status; #用于显示主服务器的状态信息,返回主服务器的二进制日志文件名和位置
从服务器(192.168.10.18)(192.168.10.19)
mysql -u root -pabc123
create user 'myslave'@'192.168.10.%' identified by 'myslave123';
grant replication slave on *.* to 'myslave'@'192.168.10.%';
create user 'manger'@'192.168.10.%' identified by 'manger123';
grant all on *.* to 'manger'@'192.168.10.%';
flush privileges;
change master to master_host='192.168.10.16', master_port=3306, master_user='myslave', master_password='myslave123', master_log_file='mysql-bin.000001', master_log_pos=1910; ##file和pos值为主服务器查询的值
start slave;
show slave status\G #检查状态
set global read_only=1; #设置为只读
8)验证:
主服务器进程创建库、表,并插入数据
———————————————————————主服务器——————————————————
mysql> create database cywl; //创建库
Query OK, 1 row affected (0.00 sec)mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| cywl |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)mysql> use cywl;
Database changed
mysql> create table xy101 (id int, name varchar(20)); //创建表
Query OK, 0 rows affected (0.00 sec)mysql> insert into xy101 values (1, 'yuan'); //插入数据
Query OK, 1 row affected (0.01 sec)mysql> insert into xy101 values (2, 'zhi');
Query OK, 1 row affected (0.00 sec)mysql> select * from xy101;
+------+------+
| id | name |
+------+------+
| 1 | yuan |
| 2 | zhi |
+------+------+
2 rows in set (0.00 sec)mysql>
——————————————————从服务器查看验证——————————————————
mysql> select * from cywl.xy101;
+------+------+
| id | name |
+------+------+
| 1 | yuan |
| 2 | zhi |
+------+------+
2 rows in set (0.00 sec)mysql>
实验:搭建半同步模式
半同步复制 : 主库在执行完客户端提交的事务后,只要等待一个从库返回响应给主库,才会响应给客户端
此实验基于以上主从复制实验结果为基础
三台虚拟机:一主(1)190.168.10.16,两从(2,3)192.168.10.18,192.168.10.19
主服务器:
(1)添加半同步模式参数,并重启mysql服务;
vim /etc/my.cnf
###添加半同步模式的参数
plugin-load=rpl_semi_sync_master=semisync_master.so #加载mysql半同步复制的插件
rpl_semi_sync_master_enabled=ON #或者设置为"1",即开启半同步复制功能
rpl-semi-sync-master-timeout=1000 #超时时间为1000ms,即1ssystemctl restart mysqld
vim /etc/my.cnf
————————————————————————————————————————————————————————————————
plugin-load=rpl_semi_sync_master=semisync_master.so
rpl_semi_sync_master_enabled=ON
rpl-semi-sync-master-timeout=1000
————————————————————————————————————————————————————————————————
systemctl restart mysqld
[root@l1 ~]# systemctl restart mysqld.service
从服务器(两个服务器配置一致):
从服务器二号机
vim /etc/my.cnf————————————插入————————————————————————————————
plugin-load=rpl_semi_sync_slave=semisync_slave.so
rpl_semi_sync_slave_enabled=ON
————————————————————————————————————————————————systemctl restart mysqld
从服务器三号机和二号机操作一样
主服务器查看:
mysql -u root -pabc123;
show status like '%Rpl_semi%'; #在主库查询半同步状态
参考说明:
Rpl_semi_sync_master_clients #半同步复制客户端的个数
Rpl_semi_sync_master_net_avg_wait_time #平均等待时间(默认毫秒)
Rpl_semi_sync_master_net_wait_time #总共等待时间
Rpl_semi_sync_master_net_waits #等待次数
Rpl_semi_sync_master_no_times #关闭半同步复制的次数
Rpl_semi_sync_master_no_tx #表示没有成功接收slave提交的次数
Rpl_semi_sync_master_status #表示当前是异步模式还是半同步模式,on为半同步
Rpl_semi_sync_master_timefunc_failures #调用时间函数失败的次数
Rpl_semi_sync_master_tx_avg_wait_time #事物的平均传输时间
Rpl_semi_sync_master_tx_wait_time #事物的总共传输时间
Rpl_semi_sync_master_tx_waits #事物等待次数
Rpl_semi_sync_master_wait_pos_backtraverse #可以理解为"后来的先到了,而先来的还没有到的次数"
Rpl_semi_sync_master_wait_sessions #当前有多少个session因为slave的回复而造成等待
Rpl_semi_sync_master_yes_tx #成功接受到slave事物回复的次数
实验:搭建MySQL读写分离:
centos7-2(192.168.9.210)与centos7-5(192.168.9.120)
二号机(192.168.10.18),三号机(192.168.10.19)做从服务器;
centos7-8(192.168.9.150)
一号机(192.168.10.16)做主服务器;
centos7-7(192.168.9.140)
四号机(192.168.10.20)Amoeba服务器
该“案例实施:搭建MySQL读写分离”,是在“案例实施:搭建MySQL主从复制”的基础上进行的,因此此处省略MySQL主从复制的步骤
Amoeba服务器:
1.初始化操作
[root@l4 ~]# systemctl disable --now firewalld.service
Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
[root@l4 ~]# setenforce 0
[root@l4 ~]# vim /etc/selinux/config
[root@l4 ~]#
[root@l4 ~]# mount /dev/sr0 /mnt
mount: /dev/sr0 写保护,将以只读方式挂载
[root@l4 ~]# df
文件系统 1K-块 已用 可用 已用% 挂载点
devtmpfs 914412 0 914412 0% /dev
tmpfs 931496 0 931496 0% /dev/shm
tmpfs 931496 10448 921048 2% /run
tmpfs 931496 0 931496 0% /sys/fs/cgroup
/dev/mapper/centos-root 38770180 4576740 34193440 12% /
/dev/sda1 1038336 191104 847232 19% /boot
tmpfs 186300 12 186288 1% /run/user/42
tmpfs 186300 0 186300 0% /run/user/0
/dev/sr0 4635056 4635056 0 100% /mnt
[root@l4 ~]#
2.安装 Java 环境
rpm -qa | grep jdk
yum remove java*
#因为 Amoeba 基于是 jdk1.5 开发的,所以官方推荐使用 jdk1.5 或 1.6 版本,高版本不建议使用。
[root@l4 ~]# cd /opt/
[root@l4 opt]# ls #传入amoeba包
amoeba-mysql-binary-2.2.0.tar.gz jdk-6u14-linux-x64.bin rhchmod +x jdk-6u14-linux-x64.bin./jdk-6u14-linux-x64.bin
//按yes,按entermv jdk1.6.0_14/ /usr/local
lsmkdir /usr/local/amoebatar xf amoeba-mysql-binary-2.2.0.tar.gz -C /usr/local/amoeba
cd /usr/local
vim /etc/profile.d/jdk1.6.sh
export JAVA_HOME=/usr/local/jdk1.6.0_14
export CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export AMOEBA_HOME=/usr/local/amoeba
export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$AMOEBA_HOME/bin
#添加配置
source /etc/profile
java -version
cd /usr/local/amoeba/bin
./amoeba #如显示amoeba start|stop说明安装成功
cd /usr/local/amoeba/conf/
cp amoeba.xml{,.bak} #修改前先进行备份
vim amoeba.xml #修改amoeba配置文件<property name="user">lin</property> ##30行修改用户名<property name="password">lin123</property> ##32行修改密码
##115行默认服务器连接到master组 #该用户名及密码为客户端连接amoeba的用户名及密码
<property name="defaultPool">master</property>
##117行去掉注释并修改
<property name="writePool">master</property>
<property name="readPool">slaves</property>
cd /usr/local/amoeba/conf/
cp amoeba.xml{,.bak}vim amoeba.xml
主从服务器创建用户
主服务器:
mysql -uroot -pabc123create user 'amoeba'@'192.168.18.%' identified by 'amoeba123';grant all on *.* to 'amoeba'@'192.168.18.%';
两个从服务器
mysql -uroot -pabc123grant all on *.* to 'amoeba'@'192.168.18.%' identified by 'amoeba123';
Amoeba服务器
cd /usr/local/amoeba/conf/
cp dbServers.xml{,.bak}
vim dbServers.xml
注释23行
##设置为amoeba用户
<property name="user">amoeba</property>
##删除30行注释,28行注释添加完整,29行密码修改
<property name="password">amoeba123</property>
##44行47行分别设置主服务器名字和地址 ##此处用户名和密码为amoeba与主从服务器连接用
<dbServer name="master" parent="abstractServer">
<property name="ipAddress">192.168.9.150</property>
##51行54行分别设置从服务器名字和地址
<dbServer name="slave1" parent="abstractServer">
<property name="ipAddress">192.168.9.210</property>
##56行下额外添加一个从服务器配置<dbServer name="slave2" parent="abstractServer"><factoryConfig><!-- mysql ip --><property name="ipAddress">192.168.9.120</property></factoryConfig></dbServer>
##64行设置服务器池名称
<dbServer name="slaves" virtual="true">
##70行定义两个服务器地址
<property name="poolNames">slave1,slave2</property>
cd /usr/local/amoeba/conf/cp dbServers.xml{,.bak}vim dbServers.xml
cd /usr/local/amoeba/bin./amoeba start & //后台运行amoebanetstat -lntp | grep 8066
[root@l4 bin]# netstat -lntp | grep 8066
tcp6 0 0 :::8066 :::* LISTEN 66180/java
测试
使用navicat
关闭从库进行验证
测试1
stop slave; #关闭两个从库
测试2
当两台从服务器重新开启时