MySQL读写分离

1、MySQL读写分离概述

当今MySQL使用相当广泛,随着用户的增多以及数据量的增大,高并发随之而来;然而我们有很多办法可以缓解数据库的压力,分布式数据库集群、负载均衡、读写分离、增加缓存服务器等等。采用读写分离技术缓解数据库的压力是一种不错的方式。

1.1 MySQL读写分离的好处

  • 物理服务器增加,机器处理能力提升,拿硬件换性能。
  • 主从只负责各自的读和写,极大程度缓解X锁和S锁争用。
  • slave可以配置myiasm引擎,提升查询性能以及节约系统开销。
  • slave可以单独设置一些参数来提升其读的性能。
  • master的写是并发,slave通过主库发送来的binlog同步数据。
  • 增加冗余,提高可用性。

1.2 读写分离实现方式

  • 代码封装
    在代码层面抽出一个中间层,由中间层来实现读写分离和数据库连接。通过代理类代码,对外暴露正常的读写接口,里面封装了逻辑,将读操作指向从库的数据源,写操作指向主库的数据源。

    • 优点:简单(开发写代码实现),并且可以根据业务定制化变化,随心所欲。
    • 缺点:如果数据库宕机了,发生主从切换了之后,就得修改配置重启。如果系统是多语言的话,需要为每个语言都实现一个中间层代码,重复开发。
  • 中间件
    一般而言是独立部署的系统,客户端与这个中间件的交互是通过 SQL 协议的。所以在客户端看来连接的就是一个数据库,通过SQL协议交互也可以屏蔽多语言的差异。

    • 缺点:就是整体架构多了一个系统需要维护,并且可能成为性能瓶颈,毕竟交互都需要经过它中转。

常见的开源数据库中间件有:官方的MySQL-Proxy、360的Atlas、Mycat、ProxySQL等。

1.3 MySQL读写分离原理

读写分离的基本原理是让主数据库处理事务性的增、删、改操作(INSERT、DELETE、UPDATE),而从数据库处理SELECT查询操作。MySQL主从复制将事务性的增、删、改操作同步到集群中的从数据库。

MySQL读写分离架构图
MySQL读写分离.jpg

在应用和数据库之间增加代理层(中间件),代理层接收应用对数据库的请求,根据不同请求类型转发到不同的实例,在实现读写分离的同时可以实现负载均衡。

2、MySQLProxy实现读写分离

2.1 MySQLProxy简介

MySQL Proxy是MySQL官方提供的数据库代理工具;MySQL Proxy实际上是在客户端请求与MySQLServer之间建立了一个连接池,所有客户端请求都是发向MySQL Proxy,然后经由MySQL Proxy进行相应的分析,判断出是读操作还是写操作,分发至对应的MySQLServer上;对于多节点Slave集群,可以实现负载均衡的效果。

2.2 MySQLProxy读写分离配置

实验拓扑图
mysqlproxy.jpg

实验环境:

主机IP地址
Master192.168.52.128
Slave192.168.52.129
Proxy192.168.52.130
  • 配置MySQL主从复制
//主服务器和从服务器安装并初始化mariadb
[root@master ~]# dnf -y install mariadb mariadb-server
[root@master ~]# systemctl enable mariadb
[root@master ~]# systemctl start mariadb
[root@master ~]# mysql_secure_installation 

[root@slave ~]# dnf -y install mariadb mariadb-server
[root@slave ~]# systemctl enable mariadb
[root@slave ~]# systemctl start mariadb
[root@slave ~]# mysql_secure_installation 

//主服务器关闭防火墙和selinux
[root@master ~]# systemctl stop firewalld.service 
[root@master ~]# systemctl disable firewalld.service 
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.

[root@master ~]# sed -i s/SELINUX=enforing/SELINUX=disabled/g /etc/selinux/config 
[root@master ~]# setenforce 0

