ProxySQL:高效 MySQL 代理实战指南(从单库代理到读写分离)

在现代应用架构中,数据库性能和可扩展性是决定系统稳定性和高效性的关键因素。对于使用 MySQL 的系统来说,ProxySQL 是一个优秀的开源中间件,能够实现连接池管理、查询路由、读写分离以及隐藏真实数据库节点等强大功能。

本文记录了我从“如何在广域网环境中安全暴露单一数据库”开始,到一步步将其扩容为“主从架构读写分离”的实际解决过程。希望这篇实战记录能帮助你快速上手并搭建一套高可用的 ProxySQL 代理层。

为什么需要 ProxySQL?(实战背景)

在我的实际业务场景中,有几台部署在不同云厂商(广域网环境)的应用服务器,需要访问一台集中管理的 MySQL 数据库。

如果直接将数据库的 3306 端口暴露给公网,面临以下严重问题:

  1. 安全隐患:真实数据库 IP 地址暴露在公网,容易遭受暴力破解或攻击。
  2. 权限粒度粗:很难在数据库入口前再加一层精细化的流量控制与账户隔离(例如只共享某一个库,限制最大连接数)。
  3. 扩展困难:未来如果业务量增长需要做数据库读写分离,应用层代码必须修改连接池或数据库配置,侵入性极强。

为了解决这些问题,我引入了 ProxySQL 作为协议层代理。ProxySQL 是一个专门为高并发和大规模数据库集群设计的轻量级中间层,具备以下核心优势:

  • 高吞吐与连接池:内置高性能连接池,大幅减少频繁连接/断开数据库产生的开销。
  • 零代码侵入读写分离:根据 SQL 语法自动分发读写请求,应用只需连接 ProxySQL 的代理端口。
  • 动态配置机制:绝大多数配置可以在运行时通过 SQL 命令动态修改,无需重启服务。

环境搭建:使用 Docker Compose 部署

为了方便运维管理和迁移,我采用 docker-compose 来部署 ProxySQL。

准备目录与初始配置文件

在宿主机上创建配置文件和数据挂载目录

mkdir -p ./proxysql/data
touch ./proxysql/proxysql.cnf

提示:如果 ./proxysql/proxysql.cnf 为空文件,ProxySQL 启动时会自动加载内置默认配置。你也可以直接使用官方提供的标准模版。

编写 docker-compose.yml

version: '3.8'

services:
  proxysql:
    image: proxysql/proxysql:2.7.0
    container_name: proxysql
    restart: on-failure:5
    # 暴露端口:6033 为业务连接端口,6032 为 ProxySQL 管理端口
    ports:
      - "6033:6033"          # MySQL 代理访问端口
      - "127.0.0.1:6032:6032" # 管理端口(强烈建议仅监听本地 127.0.0.1)
    volumes:
      - ./proxysql/proxysql.cnf:/etc/proxysql.cnf
      - ./proxysql/data:/var/lib/proxysql
    networks:
      - database_net

networks:
  database_net:
    driver: bridge

启动容器:

docker-compose up -d

确认容器运行状态

docker ps | grep proxysql

ProxySQL 核心概念解析

在正式配置之前,有两套关键概念必须理解:“三套用户”与“多层配置架构”

三套用户系统

在 ProxySQL 中,三套用户分别指管理用户、应用用户和监控用户,它们具有不同的权限和作用,适用于不同的场景。

  1. 管理用户(Admin User):
    • 作用:连接 6032 管理端口,修改 ProxySQL 本身的内部配置。
    • 默认凭据:用户名 admin,密码 admin。
  2. 监控用户(Monitor User):
    • 作用:ProxySQL 用来向后端真实的 MySQL 节点发送心跳和探活查询的用户。需要在后端 MySQL 上提前创建好。
  3. 应用用户(Application User):
    • 作用:业务应用程序连接 ProxySQL(6033 端口)使用的凭据。ProxySQL 也会拿着对应的凭据去代理访问后端的 MySQL。

多层配置架构与操作指令

ProxySQL 的配置系统分为三层,实现了“运行时修改”与“持久化落盘”分离:

