Skip to content

数据库集群读写分离

数据库服务读写分离概述介绍

在应用数据库主从架构或者数据库冗余架构时,都可以有效保证数据库逻辑故障或物理故障对业务的影响;

但是,这两种架构进行应用时,业务访问过程只是访问主数据库节点,进行读写操作,当并发量大时无型中会对主节点造成不小压力;

因此,可以设计一种新型的业务访问架构方式,可以实现将写数据请求发送到主节点,将读数据请求发送到从节点;

最终,可以有效减少主节点的业务访问压力,这样设计的数据库架构称之为读写分离架构;

数据库服务读写分离架构搭建

步骤一:读写分离架构部署环境规划

为了实现读写分离架构构建,需要准备好三节点数据库+GTID复制环境+MHA环境(普通主从环境也可以构建);

主机角色主机名称地址信息
主库服务器192.168.30.1013306
从库服务器192.168.30.1023306
从库服务器192.168.30.103(兼做管理节点)3306

步骤二:读写分离架构软件下载安装

通过官方网站或者github可以下载proxySQL软件程序,并上传到数据库服务器中进行安装;

shell
# 上传安装软件程序
[root@xiaoQ-03 ~]# rpm -ivh proxysql-2.4.6-1-centos7.x86_64.rpm
警告:proxysql-2.4.6-1-centos7.x86_64.rpm: V4 RSA/SHA512 Signature, 密钥 ID 8217c97e: NOKEY
准备中...                          ################################# [100%]

# 启动运行软件程序
[root@xiaoQ-03 ~]# systemctl start proxysql
[root@xiaoQ-03 ~]# netstat -lntup
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0            0         0.0.0.0:6032                  0.0.0.0:*               LISTEN      83020/proxysql
tcp        0            0         0.0.0.0:6033                  0.0.0.0:*               LISTEN      83020/proxysql
-- 启动生成的6032端口为管理端口,用于配置数据库中间件的功能信息连接此端口
-- 启动生成的6033端口为访问端口,用于提供对外的业务访问此端口

步骤三:读写分离架构软件管理配置

在连接进入6032端口之后,表示进行proxysql的管理终端环境,终端环境中会加载五个重要的功能库:

序号库信息配置信息解释说明
01mainmysql_servers表示后端可以连接mysql服务器的列表
mysql_users表示配置后端数据库的连接账号和监控账号
mysql_query_rules表示指定query路由到后端不同服务器的规则列表
mysql_replication_hostgroups表示节点分组配置信息,可以配置多个写或读节点到一个组中
02disk表示持久化的磁盘配置信息
03stats表示统计信息的汇总
04monitor表示监控收集的信息,比如数据库的监控状态等
05stats_history表示收集的有关软件内部功能的历史指标

说明:一般服务是通过配置文件保存功能配置信息,proxySQL是通过数据库中的表进行配置信息的存储设置;

shell
# 连接进入到proxySQL管理终端
[root@xiaoQ-03 ~]# mysql -uadmin -padmin -h127.0.0.1 -P6032
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.5.30 (ProxySQL Admin Module)

# 查看proxySQL终端数据库信息
>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)
-- 不需要use到相应数据库中进行操作,可以操作的数据表信息如下

# 查看proxySQL终端数据表信息
>show tables ;
+-----------------------------------------------------------------+
| tables                                          
+-----------------------------------------------------------------+
| global_variables                                
| mysql_aws_aurora_hostgroups                      
| mysql_collations                                 
| mysql_firewall_whitelist_rules                   
| mysql_firewall_whitelist_sqli_fingerprints       
| mysql_firewall_whitelist_users                   
| mysql_galera_hostgroups                            
| mysql_group_replication_hostgroups              
| mysql_query_rules                                
| mysql_query_rules_fast_routing                  
| mysql_replication_hostgroups                     
| mysql_servers                                  
| mysql_users                                      
| proxysql_servers                                 
| restapi_routes                                    
| runtime_checksums_values                         
| runtime_global_variables                         
| runtime_mysql_aws_aurora_hostgroups               
| runtime_mysql_firewall_whitelist_rules           
| runtime_mysql_firewall_whitelist_sqli_fingerprints
| runtime_mysql_firewall_whitelist_users            
| runtime_mysql_galera_hostgroups                   
| runtime_mysql_group_replication_hostgroups        
| runtime_mysql_query_rules                          
| runtime_mysql_query_rules_fast_routing            
| runtime_mysql_replication_hostgroups              
| runtime_mysql_servers                             
| runtime_mysql_users                             
| runtime_proxysql_servers                          
| runtime_restapi_routes                           
| runtime_scheduler                                 
| scheduler                                         
+-----------------------------------------------------------------+
32 rows in set (0.00 sec)
-- 表名以runtiem_开头的表示proxySQL服务中当前运行的配置内容,不能直接修改,不带runtime是下文图中mem相关的配置

ProxySQL管理接口的多层配置关系:

ProxySQL整套配置系统分为三层:

第一层:RUNTIME:

