在实际的生产环境中,如果对数据库的读和写都在同一个数据库服务器中操作,无论是在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。因此,一般来说 都是通过主从复制(Master-Slave)来同步数据,再通过读写分离来提升数据库并发负载能力的方案来进行部署与实施。
MySQL数据库自身提供的主从复制功能可以方便的实现数据的多处自动备份,实现数据库的拓展。多个数据备份不仅可以加强数据的安全性,通过实现读写分离还能进一步提升数据库的负载性能。
如图所示,一台主 MySQL 服务器带两台从 MySQL 服务器做数据复制,前端应用在进行数据库写操作时,对主服务器进行操作,在进行数据库读操作时,对两台从服务器进行操作,这样大量减轻了对主服务器的压力(图片来源于网络)。
MySQL 的主从复制和 MySQL 的读写分离两者有着紧密联系,首先要部署主从复制,只有主从复制完成了,才能在此基础上进行数据的读写分离。
基于SQL语句的方式是最直接的方式,也是目前默认的复制方式,后来的三种是MySQL 5以后才出现的复制方式。
实现MySQL数据库之间的主从复制
VMware虚拟机中三台centos7.6虚拟机
MySQL数据库版本5.7.17
master:14.0.0.7 server-id 10
slave1:14.0.0.17 server-id 20
slave2:14.0.0.27 server-id 30
首先在三台虚拟机中都装上mysql,一定要手工编译安装。
手工编译安装MySQL5.7.17脚本如下
---------------------安装软件环境包----------------------------------------------------
yum install ncurses ncurses-devel bison gcc gcc-c++ cmake -y
cd /opt ##将软件包拷贝到当前目录下
tar xzvf mysql-5.7.17.tar.gz -C /opt
tar xvzf boost_1_59_0.tar.gz -C /usr/local/
useradd -s /sbin/nologin mysql ##创建数据库管理用户
cd /usr/local/
mv boost_1_59_0/ boost ##改名方便使用
----------------------------手工编译安装配置----------------------------------------------
cd /opt/mysql-5.7.17/ ##和安装5.7.20的区别在于,5.7.20的boost是集成了一个包,cmake的路径和5.7.17有区别
cmake \
-DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
-DMYSQL_UNIX_ADDR=/usr/local/mysql/mysql.sock \
-DSYSCONFDIR=/etc \
-DSYSTEMD_PID_DIR=/usr/local/mysql \
-DDEFAULT_CHARSET=utf8 \
-DDEFAULT_COLLATION=utf8_general_ci \
-DWITH_INNOBASE_STORAGE_ENGINE=1 \
-DWITH_ARCHIVE_STORAGE_ENGINE=1 \
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
-DWITH_PERFSCHEMA_STORAGE_ENGINE=1 \
-DMYSQL_DATADIR=/usr/local/mysql/data \
-DWITH_BOOST=/usr/local/boost \
-DWITH_SYSTEMD=1
-------------------编译安装-------------------------------------
make
make install
-------------------进行mysql设置-------------------------------
chown -R mysql.mysql /usr/local/mysql/ ##将mysql的属主、属组设置为mysql方便管理
-------------------------配置mysql文件-------------------------------------
vim /etc/my.cnf
[client]
port = 3306
default-character-set=utf8
socket = /usr/local/mysql/mysql.sock
[mysql]
port = 3306
default-character-set=utf8
socket = /usr/local/mysql/mysql.sock
[mysqld]
user = mysql
basedir = /usr/local/mysql
datadir = /usr/local/mysql/data
port = 3306
character_set_server=utf8
pid-file = /usr/local/mysql/mysqld.pid
socket = /usr/local/mysql/mysql.sock
server-id = 1
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_AUTO_VALUE_ON_ZERO,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,PIPES_AS_CONCAT,ANSI_QUOTES
chown mysql.mysql /etc/my.cnf
--------------------------------声明环境变量--------------------------
echo 'PATH=/usr/local/mysql/bin/:/usr/local/mysql/lib:$PATH' >> /etc/profile
echo 'export PATH' >> /etc/profile
source /etc/profile
----------------------------初始化数据库---------------------------------
cd /usr/local/mysql/
bin/mysqld \
--initialize-insecure \
--user=mysql \
--basedir=/usr/local/mysql \
--datadir=/usr/local/mysql/data
cp /usr/local/mysql/usr/lib/systemd/system/mysqld.service /usr/lib/systemd/system ##将启动脚本放到systemd目录下被管理
systemctl daemon-reload ##重新加载
systemctl start mysqld
mysql数据库默认为3306端口
---------------------------管理数据库-------------------------------------------
mysqladmin -u root -p password "abc123" ##创建数据库管理用户并设置密码
Enter password: ##原密码为空
mysqladmin: [Warning] Using a password on the command line interface can be insecure.
Warning: Since password will be sent to server in plain text, use ssl connection to ensure password safety.
mysql -u root -p ##登录数据库
所有权限放给数据库中的所有表格,允许root账户从任何终端(@指定终端)登录,登录密码设置为abc123
grant all privileges on *.* to 'root'@'%' identified by 'abc123' with grant option;
show database; ##查看数据库中的内容
flush privileges;
bye或者quit退出数据库
修改配置文件
[root@localhost ~]# systemctl stop firewalld.service
[root@localhost ~]# setenforce 0
[root@localhost ~]# vim /etc/my.cnf
server-id = 10
log-bin=master-bin
log-slave-updates=true
[root@localhost mysql]# systemctl restart mysqld
为从服务器创建一个账户,便于从服务器从主服务器进行日志同步。
[root@localhost mysql]# mysql -uroot -pabc123 ##登录数据库
mysql> grant replication slave on *.* to 'myslave'@'14.0.0.%' identified by '123123'; ##创建账户,并放通权限
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> show master status; ##查看主服务器的状态。在这个状态下就尽量不要再进行操作,否则回导致位置点变化,发生错位
+-------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+-------------------+
| master-bin.000001 | 447 | | | |
+-------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
两台从服务器进行的操作都是一样的,只有server-id 不一样,所以以server-id =20 为例子,server-id =30 的从服务器就不演示了。
[root@localhost ~]# systemctl stop firewalld.service
[root@localhost ~]# setenforce 0
[root@localhost ~]# vim /etc/my.cnf
server-id = 20
relay-log=relay-log-bin ##从主服务器上同步日志文件记录到本地
relay-log-index=slave-relay-bin-index ##定义relay-log的位置和名称
[root@localhost ~]# systemctl restart mysqld
mysql> change master to master_host='14.0.0.7',master_user='myslave',master_password='123123',master_log_file='master-bin.000001',master_log_pos=447; ##在从服务器上设置到哪里去同步日志文件,使用的账户与密码,以及同步的位置点
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; ##查看状态
Slave_IO_Running: Yes ##这两个线程一定要处于开启状态
Slave_SQL_Running: Yes
在主服务器上创建一个新的数据库home
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.01 sec)
mysql> create database home;
Query OK, 1 row affected (0.01 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| home |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
读写分离就是只在主服务器上写,只在从服务器上读。基本的原理是让主数据库处理事务性查询,而从数据库处理 select 查询。数据库复制被用来把主数据库上事务性查询导致的变更同步到集群中的从数据库。
通过程序代码实现 MySQL 读写分离自然是一个不错的选择,但是并不是所有的应用都适合在程序代码中实现读写分离,像一些大型复杂的 Java 应用,如果在程序代码中实现读写分离对代码改动就较大。
Amoeba(变形虫)项目开源框架于 2008 年发布一款 AmoebaforMySQL 软件。这个软件致力于 MySQL 的分布式数据库前端代理层,它主要为应用层访问 MySQL 的时候充当 SQL 路由功能,并具有负载均衡、高可用性、SQL 过滤、读写分离、可路由到相关的目标 数据库、可并发请求多台数据库。通过 Amoeba 能够完成多数据源的高可用、负载均衡、 数据切片的功能。
实现MySQL的读写分离
基于上述的MySQL主从复制,再加一台虚拟机作为Amoeba服务器
Amoeba服务器:14.0.0.37
Amoeba 基于是 jdk1.5 开发的,所以官方推荐使用 jdk1.5 或 1.6 版本,高版本不建议使用。
[root@localhost ~]# cd /opt ##将软件包拷贝到/opt目录下
[root@localhost opt]# ls
amoeba-mysql-binary-2.2.0.tar.gz mysql-5.7.17
boost_1_59_0.tar.gz mysql-5.7.17.tar.gz
jdk-6u14-linux-x64.bin rh
[root@localhost opt]# cp jdk-6u14-linux-x64.bin /usr/local/ ##将jdk文件直接复制到/usr/local下
[root@localhost opt]# cd /usr/local/
[root@localhost local]# ls
bin etc include lib libexec sbin src
boost games jdk-6u14-linux-x64.bin lib64 mysql share
[root@localhost local]# chmod +x jdk-6u14-linux-x64.bin ##赋予执行权限
[root@localhost local]# ./jdk-6u14-linux-x64.bin ##执行安装
省略部分过程
Do you agree to the above license terms? [yes or no]
yes ##选择yes
省略部分过程
Press Enter to continue..... ##单击回车
Done.
配置jdk环境变量
[root@localhost local]# mv jdk1.6.0_14/ jdk1.6
[root@localhost local]# vim /etc/profile
[root@localhost local]# source /etc/profile ##加载环境变量
安装amoeba
[root@localhost local]# mkdir /usr/local/amoeba
[root@localhost local]# cd /opt
[root@localhost opt]# tar zxvf amoeba-mysql-binary-2.2.0.tar.gz -C /usr/local/amoeba
[root@localhost opt]# chmod -R 755 /usr/local/amoeba/ ##给amoeba提权
[root@localhost opt]# /usr/local/amoeba/bin/amoeba ##执行命令
amoeba start|stop ##说明amoeba可以正常使用了
mysql> grant all on *.* to test@'14.0.0.%' identified by '123123'; ##让三台mysql服务器通过test用户来访问amoeba服务器
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
[root@localhost opt]# cd /usr/local/amoeba/conf/
[root@localhost conf]# vim amoeba.xml
在配置文件的第30行设置客户端通过amoeba用户和123123密码访问amoeba服务器
<property name="user">amoeba</property>
<property name="password">123123</property>
然后到117行,开启读写功能池设定
<property name="defaultPool">master</property>
<property name="writePool">master</property>
<property name="readPool">slaves</property>
设置amoeba访问mysql数据库的用户和密码
<!-- mysql schema -->
<property name="schema">mysql</property>
<!-- mysql user -->
<property name="user">test</property>
<!-- mysql password -->
<property name="password">123123</property>
分别配置三个服务器对应的三个节点
<dbServer name="master" parent="abstractServer"> <factoryConfig> <!-- mysql ip --> <property name="ipAddress">14.0.0.7</property> </factoryConfig> </dbServer>
<dbServer name="slave1" parent="abstractServer">
<factoryConfig>
<!-- mysql ip -->
<property name="ipAddress">14.0.0.17</property>
</factoryConfig>
</dbServer>
<dbServer name="slave2" parent="abstractServer">
<factoryConfig>
<!-- mysql ip --> <property name="ipAddress">14.0.0.27</property>
</factoryConfig>
</dbServer>
在配置文件的末尾指除三个节点的对应关系
[root@localhost conf]# /usr/local/amoeba/bin/amoeba start &
[1] 30856
[root@localhost conf]# log4j:WARN log4j config load completed from file:/usr/local/amoeba/conf/log4j.xml
2020-08-29 00:47:26,851 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:47:30,014 INFO net.ServerableConnectionManager - Amoeba for Mysql listening on 0.0.0.0/0.0.0.0:8066.
2020-08-29 00:47:30,019 INFO net.ServerableConnectionManager - Amoeba Monitor Server listening on /127.0.0.1:20915.
测试机的客户机可以使用yum方式安装mysql
[root@localhost ~]# iptables -F
[root@localhost ~]# setenforce 0
[root@localhost ~]# yum install mysql -y
[root@localhost ~]# mysql -uamoeba -p123123 -h14.0.0.37 -P8066
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 1884603565
Server version: 5.1.45-mysql-amoeba-proxy-2.2.0 Source distribution
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| home |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
MySQL [(none)]> create database hello;
Query OK, 1 row affected (0.01 sec)
在主服务器上的home数据库中创建一张表info,并定义表结构
mysql> use home;
Database changed
mysql> create table info(id int(3),name char(10));
Query OK, 0 rows affected (0.02 sec)
mysql> show tables;
+----------------+
| Tables_in_home |
+----------------+
| info |
+----------------+
1 row in set (0.00 sec)
这时候这张表的结构应该会同步到两个从服务器上,这时候我们在从服务器上停止slave服务
mysql> stop slave;
Query OK, 0 rows affected (0.00 sec)
MySQL [home]> insert into info values(1,'zhangsan');
Query OK, 1 row affected (0.01 sec)
分别在主和从服务器上查看这张表是否被插入数据
主服务器(14.0.0.7)
mysql> insert into info values(1,'lisi');
Query OK, 1 row affected (0.00 sec)
mysql> select * from info;
+------+----------+
| id | name |
+------+----------+
| 1 | zhangsan |
| 1 | lisi |
+------+----------+
2 rows in set (0.00 sec)
从服务器slave1(14.0.0.17)
mysql> insert into info values(2,'lisi');
Query OK, 1 row affected (0.00 sec)
mysql> select * from info;
+------+------+
| id | name |
+------+------+
| 2 | lisi |
+------+------+
1 row in set (0.00 sec)
从服务器slave2(14.0.0.27)
mysql> insert into info values(3,'lisi');
Query OK, 1 row affected (0.00 sec)
mysql> select * from info;
+------+------+
| id | name |
+------+------+
| 3 | lisi |
+------+------+
1 row in set (0.00 sec)
最后在客户机14.0.0.47上查看,发现只能查看到从服务器上的内容,采用轮询的方式,看不到主服务器上的内容,所以读写分离验证成功。
MySQL [home]> select * from info;
+------+------+
| id | name |
+------+------+
| 3 | lisi |
+------+------+
1 row in set (0.00 sec)
MySQL [home]> select * from info;
+------+------+
| id | name |
+------+------+
| 2 | lisi |
+------+------+
1 row in set (0.01 sec)
MySQL [home]> select * from info;
+------+------+
| id | name |
+------+------+
| 3 | lisi |
+------+------+
1 row in set (0.00 sec)
MySQL [home]> select * from info;
+------+------+
| id | name |
+------+------+
| 2 | lisi |
+------+------+
1 row in set (0.01 sec)