[ RUNTIME ]          <-- 当前内存生效的配置(不可直接修改)
     ▲
     │  LOAD / SAVE
     ▼
[ MEMORY ]           <-- 管理接口 (6032) 修改的配置区域
     ▲
     │  LOAD / SAVE
     ▼
[ DISK / CONFIG FILE ] <-- 磁盘 SQLite 数据库及配置文件
  • RUNTIME:代表 ProxySQL 当前正在使用的物理内存配置。不能直接修改,必须从 MEMORY 层 LOAD 进来。
  • MEMORY:通过 6032 端口连接 ProxySQL 管理 SQL 接口时修改的就是这一层。随意修改不会直接影响正在运行的业务。
  • DISK & CONFIG FILE:磁盘持久化存储(内嵌的 SQLite 数据库)。用于保证重启后配置不丢失。

核心流转指令

在 6032 管理终端中,配置修改的标准流程是:修改 MEMORY -> LOAD 到 RUNTIME -> SAVE 到 DISK

常用命令模式如下:

  • LOAD TO RUNTIME; —— 将配置从内存加载到运行时生效
  • SAVE TO DISK; —— 将配置保存到磁盘数据库
  • LOAD FROM DISK; —— 从磁盘加载配置到内存
  • LOAD FROM CONFIG; —— 从配置文件加载配置到内存

(其中 可以是 MYSQL SERVERS, MYSQL USERS, MYSQL QUERY RULES, MYSQL VARIABLES 等)

实战一:配置单节点代理(对外暴露单一数据库)

首先解决我最初的诉求:隐藏后端真实 MySQL(假设 IP 为 192.168.1.100:3306),只将 app_db 数据库暴露给广域网服务。

步骤 1:在后端 MySQL 上创建对应用户

登录后端真正的 MySQL 节点,创建监控用户和业务用户:

-- 1. 创建 ProxySQL 监控用户(仅需最低基础权限)
CREATE USER 'monitor'@'%' IDENTIFIED BY 'MonitorPass123!';
GRANT USAGE, REPLICATION CLIENT ON *.* TO 'monitor'@'%';

-- 2. 创建业务应用用户(按需仅授权 app_db 数据库)
CREATE USER 'app_user'@'%' IDENTIFIED BY 'AppPass123!';
GRANT ALL PRIVILEGES ON app_db.* TO 'app_user'@'%';

FLUSH PRIVILEGES;

步骤 2:登录 ProxySQL 管理终端 (6032)

在宿主机上使用本地 mysql 客户端连接 ProxySQL 的管理接口:

mysql -h 127.0.0.1 -P 6032 -u admin -padmin

步骤 3:配置监控账户信息

在 Admin 交互界面中,告诉 ProxySQL 监控用户的信息:

SET mysql-monitor_username='monitor';
SET mysql-monitor_password='MonitorPass123!';
SET mysql-monitor_ping_interval=2000; -- 2秒探活一次

-- 生效并持久化
LOAD MYSQL VARIABLES TO RUNTIME;
SAVE MYSQL VARIABLES TO DISK;

步骤 4:添加后端数据库节点

我们将后端 MySQL 添加到组 ID 为 10 的服务器组中(通常约定 10 为主库/写库组):

-- hostgroup_id = 10 表示主库组/单库组
INSERT INTO mysql_servers (hostgroup_id, hostname, port, max_connections) 
VALUES (10, '192.168.1.100', 3306, 500);

-- 加载到运行内存并保存到磁盘
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;

步骤 5:配置业务应用用户

在 ProxySQL 中配置允许连接的用户,并指定默认路由到的服务器组:

INSERT INTO mysql_users (username, password, default_hostgroup, active) 
VALUES ('app_user', 'AppPass123!', 10, 1);

-- 加载到运行内存并保存到磁盘
LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL USERS TO DISK;

步骤 6:测试代理连接

在广域网应用服务器上,连接 ProxySQL 的 6033 代理端口:

mysql -h <ProxySQL_IP> -P 6033 -u app_user -p'AppPass123!' app_db

连接成功!此时应用访问的是 ProxySQL 的 6033 端口,真实的数据库 3306 端口成功被隐藏到了内部网络中。

