Java面试题033:一文深入了解MySQL(5)

Java面试题029:一文深入了解MySQL(1)

Java面试题030:一文深入了解MySQL(2)

Java面试题031:一文深入了解MySQL(3)

Java面试题032:一文深入了解MySQL(4)

MySQL读写分离和分库分表分别从不同角度提高系统的处理效率,读写分离侧重于处理高并发的场景,分库分表是从数据量的角度去提升单次读写的效率。

1、MySQL读写分离

当面临大量的读操作,造成写操作的效率降低时,将数据库的读和写操作分开,分别由不同的数据库服务器处理,可以有效地分担数据库的压力,优化系统性能。

读写分离是数据库架构中的一种常见策略,主要用于解决以下几种情况:

  • 当数据库的并发读写请求非常高时,单一的数据库服务器可能无法承受这样的压力,这时就需要通过读写分离来分散压力。读请求可以分发到多个从库,写请求则由主库处理。

  • 资源优化,读写分离可以使得主库专注于处理写操作和事务性的操作,从库则处理读操作。这样可以根据主从库的不同特性进行硬件和系统资源的优化配置。

  • 数据备份和故障转移,这是一个间接作用。通过读写分离,可以实现数据的实时备份。当主库出现故障时,可以迅速切换到从库,保证服务的持续可用。

  • 提高查询性能,读写分离可以将复杂的查询操作分散到多个从库上执行,从而提高查询性能。

(1)实现模式

  • 主从模式(Master-Slave):一个主库处理所有写操作,并将数据变更同步到一个或多个从库,从库处理读请求。

​ 优点:简单易实现,适用于大多数场景。

​缺点:主库可能成为瓶颈,从库延迟可能影响数据一致性。

  • 主主模式(Master-Master):多个主库同时处理读写请求,每个主库都可以接收写操作,并将变更同步到其他主库。

​ 优点:高可用性和高写入吞吐量。

​ 缺点:数据冲突和一致性问题更加复杂,配置和管理难度大。

  • 多级复制(Multi-Tier Replication):在主从架构基础上,增加中间层从库,形成多级复制结构。例如,主库 -> 中间从库 -> 最终从库。

​优点:分担主库和中间从库的压力,提高读取性能。

​ 缺点:配置和维护复杂,延迟可能累积。

(2)实现方式

  • 应用层实现(程序代码实现):在应用程序代码中手动管理数据库连接,将写请求发送到主库,读请求发送到从库。

  • ProxySQL中间件:高性能的 MySQL 代理,支持动态负载均衡、读写分离、查询缓存等功能。

  • ShardingSphere:阿里巴巴开源的分布式数据库中间件,支持分库分表、读写分离、分布式事务等。

2、ProxySQL

ProxySQL是基于MySQL的一款开源的中间件的产品,是一个灵活的MySQL代理层,可以实现读写分离,支持 Query路由功能,支持动态指定某个SQL进行缓存,

支持动态加载配置信息(无需重启 ProxySQL 服务),支持故障切换和SQL的过滤功能。

ProxySQL中管理结构

在ProxySQL,6032端口共五个库: main、disk、stats 、monitor、stats_history

  • main:

main 库中有如下信息:

mysql_servers: 后端可以连接 MySQL 服务器的列表

mysql_users: 配置后端数据库的账号和监控的账号。

mysql_query_rules: 指定 Query 路由到后端不同服务器的规则列表。

mysql_replication_hostgroups : 节点分组配置信息

注: 表名以 runtime_开头的表示ProxySQL 当前运行的配置内容,不能直接修改。不带runtime_是下文图中Mem相关的配置。

  • disk : 持久化的磁盘的配置

  • stats: 统计信息的汇总

  • monitor:监控的收集信息,比如数据库的健康状态等

  • stats_history:ProxySQL 收集的有关其内部功能的历史指标

admin管理接口,默认端口为6032 。该端口用于查看、配置ProxySQL。通过6032管理端口登入后,默认就是main库,所有的配置更改都必须在这个库中进行,disk存档库不会直接受到影响。

接收SQL语句的接口,默认端口为6033,这个接口类似于MySQL的3306端口。

ProxySQL的多层配置

整套配置系统分为三层:

  • RUNTIME:代表 ProxySQL 当前正在使用的配置,无法直接修改此配置,必须要从下一层 (MEM层)"load" 进来。

  • MEMORY:MEMORY 层上面连接 RUNTIME 层,下面disk持久层。这层可以在线操作 ProxySQL配置,随便修改,不会影响生产环境。确认正常之后在加载达到RUNTIME 和持久化的磁盘上。修改方法: insert、update、delete、select。

  • DISK和CONFIG FILE:持久化配置信息。重启时,可以从磁盘快速加载回来

使用proxysql

主要需要完成以下几项内容的配置:

1、配置监控账号。监控账号用于检测后端mysql实例是否健康(是否能连接、复制是否正常、复制是否有延迟等)。