代表proxySQL当前正在使用的配置,无法直接修改此配置,必须要从下一层(MEM层)load加载进来;

第二层:MEMORY(主要修改的配置表)

memory层上面连接runtime层,下面连接disk持久化存储层;

在这层可以在线操作ProxySQL配置,随意进行修改,不会影响生产环境,确认正常之后再加载到runtime和持久化保存到磁盘上

具体修改操作方法为:insert、update、delete、select;

第三层:DISK/CFG FILE

持久化配置信息,重启时可以从磁盘快速加载回来;

ProxySQL基于SQL语句进行读写分离实践配置:

①. 在mysql_replication_hostgroup表中,配置读写组编号:

proxySQL会根据server的read only的取值将服务器进行分组:

  • read_only=0的server,即master会被分到编号为10的写组;
  • read_only=1的server,即slave会被分到编号为20的读组;(所以需要将从库设置:set global read_only=1
SQL
db03 [(none)]>insert into mysql_replication_hostgroups(writer_hostgroup,reader_hostgroup,comment) values(10,20,'proxy');
Query OK, 1 row affected (0.00 sec)

db03 [(none)]>save mysql servers to disk;
Query OK, 0 rows affected (0.01 sec)

db03 [(none)]>load mysql servers to runtime;
Query OK, 0 rows affected (0.00 sec)

db03 [(none)]>select * from mysql_replication_hostgroups\G
*************************** 1. row ***************************
writer_hostgroup: 10
reader_hostgroup: 20
check_type: read_only
comment: proxy
1 row in set (0.00 sec)

② 添加主机到ProxySQL

sql
db03 [(none)]>insert into mysql_servers(hostgroup_id,hostname,port) values (10,'192.168.30.110',3306);
Query OK, 1 row affected (0.00 sec)

db03 [(none)]>insert into mysql_servers(hostgroup_id,hostname,port) values (20,'192.168.30.102',3306);
Query OK, 1 row affected (0.00 sec)

db03 [(none)]>insert into mysql_servers(hostgroup_id,hostname,port) values (20,'192.168.30.103',3306);
Query OK, 1 row affected (0.00 sec)

db03 [(none)]>save mysql servers to disk;
Query OK, 0 rows affected (0.01 sec)

db03 [(none)]>load mysql servers to runtime;
Query OK, 0 rows affected (0.00 sec)

db03 [(none)]>select * from mysql_servers\G;
*************************** 1. row ***************************
       hostgroup_id: 20
           hostname: 192.168.30.102
               port: 3306
          gtid_port: 0
             status: ONLINE
             weight: 1
        compression: 0
    max_connections: 1000
max_replication_lag: 0
            use_ssl: 0
     max_latency_ms: 0
            comment:
*************************** 2. row ***************************
       hostgroup_id: 20
           hostname: 192.168.30.103
               port: 3306
          gtid_port: 0
             status: ONLINE
             weight: 1
        compression: 0
    max_connections: 1000
max_replication_lag: 0
            use_ssl: 0
     max_latency_ms: 0
            comment:
*************************** 3. row ***************************
       hostgroup_id: 10
           hostname: 192.168.30.110
               port: 3306
          gtid_port: 0
             status: ONLINE
             weight: 1
        compression: 0
    max_connections: 1000
max_replication_lag: 0
            use_ssl: 0
     max_latency_ms: 0
            comment:
3 rows in set (0.00 sec)

③ 创建监控用户,并开启监控

利用监控用户对后端节点的运行情况进行监控数据同步,一旦后端节点出现数据同步异常,就不要再向故障节点发送相应业务请求;

shell
# 主库创建监控用户
db01 [(none)]>create user monitor@'%' identified with mysql_native_password by '123';
Query OK, 0 rows affected (0.02 sec)
db01 [(none)]>grant replication client on *.* to monitor@'%';
Query OK, 0 rows affected (0.01 sec)

# 在proxysql中修改variables表配置信息
db03 [(none)]>set mysql-monitor_username='monitor';
Query OK, 1 row affected (0.00 sec)

db03 [(none)]>set mysql-monitor_password='123';
Query OK, 1 row affected (0.00 sec)
-- 以上变量信息修改为方法一

db03 [(none)]>update global_variables set variable_value='monitor' where variable_name='mysql-monitor_username';
Query OK, 1 row affected (0.00 sec)

db03 [(none)]>update global_variables set variable_value='123' where variable_name='mysql-monitor_password';
Query OK, 1 row affected (0.00 sec)
-- 以上变量信息修改为方法二

db03 [(none)]>load mysql variables to runtime;
Query OK, 0 rows affected (0.01 sec)

db03 [(none)]>save mysql variables to disk;
Query OK, 154 rows affected (0.00 sec)

# 检查核实配置信息
db03 [(none)]>select @@mysql-monitor_username\G
*************************** 1. row ***************************
@@mysql-monitor_username: monitor
1 row in set (0.00 sec)

db03 [(none)]>select @@mysql-monitor_password\G
*************************** 1. row ***************************
@@mysql-monitor_password: 123
1 row in set (0.00 sec)

# 查询监控日志信息
db03 [(none)]>select * from mysql_server_connect_log;
+-------------------+------+-------------------------+---------------------------------+----------------------------------------+
| hostname          | port | time_start_us          | connect_success_time_us | connect_error                             |
+-------------------+------+-------------------------+---------------------------------+----------------------------------------+
| 192.168.30.110 | 3306 | 1674026545375939 | 2831                                     | NULL                                             |
| 192.168.30.103 | 3306 | 1674026546137911 | 1480                                     | NULL                                             |
| 192.168.30.102 | 3306 | 1674026546899730 | 3781                                     | NULL                                             |
-- 检查确认所有节点的连接访问情况

db03 [(none)]>select * from mysql_server_ping_log;
+-------------------+------+-------------------------+-----------------------------+---------------+
| hostname          | port | time_start_us          | ping_success_time_us | ping_error |
+-------------------+------+-------------------------+-----------------------------+---------------+
| 192.168.30.102 | 3306 | 1674026696004217 | 1139                               | NULL           |
| 192.168.30.103 | 3306 | 1674026696095455 | 194                                 | NULL           |
| 192.168.30.110 | 3306 | 1674026696186794 | 1466                               | NULL           |
-- 检查确认所有节点的网络连通情况

db03 [(none)]>select * from mysql_server_read_only_log limit 3;
+-------------------+------+-------------------------+----------------------+-------------+--------+
| hostname          | port | time_start_us          | success_time_us | read_only | error  |
+-------------------+------+-------------------------+----------------------+-------------+--------+
| 192.168.30.110 | 3306 | 1674027579464285 | 1325                      | 0                | NULL  |
| 192.168.30.102 | 3306 | 1674027579479777 | 1743                      | 1                | NULL  |
| 192.168.30.103 | 3306 | 1674027579494993 | 308                        | 1                | NULL  |
-- 检查确认所有节点的只读状态信息(获取主库或从库主机信息)

db03 [(none)]>select * from mysql_server_replication_lag_log;
Empty set (0.00 sec)
-- 检查确认所有节点的主从延时情况

④ 创建应用用户信息

创建数据库应用用户信息,利用应用用户,可以使proxySQL进行数据库节点的操作管理;

shell
# 主库创建应用用户
db01 [(none)]>create user root@'%' identified with mysql_native_password by '123';
Query OK, 0 rows affected (0.00 sec)

db01 [(none)]>grant all on *.* to root@'%';
Query OK, 0 rows affected (0.00 sec)

# 在proxysql中添加数据库节点的管理用户信息
db03 [(none)]>insert into mysql_users(username,password,default_hostgroup) values('root','123',10);
Query OK, 1 row affected (0.00 sec)

db03 [(none)]>load mysql users to runtime;
Query OK, 0 rows affected (0.00 sec)

db03 [(none)]>save mysql users to disk;
Query OK, 0 rows affected (0.00 sec)

# 早期版本,需要开启事务的持续化(忽略)
update mysql_users set transaction_persistent=1 where username='root';
load mysql users to runtime;
save mysql users to disk;
-- 事务路由分配持续性,同一个事务的语句不会被分配到不同的组

实用的读写规则配置

sql
> insert into mysql_query_rules(rule_id,active,match_pattern,destination_hostgroup,apply) values (1,1,'^select.*for update$',10,1);
> insert into mysql_query_rules(rule_id,active,match_pattern,destination_hostgroup,apply) values (2,1,'^select',20,1);
-- 其余数据库操作语句信息,默认路由放置到主节点进行执行

> load mysql query rules to runtime;
> save mysql query rules to disk;

select ... for update规则的rule_id必须要小于普通的select规则的rule_id,proxySQL是根据rule_id的顺序进行规则匹配的;

⑥ 测试读写分离效果

shell
[root@xiaoQ-03 ~]# mysql -uroot -p123 -P6033 -h127.0.0.1 -e "begin;select @@server_id;commit"
+-----------------+
| @@server_id |
+-----------------+
|                   51 |
+-----------------+
-- 非查询操作走的是主节点

[root@xiaoQ-03 ~]# mysql -uroot -p123 -P6033 -h127.0.0.1 -e "select @@server_id;"
+-----------------+
| @@server_id |
+-----------------+
|                   52 |
+-----------------+
[root@xiaoQ-03 ~]# mysql -uroot -p123 -P6033 -h127.0.0.1 -e "select @@server_id;"
+-----------------+
| @@server_id |
+-----------------+
|                    53 |
+-----------------+
-- 查询操作走的是从节点

>select * from stats_mysql_query_digest\G
-- 这个表对于分析SQL语句至关重要,是分析语句性能、定制路由规则指标的最主要来源

读写分离配置过程总结:

步骤操作说明涉及数据表信息涉及操作信息
01设置从库只读模式read_only=1
02添加主机组信息mysql_replication_hostgroups
03添加主机组节点信息mysql_servers
04添加用户信息(监控用户 应用用户)global_variables
mysql_users
05添加读写分离规则mysql_query_rules

感谢阅读,欢迎交流!