实战二:扩展为 MySQL 主从读写分离

当后续业务访问量增大时,我在后端新增了一台 MySQL 从库(192.168.1.101:3306),主从配置好数据同步后,希望 ProxySQL 自动把读请求分发给从库,写请求发给主库

ProxySQL 的主机组规划:

  • Hostgroup 10:写节点组(Master)
  • Hostgroup 20:读节点组(Slave)

步骤 1:在 ProxySQL 中添加从节点

登录 6032 管理端口,将从库加入 Hostgroup 20:

-- 添加 Slave 到组 20
INSERT INTO mysql_servers (hostgroup_id, hostname, port, max_connections) 
VALUES (20, '192.168.1.101', 3306, 500);

LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;

注意:从库上也需要确保包含前面创建的 monitorapp_user 账号(主从复制通常会自动同步过来)。

步骤 2:配置读写分离路由规则

ProxySQL 使用 mysql_query_rules 表中的正则表达式匹配 SQL,决定将其发往哪个主机组。

-- 清空历史规则(如果是全新配置)
DELETE FROM mysql_query_rules;

-- 规则 1:带有 FOR UPDATE 的 SELECT 语句必须走主库(Hostgroup 10)
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) 
VALUES (1, 1, '^SELECT.*FOR UPDATE$', 10, 1);

-- 规则 2:普通的 SELECT 语句全部发往从库组(Hostgroup 20)
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 规则的所有语句(如 INSERT, UPDATE, DELETE)会使用 mysql_users 表中设置的 default_hostgroup(即组 10),从而自动路由至主库。

步骤 3:配置主从动态维护(可选推荐)

ProxySQL 能够自动根据 MySQL 的 read_only 状态维护主机组。防止主从切换时发生误写:

INSERT INTO mysql_replication_hostgroups (writer_hostgroup, reader_hostgroup, comment) 
VALUES (10, 20, 'Master-Slave Auto Failover Group');

LOAD MYSQL REPLICATION HOSTGROUPS TO RUNTIME;
SAVE MYSQL REPLICATION HOSTGROUPS TO DISK;

运维与监控常用指令

ProxySQL 内部提供了非常丰富的统计视图(stats 库)和监控视图(monitor 库),无需借助第三方工具即可查询运行状态。

查看后端节点状态

-- 查看当前运行中的后端服务器列表及其状态(ONLINE / SHUNNED / OFFLINE)
SELECT hostgroup_id, hostname, port, status, Queries, Latency_us FROM runtime_mysql_servers;

查看健康检查心跳日志

-- 查看 ProxySQL 对后端的 Ping 监控情况
SELECT hostname, port, time_start_us, rc, error FROM monitor.mysql_server_ping_log ORDER BY time_start_us DESC LIMIT 5;

验证读写分离命中情况

-- 查看路由规则命中统计
SELECT rule_id, hits FROM stats.stats_mysql_query_rules;

-- 查看 SQL 级别的汇总统计
SELECT digest_text, count_star, sum_time FROM stats.stats_mysql_query_digest ORDER BY count_star DESC LIMIT 10;

总结与注意事项

通过从“单库代理”“读写分离”的平滑演进,ProxySQL 成功帮我解决了广域网数据库暴露的安全问题,并为高并发架构打下了良好的基础。

在实际使用 ProxySQL 时,有几个踩坑点需要特别注意:

  1. 修改必须持久化:在 6032 端口修改完配置后,务必执行 SAVE … TO DISK;,否则容器重启后内存中(MEMORY/RUNTIME)的更改会全部丢失。
  2. 读写分离一致性:对于在主库刚写入数据后立马要查出来的业务逻辑,如果读请求走从库,可能会因主从延迟导致查不到数据。这类特定 SQL 可以通过 SQL 注释或在 ProxySQL 中显式增加高优先级规则路由回主库。
  3. 管理端口安全:6032 端口拥有 ProxySQL 的绝对控制权,切勿直接暴露出公网,或者在部署后第一时间修改默认的 admin/admin 密码。

暂无评论

发送评论 编辑评论


				
上一篇
下一篇