2、到后端mysql实例创建监控账号。

3、配置后端mysql实例连接信息。实例连接信息存储在mysql_servers表。

4、配置连接proxysql和后端实例的账号。账号信息存储在mysql_users表。

5、配置查询路由信息。路由信息存储在mysql_query_rules表。

6、配置后端mysql集群信息。根据后端mysql集群架构,配置分别存储在mysql_replication_hostgroups、mysql_group_replication_hostgroups、runtime_mysql_galera_hostgroups、runtime_mysql_aws_aurora_hostgroups等表中。

7、根据具体需要,调优相关参数。参数存储在global_variables表。

(1)从库设定read_only参数

ProxySQL 会根据MySQL server 的read_only 的取值将服务器进行分组。 read_only=0 的server,会被分到写组,read_only=1 的server,则被分到读组。所以需要将从库设置: read_only=1;

sql 复制代码
vim /etc/my.cnf
 [mysqld]
 read_only=1

(2)配置读写组编号

在ProxySQL 上 mysql_replication_hostgroup表中(自带,无需创建)

sql 复制代码
mysql_replication_hostgroups (
     writer_hostgroup INT CHECK (writer_hostgroup>=0) NOT NULL PRIMARY KEY,
     reader_hostgroup INT NOT NULL CHECK (reader_hostgroup<>writer_hostgroup AND reader_hostgroup>=0),
     check_type VARCHAR CHECK (LOWER(check_type) IN ('read_only','innodb_read_only','super_read_only','read_only|innodb_read_only','read_only&innodb_read_only')) NOT NULL DEFAULT 'read_only',
     comment VARCHAR NOT NULL DEFAULT '', UNIQUE (reader_hostgroup)
)

创建组:(定义写为1,读为0),数字可以自定义

sql 复制代码
[root@db03 ~]# mysql -uadmin -padmin -h127.0.0.1 -P6032

MySQL> insert into mysql_replication_hostgroups (writer_hostgroup,reader_hostgroup,comment) values (1,0,'proxy');
Query OK, 1 row affected (0.00 sec)
sql 复制代码
 #加载到当前生效
 MySQL [(none)]> load mysql servers to runtime;
 Query OK, 0 rows affected (0.01 sec)
 ​
 #永久保存
 MySQL [(none)]> save mysql servers to disk;
 Query OK, 0 rows affected (0.02 sec)

ProxySQL会根据serverd的/etc/my.cnf配置文件中额的read_only的取值将服务器进行分组。read_only=0的server,master被分到编号为1的写组

(3)添加主机到ProxySQL

sql 复制代码
insert into mysql_servers(hostgroup_id,hostname,port) values (1,'172.16.100.21',3306);
insert into mysql_servers(hostgroup_id,hostname,port) values (0,'172.16.100.22',3306);
insert into mysql_servers(hostgroup_id,hostname,port) values (0,'172.16.100.23',3306);
sql 复制代码
//加载主从节点信息到runtime

load mysql servers to runtime;

save mysql servers to disk;

(4)配置 ProxySQL 所需账户

在 MySQL 的Master节点上创建 ProxySQL 的监控账户和对外访问账户

sql 复制代码
 #监控账户
 mysql>create user 'monitor'@'172.16.%.%' identified with mysql_native_password by 'Monitor@123.com';
 grant all privileges on *.* to 'monitor'@'172.16.%.%' with grant option;
 ​
 #proxysql 的对外访问账户
 mysql>create user 'proxysql'@'172.16.%.%' identified with mysql_native_password by '2025pro0702';
 grant all privileges on *.* to 'proxysql'@'172.16.%.%' with grant option;

(5)为ProxySQL监控MySQL后端节点

sql 复制代码
#切换到monitor库
 MySQL [(none)]> use monitor​
 Database changed
 MySQL [monitor]> set mysql-monitor_username='monitor';
 Query OK, 1 row affected (0.00 sec)
 ​
 MySQL [monitor]> set mysql-monitor_password='Monitor@123.com';
 Query OK, 1 row affected (0.00 sec)
 ​

上面这两句是修改变量的方式还可以在main库下面用sql语句方式修改

在main下修改:

sql 复制代码
 MySQL [monitor]> use main
 Database changed
 MySQL [main]> UPDATE global_variables SET variable_value='monitor' WHERE variable_name='mysql-monitor_username';
 Query OK, 1 row affected (0.00 sec)
 ​
 MySQL [main]> UPDATE global_variables SET variable_value='Monitor@123.com' WHERE variable_name='mysql-monitor_password';
 Query OK, 1 row affected (0.00 sec)

修改后,保存到runtime和disk

sql 复制代码
 MySQL [monitor]> load mysql variables to runtime;
 MySQL [monitor]> save mysql variables to disk;

(6)ProxySQL配置对外访问账号

