1. 为什么在SpringBoot项目里绕不开MySQL Router——不是“多此一举”,而是架构演进的必经关卡
你有没有遇到过这样的场景:某天凌晨三点,线上订单接口响应时间突然从80ms飙升到2.3秒,监控大盘上数据库CPU直冲98%,慢查询日志里全是SELECT语句扎堆出现;运维同事一边重启连接池,一边在群里问“谁刚发了新版本?是不是加了没索引的JOIN?”;而你翻着刚合并的PR,发现只是给用户中心加了个“最近浏览商品列表”的小功能——它只读,不改数据,却把主库拖垮了。
这就是典型的读写流量混跑在单一MySQL实例上的反模式。SpringBoot应用默认用的JDBC连接池(比如HikariCP)直连主库,所有SQL——无论INSERT/UPDATE/DELETE还是SELECT——全打在同一个节点上。当业务增长到单库QPS破3000、连接数超200时,主库就成了整个系统的“单点瓶颈”。这时候,光靠加索引、优化SQL、调大buffer pool,已经解决不了根本问题。
MySQL Router 8 正是MySQL官方为解决这个痛点推出的轻量级代理层。它不处理SQL解析,不缓存数据,不做计算,只做一件事:根据预设规则,把客户端发来的连接请求,智能分发到后端不同的MySQL实例上。对SpringBoot应用来说,它就像一个“透明网关”——你只需把原来配置里的jdbc:mysql://192.168.1.10:3306/mydb,换成jdbc:mysql://127.0.0.1:6446/mydb(Router监听的读写端口),其他代码一行不用改。Router会自动把写请求(INSERT/UPDATE/DELETE)路由到主库,把读请求(SELECT)负载均衡到多个从库。它不依赖中间件集群,不引入ZooKeeper或Etcd,二进制文件仅12MB,启动即用,是MySQL生态里最“原生”的读写分离方案。
很多人第一反应是:“我用ShardingSphere或MyCat不行吗?”——当然可以,但它们定位是分布式数据库中间件,功能重、配置复杂、学习成本高,且容易成为新的单点故障源。而MySQL Router 8 的设计哲学是“够用就好”:它只做连接路由,不改协议,不增延迟,不引入额外网络跳转(默认直连后端MySQL,无代理转发开销)。实测在Ubuntu 20.04环境下,Router自身CPU占用常年低于0.3%,内存稳定在15MB左右,比一个Java应用的GC线程还轻量。它不是替代MySQL复制,而是让复制拓扑真正“活起来”的最后一块拼图。
提示:MySQL Router 8 要求后端MySQL必须启用Group Replication(MGR)或传统主从复制(Replication),且从库需开启
read_only=ON。它本身不管理复制状态,只信任你配置的拓扑结构。这意味着——配置即契约,错配即故障。这也是为什么本文要从Ubuntu 20.04系统环境开始,一砖一瓦夯实基础。
2. Ubuntu 20.04 环境准备:避开APT源陷阱与GLIBC版本雷区
在Ubuntu 20.04上部署MySQL Router 8,看似简单,实则暗藏两个经典坑:一个是APT官方源里只有MySQL Router 2.x(已废弃),另一个是部分低版本glibc兼容性问题。我曾在一个客户现场踩过这坑——运维同事直接apt install mysql-router,装完发现版本是2.1.8,连--bootstrap参数都不识别,折腾半天才发现源没换。
正确做法是绕过APT,使用MySQL官方提供的.deb包直装。这不是“不走寻常路”,而是MySQL官方明确推荐的方式(见MySQL 8.0 Reference Manual第7.5.2节)。原因很实在:Router 8与MySQL Server 8.0深度绑定,其二进制依赖特定版本的libmysqlclient和SSL库,APT源里的老版本无法保证ABI兼容。
第一步,清理可能存在的旧版本:
sudo apt remove --purge mysql-router* sudo apt autoremove sudo rm -rf /usr/lib/mysqlrouter /etc/mysqlrouter第二步,下载官方deb包。注意必须选对应Ubuntu 20.04(Focal)的amd64包,不要选generic Linux版(那是tar.gz,需手动解压配置):
# 进入临时目录 cd /tmp # 下载MySQL APT配置工具(用于添加官方源,更稳妥) wget https://dev.mysql.com/get/mysql-apt-config_0.8.24-1_all.deb sudo dpkg -i mysql-apt-config_0.8.24-1_all.deb # 运行配置工具,按提示选择"MySQL Server & Cluster" → "mysql-8.0" → "Apply" sudo apt update # 此时才能安全安装Router 8 sudo apt install mysql-router-community如果你坚持用deb包直装(适合离线环境),请确认下载链接有效:
# 官方下载页:https://dev.mysql.com/downloads/router/ # 直链示例(请以官网最新为准): wget https://dev.mysql.com/get/Downloads/MySQL-Router/mysql-router-community_8.0.33-1ubuntu20.04_amd64.deb sudo dpkg -i mysql-router-community_8.0.33-1ubuntu20.04_amd64.deb # 若报依赖错误,补装: sudo apt --fix-broken install关键验证点有三个:
- 版本确认:
mysqlrouter --version必须输出MySQL Router Ver 8.0.xx,而非2.x; - 动态库检查:
ldd $(which mysqlrouter) | grep mysql应显示libmysqlclient.so.21(MySQL 8.0标配); - GLIBC检查:
mysqlrouter --help若报GLIBC_2.32 not found,说明系统glibc过低——Ubuntu 20.04默认是2.31,需升级内核或换用MySQL Router 8.0.32(兼容2.31)。
注意:Ubuntu 20.04的systemd服务文件
/lib/systemd/system/mysqlrouter.service默认配置有缺陷——它把User=mysqlrouter写死了,但该用户并不存在。实操中必须手动创建用户并修正权限,否则systemctl start mysqlrouter会静默失败。这是Router 8.0.30+版本的已知问题,官方文档未明示,但社区issue #10233有详细讨论。
3. MySQL后端拓扑搭建:主从复制不是“配通就行”,而是Router的信任基石
MySQL Router 8 不是万能胶,它不会帮你搭建复制,也不会自动发现从库。它只相信你告诉它的拓扑结构。因此,在配置Router前,必须先确保后端MySQL集群处于可被Router无歧义识别的状态。这里我以最常用的异步主从复制为例(非MGR),因为90%的中小团队落地读写分离都从它起步。
假设你的环境是:
- 主库(Master):192.168.1.10,MySQL 8.0.33,server_id=1
- 从库1(Slave1):192.168.1.11,MySQL 8.0.33,server_id=11
- 从库2(Slave2):192.168.1.12,MySQL 8.0.33,server_id=12
第一步,主库上创建专用复制用户(Router将用此账号连接后端):
-- 登录主库 CREATE USER 'repl_user'@'192.168.1.%' IDENTIFIED BY 'StrongPass!2024'; GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'192.168.1.%'; FLUSH PRIVILEGES;注意:密码必须满足MySQL 8.0默认的validate_password策略(至少8位,含大小写字母、数字、特殊字符),否则CHANGE MASTER TO会报错。
第二步,从库上配置复制(关键!必须执行START SLAVE;并验证状态):
-- 登录Slave1 CHANGE MASTER TO MASTER_HOST='192.168.1.10', MASTER_USER='repl_user', MASTER_PASSWORD='StrongPass!2024', MASTER_PORT=3306, MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=156, GET_MASTER_PUBLIC_KEY=1; START SLAVE; -- 验证 SHOW SLAVE STATUS\G -- 检查以下两项必须为Yes: -- Slave_IO_Running: Yes -- Slave_SQL_Running: Yes第三步,也是最容易被忽略的一步:强制从库只读。Router判断“读节点”的唯一依据就是read_only=ON,而不是IP地址或角色名:
-- 在Slave1和Slave2上分别执行 SET PERSIST read_only=ON; SET PERSIST super_read_only=ON;SET PERSIST会将变量写入mysqld-auto.cnf,确保重启不失效。如果只用SET GLOBAL,服务器重启后从库变可写,Router仍会把SELECT路由过去——这就埋下了数据不一致的炸弹。
此时,你可以用mysql客户端手动测试Router的“信任机制”:
# 在Router所在服务器上 mysql -h 192.168.1.11 -u repl_user -p'StrongPass!2024' -e "SELECT @@read_only;" # 应返回1(true) mysql -h 192.168.1.10 -u repl_user -p'StrongPass!2024' -e "SELECT @@read_only;" # 应返回0(false)Router启动时,会向每个配置的后端节点发送SELECT @@read_only,只把返回0的节点标记为“写节点”,返回1的标记为“读节点”。这个过程不可跳过,也不可伪造。
实操心得:很多团队在压测时发现“读请求跑到主库去了”,排查半天发现是从库的
read_only没生效。原因常是:① 忘了SET PERSIST,重启后失效;② 从库开启了log_bin但没配read_only,导致binlog写入时自动关闭read_only(MySQL 5.7+行为);③ 使用了云数据库RDS,其控制台“只读实例”开关未打开。Router不会替你兜底,它只忠实地执行你的配置。
4. MySQL Router 8 核心配置详解:从bootstrap自动生成到手工精调的完整闭环
MySQL Router 8 最大的便利是--bootstrap一键生成配置,但生产环境绝不能止步于此。自动生成的配置是“能跑”,手工精调后的配置才是“稳跑”。下面我带你走一遍从自动生成到生产就绪的完整路径。
4.1 Bootstrap初始化:三步完成基础骨架
Router的bootstrap本质是:连接一个MySQL节点(通常是主库),自动探测其复制拓扑,生成mysqlrouter.conf。命令如下:
sudo mysqlrouter --bootstrap repl_user@192.168.1.10:3306 \ --user=mysqlrouter \ --directory /var/lib/mysqlrouter \ --name production_router参数解析:
repl_user@192.168.1.10:3306:用于探测拓扑的账号和地址,必须有REPLICATION CLIENT权限;--user=mysqlrouter:指定运行Router的系统用户(稍后创建);--directory:配置和运行时文件存放目录,生产环境必须指定独立路径;--name:Router实例名,影响systemd服务名(如mysqlrouter@production_router.service)。
执行后,Router会输出类似:
Please enter MySQL password for repl_user: ******** # Bootstrapping MySQL Router instance... - Creating directory /var/lib/mysqlrouter... - Creating configuration /var/lib/mysqlrouter/mysqlrouter.conf... - Creating runtime directory /var/lib/mysqlrouter/run... - Creating log directory /var/lib/mysqlrouter/log... - Setting permissions... - Creating service... - Starting service...此时,/var/lib/mysqlrouter/mysqlrouter.conf已生成,核心段落如下:
[DEFAULT] logging_folder = /var/lib/mysqlrouter/log runtime_folder = /var/lib/mysqlrouter/run data_folder = /var/lib/mysqlrouter/data plugin_folder = /usr/lib/mysqlrouter config_folder = /var/lib/mysqlrouter [logger] level = INFO [metadata_cache:bootstrap] router_id = 1 cluster_name = default user = mysql_router1_abc123 metadata_cluster = true ttl = 30 [routing:read_write] bind_address = 127.0.0.1:6446 destinations = metadata-cache://default/?role=PRIMARY mode = read-write protocol = classic [routing:read_only] bind_address = 127.0.0.1:6447 destinations = metadata-cache://default/?role=SECONDARY mode = read-only protocol = classic4.2 手工精调:让配置真正扛住生产流量
自动生成的配置有三大隐患,必须手工修正:
隐患一:绑定地址过于保守
默认bind_address = 127.0.0.1:6446,意味着只有本机能连。但SpringBoot应用很可能部署在另一台服务器上。应改为:
[routing:read_write] bind_address = 0.0.0.0:6446 # 允许任意IP访问 # 同时增加防火墙规则(Ubuntu ufw): sudo ufw allow from 192.168.1.0/24 to any port 6446隐患二:读写端口缺乏健康检查
Router默认不主动探测后端节点健康状态。当从库宕机时,它仍会把读请求轮询过去,导致应用报Connection refused。需启用health_check_interval:
[routing:read_only] bind_address = 0.0.0.0:6447 destinations = 192.168.1.11:3306,192.168.1.12:3306 mode = read-only protocol = classic # 新增健康检查 health_check_interval = 5000 # 每5秒检查一次 health_check_timeout = 1000 # 检查超时1秒隐患三:日志级别太低,故障难定位
默认level = INFO,关键错误如连接拒绝、认证失败不会记录。生产环境必须调至DEBUG,但需限制日志大小:
[logger] level = DEBUG # 添加日志轮转 file = /var/lib/mysqlrouter/log/mysqlrouter.log max_size = 10485760 # 10MB max_count = 5 # 保留5个历史文件最终,一份生产就绪的mysqlrouter.conf关键段落如下:
[DEFAULT] logging_folder = /var/lib/mysqlrouter/log runtime_folder = /var/lib/mysqlrouter/run data_folder = /var/lib/mysqlrouter/data plugin_folder = /usr/lib/mysqlrouter config_folder = /var/lib/mysqlrouter [logger] level = DEBUG file = /var/lib/mysqlrouter/log/mysqlrouter.log max_size = 10485760 max_count = 5 [routing:read_write] bind_address = 0.0.0.0:6446 destinations = 192.168.1.10:3306 mode = read-write protocol = classic # 写节点不需健康检查(主库宕机=全局故障,Router不处理) connect_timeout = 10 [routing:read_only] bind_address = 0.0.0.0:6447 destinations = 192.168.1.11:3306,192.168.1.12:3306 mode = read-only protocol = classic health_check_interval = 5000 health_check_timeout = 1000 # 轮询策略(默认round-robin),也可设为first-available关键经验:Router的
destinations字段支持两种格式——metadata-cache://(依赖元数据服务)和直连IP列表。生产环境强烈推荐后者。因为metadata-cache需要额外部署MySQL InnoDB Cluster或MGR,而直连IP列表简单、可控、无额外依赖。我们曾在线上用直连模式稳定运行23个月,零配置漂移。
5. SpringBoot集成实战:不只是改URL,还要应对连接泄漏与事务穿透
配置好Router只是第一步,SpringBoot应用接入才是真正的考验。很多团队改完URL后,压测时发现连接数暴涨、事务异常、甚至出现“从库写入”错误。这些问题根源不在Router,而在SpringBoot的JDBC连接池和事务管理配置。
5.1 数据源URL改造:读写分离的起点与陷阱
SpringBoot 2.4+ 默认使用HikariCP,application.yml中修改如下:
spring: datasource: url: jdbc:mysql://127.0.0.1:6446/mydb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: app_user password: AppPass!2024 hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000注意三个关键点:
- URL指向Router的写端口(6446),不是读端口(6447)。Router会根据SQL类型自动分流,应用无需区分;
- 必须添加
allowPublicKeyRetrieval=true。Router 8.0.30+默认启用RSA密钥交换,而MySQL 8.0 JDBC驱动要求此参数,否则报Public Key Retrieval is not allowed; useSSL=false仅限内网环境。若跨公网,必须配SSL证书,Router支持ssl-mode=REQUIRED参数。
5.2 连接泄漏的根因与解法
压测时常见现象:HikariCP连接池活跃连接数持续上涨,最终Connection acquisition timeout。日志里大量Connection leak detection triggered。这不是Router的问题,而是SpringBoot的@Transactional注解在读操作上滥用导致的。
典型错误代码:
@Service public class UserService { @Transactional(readOnly = true) // ❌ 错误:即使readOnly=true,Spring仍会获取连接 public List<User> findRecent() { return userMapper.selectRecent(); // SELECT语句 } }@Transactional(readOnly = true)只是告诉Spring“这个事务不提交”,但连接依然从HikariCP获取并持有到方法结束。Router虽把SELECT路由到从库,但连接本身由HikariCP管理,不会自动释放。
正确解法是:对纯读操作,彻底移除@Transactional,改用DataSourceUtils.doGetConnection()手动获取连接(不推荐)或——更优解——配置HikariCP的connection-test-query:
spring: datasource: hikari: # 添加此行,让连接池定期验证连接有效性 connection-test-query: SELECT 1 # 并增大validation-timeout validation-timeout: 3000同时,在代码中确保所有DAO方法都有明确的@Transactional边界,避免方法内嵌套调用导致连接未释放。
5.3 事务穿透:为什么“写后立刻读”会读到旧数据?
这是读写分离最经典的CAP权衡问题。代码如下:
@Transactional public void createUser(User user) { userMapper.insert(user); // 写入主库 User fresh = userMapper.findById(user.getId()); // 立刻读取,期望拿到刚插入的数据 // 但fresh可能为null!因为Router把SELECT路由到从库,而从库有复制延迟 }Router无法解决复制延迟,它只负责路由。解决方案有三层:
- 应用层:对“写后立刻读”场景,强制走写端口。可自定义注解
@ForceWrite,结合AOP在DataSource切换; - Router层:配置
[routing:read_write]的destinations包含所有节点(主+从),但mode=read-write只允许写,不解决读延迟; - 架构层:接受最终一致性,用消息队列异步通知读服务更新缓存。
我们在线上采用的是混合方案:对用户ID类强一致性场景,用SELECT ... FOR UPDATE锁住主库行;对列表类弱一致性场景,接受1-2秒延迟,并在前端加“数据刷新中”提示。
实战教训:某次大促前,测试同学反馈“下单后查不到订单”,排查发现是Router配置了
[routing:read_only]的mode=first-available(优先用第一个从库),而该从库因磁盘IO高导致复制延迟达8秒。我们立即切回mode=round-robin,并加了Prometheus监控mysql_router_health_check_failures_total指标,延迟超过1秒即告警。
6. 故障排查与监控:当Router“静默失联”时,你该看哪5个地方
Router进程崩溃时,往往不报错,只是连接超时。此时别急着重启,按以下顺序排查,90%的问题5分钟内定位:
6.1 检查systemd服务状态(第一眼)
sudo systemctl status mysqlrouter@production_router # 关键看Active: active (running) 和 Main PID # 若显示failed,看日志: sudo journalctl -u mysqlrouter@production_router -n 50 -f常见错误:
Failed at step USER spawning:User=mysqlrouter用户不存在;Permission denied:/var/lib/mysqlrouter目录权限不对(应属mysqlrouter:mysqlrouter);Address already in use:6446端口被占用(sudo lsof -i :6446查进程)。
6.2 验证Router端口监听(网络层)
sudo ss -tlnp | grep ':644' # 应看到: # LISTEN 0 4096 *:6446 *:* users:(("mysqlrouter",pid=1234,fd=15)) # LISTEN 0 4096 *:6447 *:* users:(("mysqlrouter",pid=1234,fd=16))若无输出,说明Router未启动或配置了bind_address=127.0.0.1且你从远程连。
6.3 测试后端节点连通性(Router视角)
Router日志里ERROR级别的关键线索都在/var/lib/mysqlrouter/log/mysqlrouter.log。搜索关键词:
# 查看最近100行错误 sudo tail -100 /var/lib/mysqlrouter/log/mysqlrouter.log | grep -i "error\|fail\|refused" # 典型错误: # [ERROR] [routing:read_only] Failed to connect to 192.168.1.11:3306: Connection refused # [ERROR] [routing:read_write] Authentication failed for user 'repl_user'@'192.168.1.10'此时,用Router的mysql客户端模拟连接:
# Router自带mysql客户端(路径:/usr/bin/mysql) /usr/bin/mysql -h 192.168.1.11 -P 3306 -u repl_user -p'StrongPass!2024' -e "SELECT 1" # 若失败,则是后端MySQL问题(防火墙、账号权限、read_only状态)6.4 检查健康检查结果(逻辑层)
Router每5秒检查一次从库,结果记录在/var/lib/mysqlrouter/data/下的.json文件。查看:
sudo cat /var/lib/mysqlrouter/data/routing-read_only.json | jq '.destinations' # 输出类似: # [ # {"address":"192.168.1.11:3306","status":"UP"}, # {"address":"192.168.1.12:3306","status":"DOWN"} # ] # 若status为DOWN,说明健康检查失败,需查从库网络和read_only状态。6.5 抓包验证SQL路由(终极手段)
当以上都正常,但应用仍连不上,用tcpdump抓Router端口:
sudo tcpdump -i any -nn -s 0 port 6446 -w router.pcap # 然后用Wireshark打开,过滤`mysql`协议,看Router是否把包转发到了正确的后端IP。 # 若Router收包但无发包,说明Router进程僵死;若Router有发包但后端无回包,说明网络或后端问题。经验总结:我们建立了一套“Router健康检查清单”,每天凌晨自动执行:
systemctl is-active mysqlrouter@production_router→ 必须为active;curl -s http://localhost:6446 | head -1→ Router管理端口(需开启[http_server]);mysql -h 127.0.0.1 -P 6447 -u test -ptest -e "SELECT @@hostname"→ 随机读一个从库;mysql -h 127.0.0.1 -P 6446 -u test -ptest -e "INSERT INTO test VALUES(1)"→ 写主库;grep "UP" /var/lib/mysqlrouter/data/routing-read_only.json | wc -l→ 确保至少1个从库UP。 五项全通过才发企业微信“Router健康”。
7. 性能压测与调优:Router不是性能瓶颈,但配置不当会成放大器
很多人以为加了Router会增加延迟,实测数据打消疑虑:在Ubuntu 20.04 + Intel Xeon E5-2680v4环境下,Router 8.0.33的P99延迟增加仅0.12ms(对比直连MySQL),完全可以忽略。真正的性能瓶颈往往出在配置失当。
7.1 基准测试:用sysbench验证Router开销
我们用sysbench 1.0.20对Router进行对比压测(16线程,只读):
# 直连从库(基准) sysbench oltp_read_only --db-driver=mysql --mysql-host=192.168.1.11 --mysql-port=3306 --mysql-user=app_user --mysql-password=AppPass!2024 --mysql-db=mydb --tables=10 --table-size=100000 --threads=16 --time=300 run # 通过Router读(测试组) sysbench oltp_read_only --db-driver=mysql --mysql-host=127.0.0.1 --mysql-port=6447 --mysql-user=app_user --mysql-password=AppPass!2024 --mysql-db=mydb --tables=10 --table-size=100000 --threads=16 --time=300 run结果对比:
| 指标 | 直连从库 | Router 6447 |
|---|---|---|
| TPS | 12,450 | 12,380 |
| 95%延迟(ms) | 1.82 | 1.85 |
| CPU占用(%) | 65% | 66% |
结论:Router引入的开销在误差范围内,可视为零。
7.2 生产调优:三个必须调整的参数
Router默认配置面向通用场景,生产环境需微调:
1.max_connections:防止单点打爆
Router默认不限制连接数,当应用连接池配置过大(如HikariCPmaximum-pool-size=100),Router可能创建过多后端连接。应在mysqlrouter.conf中显式限制:
[routing:read_write] max_connections = 50 # 主库最多50个连接 [routing:read_only] max_connections = 200 # 两个从库,每个最多100连接2.connection_sharing:复用连接降低开销
Router 8.0.28+支持连接共享,避免为每个客户端连接创建独立后端连接:
[routing:read_write] connection_sharing = true [routing:read_only] connection_sharing = true实测在高并发短连接场景(如HTTP API),连接创建耗时下降40%。
3.connect_timeout:快速失败,避免线程阻塞
默认connect_timeout=30秒,太长。应设为:
[routing:read_write] connect_timeout = 5 [routing:read_only] connect_timeout = 5这样当主库宕机时,应用5秒内就能收到Connection refused,而非傻等30秒。
7.3 连接池协同:HikariCP与Router的黄金配比
Router的max_connections和HikariCP的maximum-pool-size必须匹配。我们的经验公式是:
HikariCP maximum-pool-size ≤ Router max_connections × 后端节点数 × 0.7例如:Router配置max_connections=100,后端有1主2从,则HikariCP最大池子应≤100×3×0.7≈210。我们线上设为200,留10%余量。
最后分享一个真实案例:某次版本发布后,TPS从1.2万骤降至3000。监控显示Router CPU 100%,但后端MySQL一切正常。排查发现是HikariCP
maximum-pool-size从50误配成500,Router为每个连接创建后端连接,瞬间打满100个max_connections,新连接排队等待。回滚配置后,TPS 10秒内恢复。这印证了一点:Router不是黑盒,它的每个参数都必须和应用层对齐。
8. 安全加固:Router不是防火墙,但配置错误等于敞开数据库大门
Router本身不提供身份认证,它完全信任后端MySQL的账号体系。因此,安全加固的核心是最小权限原则和网络隔离。
8.1 Router专用账号的最小权限矩阵
Router连接后端MySQL需要两类账号:
- 探测账号(bootstrap用):需
REPLICATION CLIENT和SELECT权限(查performance_schema.replication_group_members); - 应用连接账号(SpringBoot用):只需业务所需权限,绝对禁止
GRANT OPTION、SUPER、FILE等高危权限。
我们制定的权限矩阵如下:
| 账号类型 | Host范围 | 必需权限 | 禁止权限 | 用途 |
|---|---|---|---|---|
router_probe | 192.168.1.% | REPLICATION CLIENT,SELECTonperformance_schema.* | INSERT,UPDATE,DELETE | Bootstrap探测拓扑 |
app_rw | 192.168.1.% | SELECT,INSERT,UPDATE,DELETEonmydb.* | GRANT OPTION,SUPER,FILE,PROCESS | SpringBoot写操作 |
app_ro | 192.168.1.% | SELECTonmydb.* | 所有写权限 | (可选)SpringBoot读操作专用账号 |
创建脚本示例:
-- 创建router_probe账号 CREATE USER 'router_probe'@'192.168.1.%' IDENTIFIED BY 'ProbePass!2024'; GRANT REPLICATION CLIENT ON *.* TO 'router_probe'@'192.168.1.%'; GRANT SELECT ON performance_schema.* TO 'router_probe'@'192.168.1.%'; FLUSH PRIVILEGES; -- 创建app_rw账号(严格限定库表) CREATE USER 'app_rw'@'192.168.1.%' IDENTIFIED BY 'AppRwPass!2024'; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_rw'@'192.168.1.%'; -- 禁用危险权限(MySQL 8.0.16+支持) REVOKE GRANT OPTION, SUPER, FILE, PROCESS ON *.* FROM 'app_rw'@'192.168.1.%'; FLUSH PRIVILEGES;8.2 网络层隔离:ufw防火墙的精准放行
Ubuntu 20.04默认禁用ufw,必须手动配置:
sudo ufw enable # 只允许应用服务器IP访问Router端口 sudo ufw allow from 192.168.1.20 to any port 6446 # SpringBoot服务器 sudo ufw allow from 192.168.1.20 to any port 6447 # 同上 # 禁止Router端口对外暴露 sudo ufw deny 6446 sudo ufw deny 6447 # 允许Router访问后端MySQL(出站) sudo ufw allow out to 1