//从服务器关闭防火墙和selinux
[root@slave ~]# systemctl stop firewalld.service 
[root@slave ~]# systemctl disable firewalld.service 
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.

[root@slave ~]# sed -i s/SELINUX=enforing/SELINUX=disabled/g /etc/selinux/config 
[root@slave ~]# setenforce 0

//在主服务器上配置主从复制,开启二进制日志,设置服务id
[root@master ~]# vim /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
log_bin=mysql-bin			//开启二进制日志
server_id=1				//服务id号,不可重复,为0则拒绝从服务器连接
innodb_flush_log_at_trx_commit=1	//每个事务提交时立即写入binlog
sync_binlog=1				//事务提交后立刻刷新binlog

[root@master ~]# systemctl restart mariadb.service 

//在主服务器授权一个数据库用户用于复制,创建完成后在从服务器测试登录
MariaDB [(none)]> GRANT REPLICATION SLAVE ON *.* TO 'zcfz'@'%' IDENTIFIED BY 'hzz123!';
Query OK, 0 rows affected (0.001 sec)

MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.001 sec)

//在从服务器配置主从复制,设置服务id
[root@slave ~]# vim /etc/my.cnf.d/mariadb-server.cnf
[mysqld]
server_id=2			//服务id号,不可重复,为0则拒绝从服务器连接
read_only=1			//设置从服务器普通用户只读,不会影响主从复制

[root@slave ~]# systemctl restart mariadb.service 

//登录到从服务器数据库,在数据库中配置主服务器信息
MariaDB [(none)]> CHANGE MASTER TO MASTER_HOST='192.168.52.128',MASTER_PORT=3306,MASTER_USER='zcfz',MASTER_PASSWORD='hzz123!';
Query OK, 0 rows affected (0.002 sec)

MariaDB [(none)]> START SLAVE;
Query OK, 0 rows affected (0.001 sec)

//查看从服务器主从复制是否开启
MariaDB [(none)]> SHOW SLAVE STATUS \G 
*************************** 1. row ***************************
                Slave_IO_State: Waiting for master to send event
                   Master_Host: 192.168.52.128
                   Master_User: zcfz
                   Master_Port: 3306
                 Connect_Retry: 60
               Master_Log_File: mysql-bin.000002
           Read_Master_Log_Pos: 638
                Relay_Log_File: mariadb-relay-bin.000003
                 Relay_Log_Pos: 937
         Relay_Master_Log_File: mysql-bin.000002
              Slave_IO_Running: Yes
             Slave_SQL_Running: Yes
.....省略.....
  • MySQL Proxy实现读写分离
//proxy节点关闭防火墙和selinux
[root@proxy ~]# systemctl stop firewalld.service 
[root@proxy ~]# systemctl disable firewalld.service 
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.

[root@proxy ~]# sed -i s/SELINUX=enforing/SELINUX=disabled/g /etc/selinux/config 
[root@proxy ~]# setenforce 0

//在proxy节点下载MySQL-Proxy工具
[root@proxy ~]# wget https://cdn.mysql.com/archives/mysql-proxy/mysql-proxy-0.8.5-linux-el6-x86-64bit.tar.gz

//解压MySQL-Proxy工具
[root@proxy ~]# tar -xvf mysql-proxy-0.8.5-linux-el6-x86-64bit.tar.gz
[root@proxy ~]# mv mysql-proxy-0.8.5-linux-el6-x86-64bit /usr/local/mysql-proxy

//设置MySQL-Proxy工具环境变量
[root@proxy ~]# echo 'export PATH=$PATH:/usr/local/mysql-proxy/bin' >> ~/.bash_profile 
[root@proxy ~]# source ~/.bash_profile 

//配置MySQL-Proxy配置文件设置读写分离
[root@proxy ~]# mkdir /usr/local/mysql-proxy/conf
[root@proxy ~]# mkdir /usr/local/mysql-proxy/logs

