RDS 代理
通过 生产级别的 TCP 代理服务器 实现 MySQL 透明访问,支持 Navicat、DBeaver 等原生客户端工具直接连接。
✅ 已实现功能:
- 原生 TCP 代理(端口 13306)
- 高性能连接池(连接复用、健康检查)
- 协议解析与实时阻断(CRITICAL 级别 SQL)
- Prometheus 监控(10+ 指标)
- Goroutine 池(限制并发)
- 优雅关闭(信号处理)
- 管理 API(会话管理、连接池统计)
🔗 快速链接:
技术方案
基于用户映射表的 MySQL 代理网关:代理服务维护独立的用户认证系统,通过用户-数据库映射表路由到后端 MySQL 实例。
核心设计理念
graph TB
subgraph Client[客户端层]
A[MySQL客户端<br/>Navicat/DBeaver]
end
subgraph Proxy[RDS代理网关]
B[TCP监听器<br/>:13306]
C[认证模块<br/>调用User模块]
D[路由模块<br/>查询RDS映射]
E[连接池<br/>后端连接管理]
F[审计模块<br/>SQL记录]
end
subgraph MProxyCommon[MProxy公共模块]
G[User模块<br/>统一用户管理]
end
subgraph Storage[存储层]
H[(mproxy_users<br/>代理用户表)]
I[(rds_user_mappings<br/>RDS映射表)]
J[(rds_backend_mysql<br/>后端实例表)]
K[(rds_sql_audit<br/>审计表)]
end
subgraph Backend[后端数据库集群]
L[MySQL-1<br/>业务A库<br/>10.0.0.1:3306]
M[MySQL-2<br/>业务B库<br/>10.0.0.2:3306]
N[MySQL-3<br/>业务C库<br/>10.0.0.3:3307]
end
A -->|1.连接请求| B
B -->|2.提取用户名密码| C
C -->|3.验证用户| G
G -->|4.查询用户| H
C -->|5.查询映射关系| I
D -->|6.获取后端配置| J
E -->|7.获取后端连接| L
E -->|7.获取后端连接| M
E -->|7.获取后端连接| N
F -->|8.记录SQL| K
style G fill:#ffe6f3
style C fill:#e6f3ff
style D fill:#fff4e6
style E fill:#f0f0f0
数据模型关系
erDiagram
MPROXY_USERS ||--o{ RDS_USER_MAPPINGS : has
RDS_USER_MAPPINGS ||--|| RDS_BACKEND_MYSQL : targets
MPROXY_USERS ||--o{ RDS_SQL_AUDIT : generates
MPROXY_USERS {
bigint id PK
string username UK "代理用户名"
string password_hash "bcrypt密码"
string status "enabled/disabled"
int max_connections "最大连接数"
json permissions "权限配置JSON"
string description "用户描述"
datetime created_at
datetime updated_at
}
RDS_USER_MAPPINGS {
bigint id PK
string username FK "代理用户名"
string backend_id FK "后端MySQL ID"
string backend_username "后端用户名"
string backend_password "加密密码"
string target_database "目标库"
bool read_only "只读模式"
int priority "优先级"
bool enabled
datetime created_at
}
RDS_BACKEND_MYSQL {
bigint id PK
string backend_id UK "实例ID"
string host "主机地址"
int port "端口"
string role "master/slave"
int max_pool_size "连接池大小"
int weight "权重"
bool enabled
datetime created_at
}
RDS_SQL_AUDIT {
bigint id PK
string session_id
string username FK "代理用户"
string backend_id FK "后端实例"
string sql_statement
string sql_type
int exec_time_ms
datetime executed_at
}
模块架构
mproxy/
├── user/ # 用户模块(公共模块)
│ ├── impl.go # 用户服务实现
│ ├── interface.go # 用户接口定义
│ └── model.go # mproxy_users 表模型
│
├── rds/ # RDS代理模块
│ ├── impl/
│ │ ├── auth.go # 认证逻辑(调用user模块)
│ │ ├── mapping.go # 映射管理
│ │ ├── router.go # 路由策略
│ │ └── tcp_server.go # TCP服务器
│ └── model.go # RDS相关表模型
│
└── redis/ # Redis代理模块(未来扩展)
└── ...
数据库表设计
1. 代理用户表 (mproxy_users) - User模块
CREATE TABLE mproxy_users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username rds_backend_mysql (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
backend_id VARCHAR(64) NOT NULL UNIQUE COMMENT '后端实例ID',
host VARCHAR(255) NOT NULL COMMENT 'MySQL主机地址',
port INT NOT NULL DEFAULT 3306 COMMENT 'MySQL端口',
role ENUM('master', 'slave', 'analytics') DEFAULT 'master' COMMENT '角色',
max_pool_size INT DEFAULT 20 COMMENT '连接池大小',
enabled BOOLEAN DEFAULT TRUE COMMENT '是否启用',
weight INT DEFAULT 1 COMMENT '负载均衡权重',
description VARCHAR(255) COMMENT '实例描述',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_backend_id (backend_id),
INDEX idx_enabled (enabled),
INDEX idx_role (role)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='RDS后端MySQL实例表';
-- 示例数据
INSERT INTO rds_backend_mysql (backend_id, host, port, role, max_pool_size) VALUES
('mysql-biz-master', '10.0.0.1', 3306, 'master', 20),
('mysql-biz-slave-1', '10.0.0.2', 3306, 'slave', 50),
('mysql-biz-slave-2', '10.0.0.3', 3306, 'slave', 50),
('mysql-report', '10.0.0.4', 3307, 'analytics', 10);
port INT NOT NULL DEFAULT 3306 COMMENT 'MySQL端口',
role ENUM('master', 'slave', 'analytics') DEFAULT 'master' COMMENT '角色',
max_pool_size INT DEFAULT 20 COMMENT '连接池大小',
enabled BOOLEAN DEFAULT TRUE COMMENT '是否启用',
weight INT DEFAULT 1 COMMENT '负载均衡权重',
description VARCHAR(255) COMMENT '实例描述',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_backend_id (backend_id),
INDEX idx_enabled (enabled),
INDEX idx_role (role)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后端MySQL实例表';
-- 示例数据
INSERT INTO backend_mysql (backend_id, host, port, role, max_pool_size) VALUES
('mysql-biz-master', '10.0.0.1', 3306, 'master', 20),
('mysql-biz-slave-1', '10.0.0.2', 3306, 'slave', 50),
('mysql-biz-slave-2', '10.0.0.3', 3306, 'slave', 50),
('mysql-report', '10.0.0.4', 3307, 'analytics', 10);
#### 3. 用户映射表 (user_mappings)
```sql
CREATE TABLE rds_user_mappings (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(64) NOT NULL COMMENT '代理用户名(关联mproxy_users)',
backend_id VARCHAR(64) NOT NULL COMMENT '后端MySQL ID',
backend_username VARCHAR(64) NOT NULL COMMENT '后端MySQL用户名',
backend_password VARCHAR(512) NOT NULL COMMENT '后端MySQL密码(AES加密)',
target_database VARCHAR(64) COMMENT '目标数据库名',
read_only BOOLEAN DEFAULT FALSE COMMENT '是否只读',
priority INT DEFAULT 1 COMMENT '优先级(数字越大越优先)',
enabled BOOLEAN DEFAULT TRUE COMMENT '是否启用',
description VARCHAR(255) COMMENT '映射描述',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_backend (username, backend_id),
INDEX idx_username (username),
INDEX idx_backend_id (backend_id),
FOREIGN KEY (username) REFERENCES mproxy_users(username) ON DELETE CASCADE,
FOREIGN KEY (backend_id) REFERENCES rds_backend_mysql(backend_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='RDS用户映射表';
-- 示例数据:app_developer 映射到主库(读写)
INSERT INTO rds_user_mappings (username, backend_id, backend_username, backend_password, target_database, read_only, priority) VALUES
('app_developer', 'mysql-biz-master', 'biz_user', 'encrypted:xxxxx', 'business_db', FALSE, 1);
-- 示例数据:report_user 映射到从库(只读)
INSERT INTO rds_user_mappings (username, backend_id, backend_username, backend_password, target_database, read_only, priority) VALUES
('report_user', 'mysql-biz-slave-1', 'readonly_user', 'encrypted:yyyyy', 'business_db', TRUE, 1),
('report_user', 'mysql-biz-slave-2', 'readonly_user', 'encrypted:yyyyy', 'business_db', TRUE, 2);
-- 示例数据:dba_admin 可以访问所有实例
INSERT INTO rds_user_mappings (username, backend_id, backend_username, backend_password, target_database, read_only) VALUES
('dba_admin', 'mysql-biz-master', 'root', 'encrypted:zzzzz', NULL, FALSE),
('dba_admin', 'mysql-biz-slave-1', 'root', 'encrypted:zzzzz', NULL, FALSE);
4. 配置文件 (application.toml)
[mproxy]
# 元数据数据库配置(存储 mproxy_users 表)
[mproxy.metadata_db]
host = "127.0.0.1"
port = 3306
username = "mproxy_admin"
password = "admin_password"
database = "mproxy_metadata"
max_pool_size = 10
[rds]
# RDS代理服务配置
tcp_enabled = true
tcp_listen_addr = "0.0.0.0:13306"
tcp_max_conns = 1000
# RDS元数据配置(存储 rds_user_mappings、rds_backend_mysql 表)
# 可以和 mproxy.metadata_db 使用同一个数据库
[rds.metadata_db]
host = "127.0.0.1"
port = 3306
username = "rds_proxy"
password = "proxy_password"
database = "mproxy_metadata" # 共享同一个数据库
max_pool_size = 10
使用场景示例
场景 1:开发环境隔离
graph LR
A[开发者Alice] -->|app_dev_alice| B[RDS代理]
C[开发者Bob] -->|app_dev_bob| B
B -->|映射| D[MySQL Dev-1]
B -->|映射| E[MySQL Dev-2]
style A fill:#e6f3ff
style C fill:#ffe6f3
style D fill:#f0f0f0
style E fill:#f0f0f0
配置:
app_dev_alice → dev_mysql_1 (独立开发库)
app_dev_bob → dev_mysql_2 (独立开发库)
场景 2:读写分离
graph TB
A[业务应用] -->|app_user| B[RDS代理]
C[报表系统] -->|report_user| B
B -->|写操作| D[Master<br/>10.0.0.1]
B -->|读操作| E[Slave-1<br/>10.0.0.2]
B -->|读操作| F[Slave-2<br/>10.0.0.3]
D -.->|复制| E
D -.->|复制| F
style D fill:#ffcccc
style E fill:#ccffcc
style F fill:#ccffcc
配置:
app_user → Master (读写) + Slave (只读)
report_user → Slave (只读)
场景 3:多租户隔离
graph TB
A[租户A] -->|tenant_a| B[RDS代理]
C[租户B] -->|tenant_b| B
D[租户C] -->|tenant_c| B
B --> E[MySQL-1<br/>tenant_a_db]
B --> F[MySQL-2<br/>tenant_b_db]
B --> G[MySQL-3<br/>tenant_c_db]
style E fill:#e6f3ff
style F fill:#ffe6f3
style G fill:#fff4e6
配置:
tenant_a → mysql-1/tenant_a_db
tenant_b → mysql-2/tenant_b_db
tenant_c → mysql-3/tenant_c_db
方案对比
| 对比项 |
直接路由方案 |
用户映射方案 (推荐) |
| 认证方式 |
透传后端MySQL认证 |
代理层独立认证 |
| 权限控制 |
依赖后端MySQL |
代理层统一管理 |
| 多租户支持 |
❌ 需要多个MySQL账号 |
✅ 一个代理账号映射多个后端 |
| 读写分离 |
⚠️ 需客户端配置 |
✅ 代理层自动处理 |
| 连接数控制 |
❌ 后端MySQL限制 |
✅ 代理层精确控制 |
| 密码管理 |
❌ 分散在各个MySQL |
✅ 统一管理 |
| 审计粒度 |
⚠️ 只知道后端用户 |
✅ 知道真实业务用户 |
| 实现复杂度 |
⭐⭐ |
⭐⭐⭐⭐ |
| 灵活性 |
⭐⭐ |
⭐⭐⭐⭐⭐ |
选型理由:
- ✅ 用户映射方案 = ProxySQL + MyCAT + MaxScale 的标准设计
- ✅ 支持一个用户映射到多个后端(读写分离、负载均衡)
- ✅ 支持权限隔离(不同用户看到不同数据库)
- ✅ 支持连接数管控(防止后端过载)
- ✅ 支持密码轮换(不影响客户端)
代理流程详解
1. 连接认证流程
sequenceDiagram
participant Client as MySQL客户端
participant Proxy as RDS代理网关
participant UserDB as 代理用户表
participant MappingDB as 用户映射表
participant Backend as 后端MySQL
Client->>Proxy: 1. TCP连接请求 (username, password)
Proxy->>Proxy: 2. 解析MySQL握手包
Proxy->>UserModule: 3. 调用User模块验证<br/>user.Authenticate(username, password)
UserModule->>UserDB: 4. 查询用户<br/>SELECT * FROM mproxy_users<br/>WHERE username=? AND status='enabled'
UserDB-->>UserModule: 5. 返回用户信息
UserModule-->>Proxy: 6. 验证结果 (password_hash, max_connections, permissions)
alt 用户不存在或密码错误
Proxy-->>Client: 7a. 认证失败 (Access denied)
else 认证成功且有RDS权限
Proxy->>MappingDB9. 建立后端连接 (backend_username, backend_password)
Backend-->>Proxy: 10. 后端认证成功
Proxy-->>Client: 11. 代理认证成功
end
实施计划
阶段 0:User模块开发 🆕 ⏳
-
创建 mproxy/user/ 模块目录
-
定义 User 服务接口 (Authenticate, GetUser, CreateUser, UpdateUser)
-
实现 User 服务 (连接元数据库、bcrypt 验证)
-
创建 mproxy_users 表 (DDL + 索引)
-
单元测试 User 模块
-
提供 User 管理 API (GET/POST/PUT/DELETE /api/mproxy/users)
2:认证模块 ⏳
-
实现元数据库连接池(读取配置表)
-
实现 MySQL 握手包解析(提取用户名/密码)
-
集成 User 模块进行认证(调用 user.Authenticate())
-
检查用户 RDS 权限(permissions.rds == true)
-
实现映射查询(JOIN rds_user_mappings 和 rds_backend_mysql)
-
实现映射结果缓存(减少数据库查询)_mappings` 表 (DDL + 外键)
-
设计配置文件结构 (元数据库连接信息)
-
准备测试数据 (INSERT 语句)
-
评审数据模型
阶段 元数据库连接池(读取配置表)
- 实现 MySQL 握手包解析(提取用户名/密码)
- 实现代理用户验证逻辑(查询
proxy_users 表)
- 实现 bcrypt 密码校验
- 实现用户映射查询(JOIN
user_mappings 和 backend_mysql)
- 实现映射结果缓存(减少数据库查询) 握手包解析(提取用户名/密码)
- 实现代理用户验证逻辑
- 实现用户映射查询
- 实现后端 MySQL 认证
- 测试认证流程
阶段 3:路由模块 ⏳
- 实现用户映射加载器
- 实现单映射路由(1 user → 1 backend)
- 实现多映射路由(1 user → N backends)
- 实现读写分离路由策略
- 实现负载均衡路由策略
阶段 4:连接池改造 ⏳
- 改造连接池支持多后端
- 实现按用户+后端的连接池分组
- 实现连接数限制(per user)
- 实现连接健康检查
- 实现连接复用优化
阶段 5:测试与优化 ⏳
- 单元测试(认证、路由、连接池)
- 集成测试(Navicat/DBeaver 连接)
- 压力测试(1000 并发连接)
- 性能优化(减少映射查询次数)
- 文档完善
参考实现
类似开源项目:
-
ProxySQL:基于 C++ 的高性能 MySQL 代理,支持用户映射和读写分离
-
MyCAT:基于 Java 的分库分表中间件,支持多租户
-
MaxScale:MariaDB 官方代理,支持复杂路由规则
-
Vitess:YouTube 开源的 MySQL 集群方案
- 代理用户密码:bcrypt (推荐,安全性高)
- 后端MySQL密码:AES-256 加密存储 (需要解密后连接后端)
-
映射查询性能:
- 是否需要缓存映射关系?(推荐:本地缓存 + TTL 5分钟)
- 缓存失效策略:定时刷新 or 手动刷新 API?
-
配置热更新:
- 是否支持不重启更新映射配置?(推荐:支持)
- 提供管理 API:POST /api/rds/reload-config
-
后端连接失败:
- 是否自动切换到备用后端?(推荐:如果有多个映射,自动 failover)
- 失败重试次数:3次?间隔时间?
-
连接池粒度:
- 按 (backend_id) 分组? (推荐,共享连接)
- 还是按 (proxy_username, backend_id) 分组?(隔离性更好)
-
元数据库高可用:
- 元数据库挂了怎么办?(使用本地缓存兜底?)
- 是否需要主从配置?
-
管理 API:
- 是否提供 Web 界面管理用户和映射?
- 还是只提供 RESTful API
-
配置热更新:是否支持不重启更新映射配置?
-
后端连接失败:是否自动切换到备用后端?
-
连接池粒度:是否需要按 (user, backend, database) 三元组分组? else 认证成功
Proxy->>MappingDB: 5b. 查询映射 SELECT * FROM user_mappings WHERE username=?
MappingDB-->>Proxy: 6. 返回映射配置 (backend_id, target_db, credentials)
Proxy->>Backend: 7. 建立后端连接 (backend_username, backend_password)
Backend-->>Proxy: 8. 后端认证成功
Proxy-->>Client: 9. 代理认证成功
end
#### 2. SQL 执行流程
```mermaid
sequenceDiagram
participant Client as MySQL客户端
participant Proxy as RDS代理网关
participant Parser as SQL解析器
participant Audit as 审计模块
participant Pool as 连接池
participant Backend as 后端MySQL
Client->>Proxy: 1. 发送SQL查询
Proxy->>Parser: 2. 解析SQL (类型、表、风险)
Parser-->>Proxy: 3. 返回解析结果 (type=SELECT, risk=low)
alt 高风险SQL (DROP/TRUNCATE)
Proxy->>Audit: 4a. 记录拒绝事件
Proxy-->>Client: 4b. 拒绝执行 (SQL blocked)
else 正常SQL
Proxy->>Pool: 4c. 获取后端连接
Pool-->>Proxy: 5. 返回连接
Proxy->>Backend: 6. 转发SQL
Backend-->>Proxy: 7. 返回结果集
Proxy->>Audit: 8. 异步记录审计
Proxy-->>Client: 9. 返回结果
end
3. 路由决策流程
flowchart TD
A[接收SQL请求] --> B{查询用户映射}
B -->|单个映射| C[直连指定后端]
B -->|多个映射| D{读写分离?}
D -->|是| E{SQL类型判断}
E -->|SELECT| F[路由到Slave]
E -->|INSERT/UPDATE/DELETE| G[路由到Master]
D -->|否| H{负载均衡策略}
H -->|轮询| I[Round Robin]
H -->|哈希| J[Hash by username]
H -->|加权| K[Weighted Random]
C --> L[执行SQL]
F --> L
G --> L
I --> L
J --> L
K --> L
L --> M[返回结果]
style E fill:#e6f3ff
style H fill:#fff4e6
审计方案
审计架构
flowchart TD
A[MySQL协议数据流] --> B{协议解析}
B -->|握手包| C[连接审计]
B -->|查询包| D[SQL审计]
B -->|结果集| E[性能统计]
D --> F{风险检测}
F -->|critical| G[阻断执行]
F -->|high| H[告警]
F -->|low| I[记录]
C & D & E --> J[(审计数据库)]
G & H & I --> J
style G fill:#ffcccc
style H fill:#fff4cc
style I fill:#ccffcc
SQL风险等级
pie title SQL操作风险分布
"低风险(SELECT)" : 70
"中风险(INSERT/UPDATE)" : 20
"高风险(DELETE/TRUNCATE)" : 8
"严重(DROP/GRANT)" : 2
审计数据模型
erDiagram
MYSQL_CONNECTION ||--o{ SQL_AUDIT : executes
MYSQL_CONNECTION ||--o{ RISK_EVENT : triggers
SQL_AUDIT ||--o{ AFFECTED_TABLE : involves
MYSQL_CONNECTION {
string session_id PK
string user_id
string db_host
string db_name
datetime connect_time
}
SQL_AUDIT {
string session_id FK
string sql_statement
string sql_type
int exec_time
int64 affected_rows
}
RISK_EVENT {
string session_id FK
string risk_level
string action
datetime timestamp
}
数据脱敏方案
flowchart LR
A[查询结果] --> B{脱敏规则匹配}
B -->|手机号| C["138****1234"]
B -->|邮箱| D["abc***@domain.com"]
B -->|身份证| E["110***********123"]
B -->|无规则| F[原始数据]
C & D & E & F --> G[返回客户端]