ProxySQL 的mysql_users表结构如下:

sql 复制代码
mysql_users (
     username VARCHAR NOT NULL,
     password VARCHAR,
     active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1,
     use_ssl INT CHECK (use_ssl IN (0,1)) NOT NULL DEFAULT 0,
     default_hostgroup INT NOT NULL DEFAULT 0,
     default_schema VARCHAR,
     schema_locked INT CHECK (schema_locked IN (0,1)) NOT NULL DEFAULT 0,
     transaction_persistent INT CHECK (transaction_persistent IN (0,1)) NOT NULL DEFAULT 1,
     fast_forward INT CHECK (fast_forward IN (0,1)) NOT NULL DEFAULT 0,
     backend INT CHECK (backend IN (0,1)) NOT NULL DEFAULT 1,
     frontend INT CHECK (frontend IN (0,1)) NOT NULL DEFAULT 1,
     max_connections INT CHECK (max_connections >=0) NOT NULL DEFAULT 10000,
     attributes VARCHAR CHECK (JSON_VALID(attributes) OR attributes = '') NOT NULL DEFAULT '',
     comment VARCHAR NOT NULL DEFAULT '',
     PRIMARY KEY (username, backend),
     UNIQUE (username, frontend)
)
sql 复制代码
insert into mysql_users (username,password,default_hostgroup,transaction_persistent) values ('proxysql','2025pro0702',1,1);


load mysql users to runtime;
save mysql users to disk;

transaction_persistent 如果为1,则一个完整的SQL只可能路由到一个节点;这点非常重要,主要解决这种情况:一个事务有混合的读操作和写操作组成,事务未提交前,如果事务中的读操作和写操作路由到不同节点,那么读取到的结果必然是脏数据。所以一般情况下,该值应该设置为1,尤其是业务中使用到事务机制的情况(默认为0)

mysql_users 表有不少字段,最主要的三个字段username,password,default_hostgroup 。

(7)添加读写分离规则(mysql_query_rules)

基于用户的路由:

sql 复制代码
Insert into mysql_users(username,password,default_hostgroup) 
values('writer','123',10),('reader','123',20);
load mysql users to runtime;
save mysql users to disk;
 
 
insert into mysql_query_rules(rule_id,active,username,destination_hostgroup,apply) 
values(1,1,'writer',10,1),(2,1,'reader',20,1);
 
load mysql query rules to runtime;
save mysql query rules to disk;

基于端口的路由:

sql 复制代码
## 修改ProxySQL监听SQL流量的端口号,监听多端口上。
set mysql-interfaces='0.0.0.0:6033;0.0.0.0:6034';
save mysql variables to disk;
## 重启生效
systemctl restart proxysql
 
## 设定路由规则
 
insert into mysql_query_rules(rule_id,active,proxy_port,destination_hostgroup,apply) 
values(1,1,6033,10,1), (2,1,6034,20,1);
 
load mysql query rules to runtime;
save mysql query rules to disk;

还可以基于监听地址(修改字段proxy_addr即可),也可以基于客户端地址(修改字段client_addr字段即可)。

基于正则表达式的路由:

sql 复制代码
 insert into mysql_query_rules(rule_id,active,match_pattern,destination_hostgroup,apply) 
 values(1,1,'^select .* for update$',1,1);
 ​
 insert into mysql_query_rules(rule_id,active,match_pattern,destination_hostgroup,apply) 
 values(2,1,'^select',0,1);
  1. 表示像select * from xxx for update这种语句都会分到到写组,2)表示像select这种语句都会被分配到读组。
相关推荐
guojl16 分钟前
Java多任务编排技术
java
丶意冷25 分钟前
mybatisPlus分页方言设置错误问题 mybatisPlus对于Oceanbase的Oracle租户分页识别错误
java·数据库·oracle·oceanbase
要开心吖ZSH1 小时前
《Spring 中上下文传递的那些事儿》Part 4:分布式链路追踪 —— Sleuth + Zipkin 实践
java·分布式·spring
桦说编程1 小时前
深入解析CompletableFuture源码实现
java·性能优化·源码
蓝澈11212 小时前
迪杰斯特拉算法之解决单源最短路径问题
java·数据结构
Kali_072 小时前
使用 Mathematical_Expression 从零开始实现数学题目的作答小游戏【可复制代码】
java·人工智能·免费
rzl022 小时前
java web5(黑马)
java·开发语言·前端
时序数据说2 小时前
为什么时序数据库IoTDB选择Java作为开发语言
java·大数据·开发语言·数据库·物联网·时序数据库·iotdb
戒不掉的伤怀2 小时前
【Navicat 连接MySQL时出现错误1251:客户端不支持服务器请求的身份验证协议;请考虑升级MySQL客户端】
服务器·数据库·mysql
jingling5552 小时前
面试版-前端开发核心知识
开发语言·前端·javascript·vue.js·面试·前端框架