[root@proxy ~]# vim /usr/local/mysql-proxy/conf/mysql-proxy.conf
[mysql-proxy]
plugins=proxy						//proxy插件
user=root 						//以root身份运行mysql-proxy
proxy-address=192.168.52.130:3306			//监听本机中IP地址和端口,默认端口4040。
proxy-backend-addresses=192.168.52.128:3306		//进行写的数据库master
proxy-read-only-backend-addresses=192.168.52.129:3306	//进行读的数据库slave
proxy-lua-script=/usr/local/mysql-proxy/share/doc/mysql-proxy/rw-splitting.lua	//指定读写分离脚本文件
pid-file=/usr/local/mysql-proxy/logs/mysql-proxy.pid	//pid文件位置
log-file=/usr/local/mysql-proxy/logs/mysql-proxy.log    //生成日志文件
log-level=debug						//日志等级为debug
keepalive=true     					//心跳检测,崩溃时,尝试重启
daemon=true        					//守护进程
:wq!

//修改配置文件权限,防止信息泄露,如果不修改启动报错
[root@proxy ~]# chmod 660 /usr/local/mysql-proxy/conf/mysql-proxy.conf 

//设置读写分离连接数量
[root@proxy ~]# vim /usr/local/mysql-proxy/share/doc/mysql-proxy/rw-splitting.lua
if not proxy.global.config.rwsplit then
        proxy.global.config.rwsplit = {
                min_idle_connections = 1,	//默认超过4个连接数时,才开始读写分离,改为1
                max_idle_connections = 8,

                is_debug = false
        }
end

//开启MySQL-Porxy实现读写分离
[root@proxy ~]# mysql-proxy --defaults-file=/usr/local/mysql-proxy/conf/mysql-proxy.conf

//在主服务器和从服务器上授权一个用户进行登录测试,主服务器和从服务器用户需一致
//只需在主服务器授权一个用户,主从复制会将此用户同步至从服务器
MariaDB [(none)]> GRANT ALL ON *.* TO 'hzz'@'%' IDENTIFIED BY 'hzz123!';
Query OK, 0 rows affected (0.001 sec)

MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.001 sec)

//测试,在测试端上登录到proxy代理
[root@test ~]# mysql -uhzz -phzz123! -h192.168.52.130

//登录到proxy代理后,创建一个数据库和表
MariaDB [(none)]> create database hzz;
Query OK, 1 row affected (0.001 sec)

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| hzz                |
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.001 sec)

MariaDB [(none)]> create table hzz.hzztable(id int(7));
Query OK, 0 rows affected (0.004 sec)

//然后在从服务器上停止主从复制
MariaDB [(none)]> stop slave;
Query OK, 0 rows affected (0.001 sec)

//回到测试端,登录到proxy代理,插入一条数据,发现select查看不到,说明读写分离成功
MariaDB [(none)]> insert into hzz.hzztable(id)value(1);
Query OK, 1 row affected (0.002 sec)

MariaDB [(none)]> select * from hzz.hzztable;
0 row in set (0.001 sec)

3、ProxySQL实现读写分离

3.1 ProxySQL简介

ProxySQL是灵活强大的MySQL代理层, 是一个能实实在在用在生产环境的MySQL中间件,可以实现读写分离,支持Query路由功能,支持动态指定某个SQL进行 cache,支持动态加载配置、故障切换和一些SQL的过滤功能。还有一些同类产品比如DBproxy、MyCAT、OneProxy等。但经过反复对比和测试之后,还是觉得ProxySQL是一款性能不谙,靠谱稳定的MySQL中间件产品!

官方站点:https://proxysql.com
官方github:https://github.com/sysown/proxysql
官方手册:https://proxysql.com/documentation

3.2 ProxySQL架构

ProxySQL架构图
proxy SQL.png

ProxySQL启动后,将监听两个端口:

  • admin管理端口,默认端口为6032。该端口用于查看、配置ProxySQL
  • 服务端口,默认端口为6033,这个接口类似于MySQL的3306端口

