1. 为什么我最终选了 Mycat 2:先聊聊踩过的弯路
在正式讲配置步骤之前,我想先说一下自己在做数据库读写分离时是怎么一步步走到 Mycat 2 这个方案上的。这个过程里我尝试过不少看起来更"轻量"的替代思路,但最终都因为维护成本、功能边界或者版本兼容问题放弃了。
最初的想法其实特别朴素:直接用 MySQL 自带的半同步复制,再加一层多数据源路由不就行了吗?但实际操作下来发现,应用层多数据源方案有几个绕不开的痛点:
- 每个 MySQL 客户端连接都要在 Java 代码里显式指定走主库还是走从库,哪怕用了
AbstractRoutingDataSource这样的框架封装,SQL 语句层面的读写判定还是得自己写拦截逻辑(比如判断语句是否以SELECT开头,这条规则在外层嵌套查询、存储过程调用、FOR UPDATE语句等各种场景下很容易出错)。 - 一旦新增从库节点,应用配置和连接池配置都得跟着改,运维的兄弟姐妹天天追着我更新配置。
- 不同微服务模块对数据库的访问方式不统一,有的用 MyBatis,有的用 JPA,权限控制散落各处,排查问题的时候要去好几个项目里翻代码。
后来我去了解了专门做数据库中间件的方案,对比了 ShardingSphere 和 Mycat 2。ShardingSphere 的分片能力确实更完善,但当时我们团队的核心诉求很明确:先解决读写分离和主从切换的基础问题,暂时不需要分库分表。Mycat 2 在这一点上更轻、更直接——部署一个中间件节点,应用连接它就像连一个普通 MySQL 实例,读写分离和主从切换都收敛在中间件层,应用的 JDBC 配置几乎不用动。再加上 Mycat 2 对 MySQL 8.0 的兼容性做得比 Mycat 1.6 好不少,连接池、鉴权、字符集这些细节都更贴近现代 MySQL 的行为习惯。
我在这里给读者一个明确建议:如果你的项目目标是短期内快速落地主从读写分离,且团队成员对 ShardingSphere 不熟悉,那么 Mycat 2 的学习曲线和部署复杂度确实友好得多。团队里需要有人熟悉 Netty 和配置文本的调试方式,因为中间件层的错误排查和应用层不太一样。
另外一个很实际的原因是,Mycat 2 可以直接复用现有的 MySQL 主从复制链路。它自己不负责同步数据,只负责路由和转发。这意味着主从同步的方案可以继续沿用 MySQL 原生复制——无论是ROW格式的 binlog、半同步复制还是 GTID 模式,Mycat 2 都不干预。数据一致性这个最敏感的问题,可以继续信任 MySQL 自己的复制机制来保证。
小结一下我的选型思路:
- 应用层多数据源方案 → 适合模块极少的项目,SQL 判定逻辑简单,但不适合有很多服务实例需要统一治理的场景。
- ShardingSphere → 功能全面,适合整体规划分库分表,但它引入的分片规则抽象层对只有读写分离需求的项目来说显得大。
- Mycat 2 → 轻量、专注,配置文本直观,适合已经决定用 MySQL 主从架构、只差一个统一入口的团队。
既然选定了中间件,下一步就得把手上的 MySQL 环境先理顺:搭建主从并验证同步,因为 Mycat 2 只是路由层,数据同步的根基始终是 MySQL 的复制本身。
2. 先搭建 MySQL 主从:这一步不到位的坑,后面全暴露出来
Mycat 2 本身不复制数据。读写分离的效果完全依赖 MySQL 主从复制是否健康。我在刚开始实验的时候,因为图省事,用了非常简陋的主从配置方式,结果后续调 Mycat 2 的读权重和负载均衡策略时,遇到了一堆"看起来是路由问题、其实是复制延迟问题"的怪现象。
环境规划建议如下(这里给出一套我用过的标准组合):
| 节点角色 | 主机地址 | 端口 | MySQL 版本 | 用途 |
|---|---|---|---|---|
| 主库 master | 192.168.1.10 | 3306 | 8.0.32 | 所有写操作、实时性要求高的读 |
| 从库 slave1 | 192.168.1.11 | 3306 | 8.0.32 | 走 Mycat 的普通读操作 |
| 从库 slave2 | 192.168.1.12 | 3306 | 8.0.32 | 走 Mycat 的普通读操作,可测试多从节点效果 |
| Mycat 2 节点 | 192.168.1.20 | 8066 | - | 中间件层,应用连接入口 |
为什么从库版本尽量和主库保持一致?我在迁移阶段试过主库 8.0、从库 5.7 的组合,caching_sha2_password认证插件会有兼容性告警,某些字符集排序规则的表现也不一致,导致主库写的字符集和从库读到的字符集之间出现细微偏差。后来我统一成 8.0.32 版本,这些问题基本就消失了。
2.1 主库需要调整的核心配置
主库的my.cnf(在 Linux 环境下,路径通常是/etc/my.cnf或/etc/mysql/my.cnf)建议设置这几项:
[mysqld] # 节点唯一ID,主库和从库必须不同 server-id = 1 # 开启binlog日志,读写分离和后续时间点恢复都依赖它 log-bin = mysql-bin # binlog格式,MySQL 8.0默认已经是ROW,不过我习惯显式写上 binlog_format = ROW # 需要记录binlog的数据库,这里是testdb binlog-do-db = testdb # 避免记录MySQL系统库的binlog,减少无意义日志量 binlog-ignore-db = mysql # 半同步复制需要的插件支持,先留着后续启用 gtid_mode = ON enforce_gtid_consistency = ON关于binlog-do-db这个参数,我踩过一个不小的坑:如果业务库很多,在 Mycat 2 的读写分离下,某个库没被binlog-do-db覆盖,应用通过 Mycat 写入该库后,从库上永远看不到数据。这个问题不会在 MySQL 层面报错,Mycat 2 也不会感知,只有在业务反馈"某张表查不到数据"时才会被发现。排查路径绕了一大圈,最后发现是配置只覆盖了部分库。
我的建议是:如果有可能新增业务库,宁可先不配置binlog-do-db和binlog-ignore-db,让所有用户库都进 binlog,然后在从库上用replicate-wild-do-table之类参数控制要复制的表。虽然日志量会大一些,但至少不会出现"漏库"这种隐蔽问题。
2.2 从库配置与复制链路检查
从库的my.cnf配置:
[mysqld] server-id = 2 read_only = 1 # 允许复制线程写入,这一步很关键,read_only不会阻碍SQL线程应用binlog super_read_only = 1 relay-log = relay-bin log_bin = mysql-bin binlog_format = ROW gtid_mode = ON enforce_gtid_consistency = ON配置完成之后,重启主库和从库的 MySQL 服务,然后在主库上创建复制账号:
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'StrongPass123!'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';在从库上执行 CHANGE MASTER 之前,务必确认主库当前 binlog 位置和 GTID 信息。用 GTID 模式会比传统 file+position 方式在链路切换时省心很多,因为它天然记录事务执行的全局位点,即使从库落后较多也能准确接着同步:
CHANGE MASTER TO MASTER_HOST='192.168.1.10', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='StrongPass123!', MASTER_AUTO_POSITION=1; START SLAVE;启动后立刻检查复制状态:
SHOW SLAVE STATUS\G;最需要关注的两个字段是Slave_IO_Running: Yes和Slave_SQL_Running: Yes。如果SQL_Running是 No,查看Last_SQL_Error,绝大多数情况是主库上执行了从库无法重复的操作(比如DROP TABLE一张不存在的表,或者主从字符集不一致导致的中文插入异常)。这时可以用STOP SLAVE; SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; START SLAVE;跳过单条错误,但这只适用于测试环境,生产环境下跳过错误可能导致主从数据永久不一致,必须谨慎。
2.3 写入测试数据并验证延迟
主库上执行:
USE testdb; CREATE TABLE IF NOT EXISTS user_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); INSERT INTO user_info(name) VALUES('alice'),('bob'),('carol');等一两秒后,在从库上查:
SELECT COUNT(*) FROM testdb.user_info;能查到 3 条,说明同步链路已经通了。然后我在主库上连续插入了十万行数据,再在从库上通过SHOW SLAVE STATUS看Seconds_Behind_Master,正常情况下应该很快归零。如果这个值持续走高或者抖动,通常不是 Mycat 2 能解决的——首先要检查从库的磁盘 IO 是不是太慢、有没有其他大查询把 CPU 占满,或者网络带宽是否被备份任务占用了。
顺手记一个排查延迟的经验:不要只看Seconds_Behind_Master这一个指标。它本质上是通过对比系统时间和中继日志时间戳计算出来的,如果主从系统时钟不一致,这个值本身就不可信。更稳的做法是直接对比主库 GTID 和从库Retrieved_Gtid_Set、Executed_Gtid_Set的差值。
-- 从库上执行,查看已获取和已执行的GTID集合差异 SHOW MASTER STATUS; SHOW SLAVE STATUS\G;确保两边的 GTID 前缀一致、从库的Executed_Gtid_Set在持续追赶主库,这才是真正可靠的同步判断方式。MySQL 主从验证到这一步,才算具备给 Mycat 2 使用的资格。
3. 部署 Mycat 2:从下载到能连通的完整流程
Mycat 2 的部署方式和 Mycat 1.6 那一代有本质区别。1.6 是编辑schema.xml、rule.xml、server.xml这些大而全的配置文件;2.0 改成了分目录、分功能的 YAML 风格搭配少量 SQL 文件。初次上手的人如果继续搜索 1.6 的教程来配置 2.0,十个有九个会把文件结构搞混,因为很多旧教程举例太久远了。
我建议在正式部署前先明确版本。我当时用的组合是Mycat 2.0.0 稳定版(基于 Netty 4.x)加上 MySQL 8.0.32,这个组合在 GitHub 的 release 页面上能直接下载。
3.1 环境准备:JDK 版本别搞错
Mycat 2 是 Java 写的,运行环境需要 JDK 8 以上。我强烈建议用 JDK 8u202 或更新版本,因为 JVM 对 TLS 协议的默认支持影响到后续注册中心连接和 MySQL 连接的建立。如果用太老的 JDK,连接加密链路会出现 SSL 握手失败,报错信息还很绕——我记得有次一直报Could not connect to MySQL,查了半天才发现是 JDK 的 TLS 版本太老不支持 MySQL 8.0 的默认加密套件。
验证环境:
java -version输出示例:
openjdk version "1.8.0_202" OpenJDK Runtime Environment (build 1.8.0_202-b08) OpenJDK 64-Bit Server VM (build 25.202-b08, mixed mode)3.2 下载与目录结构
从 Mycat 官方 GitHub Releases 页面下载压缩包后,解压到/opt/mycat2:
wget https://github.com/MyCATApache/Mycat2/releases/download/mycat2-1.20-release/mycat2-1.20-release-jar-with-dependencies.jar mkdir -p /opt/mycat2/lib cp mycat2-1.20-release-jar-with-dependencies.jar /opt/mycat2/libMycat 2 的启动脚本很直接,如果你下载的是完整包,里面会有bin/mycat脚本;如果是纯 jar 包,则需要自己准备启动命令。我习惯用下面这种启动方式:
cd /opt/mycat2 nohup java -Dfile.encoding=UTF-8 -Xms1g -Xmx1g \ -jar lib/mycat2-1.20-release-jar-with-dependencies.jar \ > /opt/mycat2/logs/mycat.log 2>&1 &等 3 到 5 秒后查看日志,确认 Mycat 2 是否正常监听 8066 端口:
tail -f /opt/mycat2/logs/mycat.log netstat -tlnp | grep 8066出现类似下面的日志说明启动成功:
INFO MycatServer start success, listen port 80663.3 Mycat 2 的配置核心:几个关键文件
Mycat 2 把配置拆分成了多文件结构,分布在conf/目录下。其中决定读写分离的关键文件是datasource.yaml(这里在 1.20 版本中文件名是 datasource.yml,但核心内容一致),以及schemas目录下面的schema.testdb.yaml。
我对配置结构的理解是:Mycat 2 用datasource(数据源)+ cluster(集群)+ schema(逻辑库/逻辑表)三层来定义路由。数据源直接指真实的 MySQL 地址,集群定义主从分组和读写策略,schema 定义逻辑库名和应用侧的访问方式。理解这个层次关系,后面所有配置就不是在"背参数",而是按逻辑去组织。
一个典型的数据源配置长这样(此处 YAML 语法以 Mycat 2 常见版本为例,不同小版本字段名略有差异):
datasources: master: url: jdbc:mysql://192.168.1.10:3306/testdb?useUnicode=true&characterEncoding=utf8 username: mycat_user password: MycatPass123! maxRetryCount: 3 slave1: url: jdbc:mysql://192.168.1.11:3306/testdb?useUnicode=true&characterEncoding=utf8 username: mycat_user password: MycatPass123! maxRetryCount: 3 slave2: url: jdbc:mysql://192.168.1.12:3306/testdb?useUnicode=true&characterEncoding=utf8 username: mycat_user password: MycatPass123!注意 MySQL 的账号需要先在主库和所有从库上创建,并授予对应库的读写权限。Mycat 2 中间件本身只是拿着这套账号去连接真实 MySQL。
然后配置集群:
clusters: c0: masters: [master] replicas: [slave1, slave2] heartbeat: type: mysql url: jdbc:mysql://192.168.1.11:3306/testdb这段配置的含义是:集群c0的主数据源是master,从数据源是slave1和slave2。heartbeat用于 Mycat 2 对后端 MySQL 做健康检查。如果主库宕机,Mycat 2 可以根据心跳感知到并触发切换逻辑。
最后是 schema 配置。在 Mycat 2 中,逻辑库和物理库的映射关系往往使用schema.testdb.yaml这类文件。为方便理解,我贴一个精简示例:
schemas: testdb: targetCluster: c0: read: c0 write: c0其中read和write指向同一个集群c0,Mycat 2 会在c0内部分发: 写请求发到masters,读请求在replicas之间做负载均衡。
3.4 配置完成后重启并验证连通
修改配置后,需要重启 Mycat 2 使配置生效:
kill -9 $(pgrep -f mycat2-1.20-release) cd /opt/mycat2 nohup java -Dfile.encoding=UTF-8 -Xms1g -Xmx1g \ -jar lib/mycat2-1.20-release-jar-with-dependencies.jar \ > /opt/mycat2/logs/mycat.log 2>&1 &然后用 MySQL 客户端连接 Mycat 2 的 8066 端口试试:
mysql -h 192.168.1.20 -P 8066 -u root -p提示:Mycat 2 默认的管理用户是
root,实际项目里要立刻改掉默认密码,尤其不要图方便配置成和应用共用的账号。
连接成功后执行:
SHOW DATABASES; USE testdb; SHOW TABLES;如果能看到testdb以及之前创建的user_info表,说明逻辑库映射已经生效。至此,中间件层面已经打通,接下来才轮到读写分离的路由验证。
4. 读写分离在 Mycat 2 里的路由规则与验证方法
读写分离的核心问题只有一个:一条 SQL 进来,Mycat 2 怎么判断它该去主库还是从库?如果这个判断逻辑没弄明白,后面调试任何"读走了主库"还是"写走了从库"的疑问都会摸不着头脑。
4.1 读与写是怎么被判定的
Mycat 2 对 SQL 类型的判断逻辑大致如下:
- 事务中的 SQL:如果事务已经开启(比如执行了
BEGIN或START TRANSACTION),事务内的所有操作都会路由到主库。这是为了保证事务的隔离性和一致性——如果事务里先 INSERT,再 SELECT,SELECT 跑到从库上,而这个从库复制延迟刚好没追上,就会查出旧数据,业务上就出现"刚插入的行读不到"的问题。 - 非事务中的
SELECT:默认路由到从库,参与负载均衡。前提是这条 SELECT 没有特殊标记。 - 非事务中的
INSERT/UPDATE/DELETE:默认路由到主库。 - 带
FOR UPDATE的 SELECT:虽然语法是 SELECT,但有行锁语义,必须去主库,不能因为"它是 SELECT"就送去从库。这一点在 Mycat 2 的实现中是有考虑到的。 - 带
NOW()/CURRENT_TIMESTAMP之类函数的 SELECT:如果对时间一致性要求极高,需要自己控制是否走主库。Mycat 2 不会自动判断函数的确定性,这是默认行为的一个边界。
为了在不需要改 SQL 的情况下强制读主库,Mycat 2 支持在 SQL 中加注释路由提示。注释写法大概是:
/*!mycat:db_type=master*/ SELECT * FROM user_info WHERE id = 1;这个能力在调试阶段用处极大——当你怀疑"明明刚写入的数据为什么查不到"时,加上这个注释让查询强制走主库,如果主库能查到而从库查不到,说明复制延迟;如果主库也查不到,说明是应用逻辑或事务提交的问题,和路由无关。一注释就把问题域切干净了。
4.2 怎么确认 SQL 真的走了我预期的节点
这个问题我一直强调:不要凭感觉,要看 Mycat 2 的日志和 MySQL 侧的证据。三个方法叠加,基本能还原出 SQL 的真实路径。
方法一,看 Mycat 2 日志。在/opt/mycat2/logs/mycat.log里搜 SQL 关键字,查看前后上下文,有时能直接看到路由到的 target 数据源名。
方法二,在 MySQL 侧开启通用日志。分别在主库和从库上执行:
SET GLOBAL general_log = ON; SET GLOBAL general_log_file = '/var/log/mysql/general.log';之后执行一条查询,再分别看主库和从库的 general log 里是否出现了这条 SQL。如果主库的 log 出现,说明它走了主库;如果从库的 log 出现,说明走了从库。这个方法比任何监控面板都直观,但生产环境别开太久——general log 每一条 SQL 都落盘,并发高时会严重影响 MySQL 性能。用几分钟验证完功能就马上关掉。
方法三,在 Mycat 2 里执行EXPLAIN类的 SQL,有时会返回路由目标信息。不同版本输出格式不同,可以试试:
EXPLAIN SELECT * FROM user_info WHERE id = 1;4.3 实测:读写分离的完整验证过程
这里给出一份我在本地方便复现的测试脚本:
- 启动 Mycat 2 后,用 MySQL 客户端连上 8066。
- 执行写操作:
USE testdb; INSERT INTO user_info(name) VALUES ('dave');- 主库上确认数据存在:
USE testdb; SELECT * FROM user_info WHERE name = 'dave';- 从库上确认数据已经同步(等待 1 到 2 秒):
USE testdb; SELECT * FROM user_info WHERE name = 'dave';- 在 Mycat 2 里执行一条 SELECT:
SELECT * FROM user_info WHERE name = 'dave';- 打开主库和从库的 general log,看这条 SELECT 实际落在哪个节点。
实测下来,如果从库当前没有积压复制延迟,SELECT 会走从库;如果从库刚好在复制大事务(比如大量批量 UPDATE),延迟比较高,而 Mycat 2 又没有做读写分离延迟阈值控制,SELECT 也仍然可能被路由到从库。这时业务就会看到数据延迟问题。这个问题的解法不是让中间件自动切主,而是优化复制延迟本身,比如拆分大事务、把批量操作分批提交、增加从库配置等。明白这一点,很多"读写分离踩坑"其实是复制架构层面的问题,可以少走很多弯路。
4.4 多从库负载均衡:权重分配怎么设
Mycat 2 在集群配置里可以通过权重影响从库的选择。测试环境里可以先把两个从库的权重设成一样的,观察请求是否轮询或随机分布;也可以设成 3:1 模拟不均匀负载,感受一下中间件层面的调度效果:
clusters: c0: masters: [master] replicas: [slave1, slave2] # 权重比例示意,具体字段因版本而异 balance: 3:1如果条件允许,可以在从库上用SHOW GLOBAL STATUS LIKE 'Com_select'分别在两个从库上多查几次,对比两个节点的Com_select增长情况,就能看到负载是否按预期分布。合理分配读写负载是读写分离功能落地的主要价值之一,验证这一步绝不能省。
5. 主从同步链路和 Mycat 2 的配合:遇到问题怎么定位
这一节想单独说说 Mycat 2 和主从同步链路的配合边界问题。我在实践中最常见的困惑是:应用报了"数据查不到",到底该怪 Mycat 2 路由错了,还是该怪复制没跟上?有了前面验证经验,大概率能快速定位。但如果主从复制链路本身被破坏,问题就复杂一层。
5.1 主从同步中断时的复制元组问题
MySQL 的主从复制是异步或半异步的,从库应用 binlog 时完全可能遇到各种中断。常见中断原因有这些:
| 故障类型 | 典型报错关键词 | 解决思路 |
|---|---|---|
| 主从 SQL 线程执行失败 | Last_SQL_Error | 查询具体错误码,根据错误手工修复或跳过 |
| 磁盘空间满 | Disk full | 清理 relay log、binlog,扩展磁盘 |
| 网络抖动导致 IO 线程重连 | Got fatal error 1236 | 检查MASTER_AUTO_POSITION,必要时重新拉取 |
| 从库误写数据 | Duplicate entry | 恢复从库一致性,最稳妥做法是重建从库 |
如果从库 SQL 线程中断,所有后续应用写入的数据都不会同步过去。但 Mycat 2 的读写分离此时并不会自动感知从库数据不一致——它只是路由,SQL 发过去之后,从库能不能返回正确结果它管不了。这就会造成最恐怖的一种故障:某些行数据在主库存在,在从库不存在,应用通过 Mycat 2 查询时走从库,得到一个不是最新状态的数据集,而且中间件层面根本不报错。这种情况必须靠监控提前发现:定期轮询SHOW SLAVE STATUS,对Slave_SQL_Running非 YES 的情况配置告警,一旦发现从库坏了,应立即在 Mycat 2 里把这个从库暂时剔除或在从库上重建恢复。
5.2 主从切换:Mycat 2 在主库宕机时做了什么
Mycat 2 通过心跳感知主库节点是否存活。我在测试环境中做过一次模拟演练:直接在主库节点上停掉 MySQL 服务,然后从 Mycat 2 的日志里观察心跳失败和切换尝试的记录。由于我在集群配置里设置了心跳检测,Mycat 2 会标记主数据源不可用,此时如果配置允许,后续的写操作会报错或者触发备选主节点逻辑。这个行为在不同版本的 Mycat 2 中存在差异,有的版本只读配置里被标记为备选主节点时才会提升,因此生产环境中要提前确认版本文档,并做好主从切换的自动化脚本(比如通过 MHA 或 Orchestrator 做 MySQL 层面的 VIP 切换,再通知 Mycat 2 刷新配置)。
如果不想引入额外管理组件,我建议至少做到以下几点:
- 用脚本每 10 秒轮询一次主库的 3306 端口和 MySQL 心跳表。
- 主库不可用时,将原本指向主库的数据源配置切换为某一台数据最新、延迟最小的从库,并提升它为可写。
- 将 Mycat 2 的配置热更新或重启,促使新配置生效。
这个过程有可能会丢失少量未同步到从库的数据,所以生产环境务必开启半同步复制并合理配置rpl_semi_sync_master_timeout,在主库等待从库确认的窗口内尽量减少数据丢失窗口。
5.3 主从延迟对读写分离的影响,以及缓解实战
主从延迟是读写分离架构绕不开的话题。同一个主库在高并发写入压力下,从库的复制线程如果跟不上,Seconds_Behind_Master 就会涨。此时应用读取实时数据(比如用户刚提交的订单),如果请求被 Mycat 2 分发到从库,就可能读到旧数据。
应对延迟现状,我在项目里的做法是:
第一,做延迟阈值感知。在业务代码层面,对关键实时查询(比如用户余额、支付状态)通过前面说的/*!mycat:db_type=master*/强制走主库;对非实时列表查询(如历史订单、报表类查询)正常走从库。这个"关键性分级"的策略比让中间件去做自适应延迟感知要简单可靠得多——中间件并不知道哪条 SQL 涉及的钱不允许延迟,应用开发者最清楚。
第二,控制大事务。一条更新十万行的 SQL,虽然在主库执行得很快,但复制到从库时要按事务应用,期间其它事务也可能排队,延迟被瞬间拉高。我在项目规范里要求所有批量 UPDATE/DELETE 拆分到每批 500 到 1000 条以内,既减小锁粒度,也降低复制延迟波峰。
第三,监控Seconds_Behind_Master并通过 Prometheus 采集历史曲线。一旦延迟超过设定阈值(比如 5 秒),就触发告警,同时把对应从库暂时从 Mycat 2 的读集群中摘除,直到延迟恢复。这个摘除动作可以通过脚本修改 Mycat 2 的 datasource 配置并热更新来实现,虽然 Mycat 2 的管理命令在不同版本里略有差异,但思路是通用的。
5.4 从库数据不一致的修复:如何重建从库而不影响 Mycat 2
当从库已经出现不可逆的损坏(比如误删库、relay log 损坏),最干净的修复方式不是不断跳错,而是重新克隆主库数据。在保持 Mycat 2 配置不变的前提下,可以把出问题的从库节点从 Mycat 2 的集群配置里暂时摘除,然后在从库上重新初始化:
- 在主库上做一次逻辑备份(
mysqldump --single-transaction --set-gtid-purged=ON)或者物理备份(如 Percona XtraBackup)。 - 将备份传输到从库。
- 在从库上恢复数据。
- 清空 relay log 和旧 GTID 信息,重新 CHANGE MASTER 指向主库。
- 启动复制并验证。
整个过程不需要动 Mycat 2 的其它配置,等从库追上主库后,再把它重新加回集群,恢复读流量。这就是配置分层带来的好处:MySQL 复制链路和 Mycat 2 路由层互不干扰,可以独立维护。
6. 从实际项目出发:Mycat 2 的配置优化与长期维护建议
走到这一步,一个能跑通的读写分离已经成型了。但如果要应用到生产环境,还有一些细节不能忽略,我按项目推进的顺序讲一下。
6.1 连接数、线程池与性能参数初调
Mycat 2 本身作为中间件,会占用一定的内存和线程资源。JVM 启动参数里我给-Xms和-Xmx各分配了 1GB,对单机小规模项目足够。如果业务并发高,需要根据连接数做加内存。一个粗略的经验值:每个后端 MySQL 连接约占 1 到 2MB 内存,Mycat 2 需要同时维护前端应用连接和后端 MySQL 连接,两者比例大约 1:1 或 1:0.8。比如 200 个应用连接,后端连接池 160 个左右,JVM 堆内存至少 2GB 起步。
连接池配置能细化的维度包括:初始连接数、最大连接数、最小空闲连接、连接超时时间、空闲检测间隔。通常在datasource.yaml里给每个数据源配置:
datasources: master: minCon: 10 maxCon: 100 slave1: minCon: 10 maxCon: 100 slave2: minCon: 10 maxCon: 100要特别注意:后端连接池的总大小要大于应用侧并发连接数上限。否则应用并发一高,Mycat 2 里排队等后端连接,表现就是接口响应越来越慢,应用侧连接池却显示健康,排查时容易让人误判为应用代码性能问题。
6.2 账号权限与安全管理
Mycat 2 提供独立权限层。我在配置逻辑库访问时只创建一个业务账号,授权仅到testdb逻辑库的 DML/DDL 权限,不开放管理端。生产环境有几个明确的红线:
- 不把 Mycat 2 的管理端口暴露在公网。
- 修改默认管理密码,密码使用独立的强密码,与应用账号密码不同。
- 在 MySQL 主从节点上,给 Mycat 2 使用的连接账号最小化授权。不要把 root 密码放进 Mycat 2 的配置里。
6.3 监控体系与巡检脚本
光搭好架构不算完,后面维护才是大头。我维护的读写分离环境中,至少做了以下三个监控点:
- Mycat 2 本身的存活与端口监听。脚本或监控软件每 30 秒探测 8066 端口,连续两次失败就告警。
- MySQL 主从复制健康度。每 60 秒执行一次
SHOW SLAVE STATUS,检测Slave_IO_Running和Slave_SQL_Running是否都等于 Yes,以及Seconds_Behind_Master是否超过阈值。 - 数据一致性抽检。每周从主库和从库里各取一张大表的部分行做哈希校验,从根上发现隐性不一致。比如对
user_info这张表按主键取前 1000 行,计算CRC32(CONCAT_WS(...)),两边比对。
如果发现不一致,按照时间的先后顺序排查:先看是否有 DDL 语句触发从库重建、是否有 MySQL 升级导致复制行为变化、是否有大量无主键表在复制时出现特殊问题。这类问题的根因往往是设计层面的,不是单纯的 Mycat 2 配置问题。
6.4 灰度切换和回滚预案
所有架构调整都存在风险。我在切换当天准备的预案可以概括为:最小变更、快速回滚、业务降级兜底。
具体操作是:
- 提前用业务账号连接 Mycat 2,执行一轮完整的 CRUD 冒烟测试。
- 在应用侧准备好两套数据库地址配置:一套直连 MySQL 主库(绕过中间件),一套走 Mycat 2。切换失败时可以 5 分钟内切回直连模式。
- 切换窗口选择在业务低峰期,切换后观察至少 30 分钟错误日志和慢查询。
值得一提的还有一个容易被忽略的运维细节:Mycat 2 所在主机的时间要和 MySQL 主从节点保持一致(NTP 同步),否则日志的时间戳、审计的先后顺序、甚至某些超时判断都会产生偏差。这个虽然不影响 SQL 路由正确性,但在故障复盘时会带来很多误导。
7. 最后分享几个我实测后的心得体会
文章到这里,核心内容已经写完了。最后说几句掏心窝的话。
第一个体会,读写分离真正难的不是中间件配置,而是业务对一致性的容忍度分级。同一张表,实时行情数据和历史统计数据的读取要求完全不同。在 Mycat 2 的配置里,其实没有特别优雅的"按 SQL 自动判定实时性"的黑科技,最可控的方法还是应用自己统一管理强制走主库的 SQL 清单。配置一个MasterRouteRequired这样的标注或者常量列表,把需要强一致的查询放进去,比在中间件层面做各种猜测可靠得多。
第二个体会,主从同步是读写分离的地基,而大多数人只盯着中间件。从库磁盘 IO、网络延迟、大事务批处理,每一项对读写分离整体效果的影响都不小于路由策略本身。至少在我的实践里,出现"读写分离后数据库变慢"的案例,查到最后往往是复制延迟导致大量请求重试,而非中间件路由损失了性能。
第三个体会,不要一上来就追求全自动化。Mycat 2 的配置热更新、主从自动切换,这些听起来很美好,但如果团队还没有积累足够的运维经验,反而会在故障时增加判断复杂度。先从半自动做起:监控告警自动化,切换操作先手工脚本化,等每一次切换的流程都理顺了,再尝试加入自动决策逻辑。稳妥永远是生产环境的第一原则。
至此,从 MySQL 主从搭建、Mycat 2 部署配置,到读写分离路由验证、故障定位和长期维护,整条链路已经梳理完毕。这篇文章里的步骤和坑都是我自己动手跑过的,如果你也在做同类架构,希望它能帮你省掉几次通宵排障的时间。