ProxySQL多层管理配置设计 (有三层配置):

  • runtime:运行中正在使用的配置
  • memory:提供用户动态修改配置
  • disk:持久层,将修改的配置保存到磁盘SQLite表中(即:proxysql.db)
  • config:配置文件(即:proxysql.cnf),和disk一样处于持久层,一般不使用它,仅仅在第一次开启ProxySQL时加载

ProxySQL配置系统分为三层的目的:

  • 自动更新
  • 尽可能的不重启proxysql就可以修改配置
  • 错误配置时方便回滚

注意事项:

  • ProxySQL每一个配置项在三层中都存在,但是这三层是互相独立的,也就是说proxysql可以同时拥有三份配置,每层都是独立的,可能三份配置都不一样,也可能三份都一样
  • RUNTIME层代表ProxySQL当前生效的正在使用的配置,无法直接修改这里的配置,必须要从下一层"load"进来
  • MEMORY这一层上面连接RUNTIME层,下面连接持久化层,在这层可以正常操作 ProxySQL配置,随便修改,不会影响生产环境。修改一个配置一般都是先在 MEMORY层完成,然后确认正常之后再加载到RUNTIME和持久化到磁盘上
  • DISK和CONFIG FILE层持久化配置信息,重启后内存中的配置信息会丢失,所以需要将配置信息保留在磁盘中,重启时可以从磁盘快速加载回来。

3.3 ProxySQL配置文件介绍

//第一次启动前修改CONFIG FILE配置文件
[root@proxysql ~]# vim /etc/proxysql.cnf
datadir="/var/lib/proxysql"			#数据目录
errorlog="/var/lib/proxysql/proxysql.log"

admin_variables=
{
        admin_credentials="admin:admin"		#管理员用户名与密码
        mysql_ifaces="0.0.0.0:6032"		#管理端口,用来连接proxysql的管理数据库
}

mysql_variables=
{
        threads=4				#指定转发端口开启的线程数量
        max_connections=2048
        default_query_delay=0
        default_query_timeout=36000000
        have_compress=true
        poll_timeout=2000
        interfaces="0.0.0.0:6033"		#指定代理端口
        default_schema="information_schema"
        stacksize=1048576
        server_version="5.5.30"			#指定后端mysql的版本
        connect_timeout_server=3000
        monitor_username="monitor"
        monitor_password="monitor"
        monitor_history=600000
        monitor_connect_interval=60000
        monitor_ping_interval=10000
        monitor_read_only_interval=1500
        monitor_read_only_timeout=500
        ping_interval_server_msec=120000
        ping_timeout_server=500
        commands_stats=true
        sessions_sort=true
        connect_retries_on_failure=10
}

mysql_servers =
(
)

mysql_users:
(
)

mysql_query_rules:
(
)

scheduler=
(
)

mysql_replication_hostgroups=
(
)

[root@proxysql ~]# systemctl start proxysql

3.4 ProxySQL在线配置数据库介绍

//通过mysql客户端登录到ProxySQL管理端口
[root@proxysql ~]# mysql -uadmin -padmin -h127.0.0.1 -P6032

MySQL [(none)]> show databases;
+-----+---------------+-------------------------------------+
| seq | name          | file                                |
+-----+---------------+-------------------------------------+
| 0   | main          |                                     |
| 2   | disk          | /var/lib/proxysql/proxysql.db       |
| 3   | stats         |                                     |
| 4   | monitor       |                                     |
| 5   | stats_history | /var/lib/proxysql/proxysql_stats.db |
+-----+---------------+-------------------------------------+
5 rows in set (0.00 sec)
库名 作用
mainmemory层的内存配置数据库,表里存放后端db实例、用户验证、路由规则等信息。表名以runtime_开头的表示proxysql当前运行的配置内容,不可修改,只能修改对应的非runtime_ 开头的(在内存)里的表,然后LOAD使其生效,SAVE使其存到硬盘以供下次重启加载。
disk持久化到硬盘的sqlite数据文件。在重新启动时,“main” 不会持久存在,需要从“磁盘”数据库或配置文件中加载。
stats包含proxysql运行抓取的统计信息,包括到后端各命令的执行次数、流量、processlist、查询种类汇总、执行时间等等。
monitor存储monitor模块收集的信息,主要是对后端db的健康/延迟检查。

3.5 main数据库常用配置表介绍

//main内存配置数据库中常用配置表
MySQL [(none)]> show tables;
+----------------------------------------------------+
| tables                                             |
+----------------------------------------------------+
| global_variables                                   |
| mysql_collations                                   |
| mysql_query_rules                                  |
| mysql_replication_hostgroups                       |
| mysql_servers                                      |
| mysql_users                                        |
+----------------------------------------------------+
表名 作用
global_variables该表定义管理变量和MySQL变量;管理变量,控制管理界面的行为,他们的名字以标记“admin-”开头;mysql变量,控制代理的MySQL功能,他们的名字以标记“mysql-”开头。
global_variables官网参考文档:https://proxysql.com/documentation/global-variables
mysql_collations相关字符集和校验规则。
mysql_query_rules定义查询路由规则。
mysql_replication_hostgroups监视指定主机组中所有服务器的read_only值,并且根据read_only的值将服务器分配给写入器或读取器主机组。ProxySQL monitor模块会监控hostgroups后端所有servers的read_only 变量,如果发现从库的read_only变为0、主库变为1,则认为角色互换了,自动改写mysql_servers表里面 hostgroup关系,达到自动Failover效果。
mysql_servers包含要连接的ProxySQL的后端服务器列表。
mysql_users包含配置后端数据库的程序账号和监控账号。

3.6 stats数据库常用配置表介绍

//stats库中常用的表
MySQL [(none)]> show tables from stats;
+--------------------------------------+
| tables                               |
+--------------------------------------+
| stats_mysql_commands_counters        |
| stats_mysql_connection_pool          |
| stats_mysql_processlist              |
| stats_mysql_query_digest             |
| stats_mysql_query_rules              |
+--------------------------------------+
表名作用
stats_mysql_commands_counters统计各种SQL类型的执行次数和时间,通过参数mysql-commands_stats控制开关,默认是ture。
stats_mysql_connection_pool连接后端MySQL的连接信息。
stats_mysql_processlist类似MySQL的show processlist的命令,查看各线程的状态。
stats_mysql_query_digest表示SQL的执行次数、时间消耗等。通过变量mysql-query_digests控制开关,默认是开。
stats_mysql_query_rules路由命中次数统计。

3.7 monitor数据库常用配置表介绍

//monitor库中常用的表        
MySQL [(none)]> show tables from monitor;
+--------------------------------------+
| tables                               |
+--------------------------------------+
| mysql_server_connect_log             |
| mysql_server_ping_log                |
| mysql_server_replication_lag_log     |
+--------------------------------------+
表名作用
mysql_server_connect_log连接到所有MySQL服务器以检查它们是否可用,该表用来存放检测连接的日志。
mysql_server_ping_log使用mysql_ping API ping后端MySQL服务器,检查它们是否可用,该表用来存放ping的日志。
mysql_server_replication_lag_log后端MySQL服务主从延迟的检测。

3.8 ProxySQL配置流程

ProxySQL安装部署官方文档:https://proxysql.com/documentation/installing-proxysql/

ProxySQL基础配置官方文档:https://proxysql.com/documentation/ProxySQL-Configuration

ProxySQL读写分离官方文档:https://proxysql.com/documentation/proxysql-read-write-split-howto

ProxySQL分片官方文档:https://proxysql.com/documentation/how-to-setup-proxysql-sharding/