
做MySQL运维的朋友迟早会碰到一个绕不开的话题数据库代理Proxy。业务量一旦上来主库写压力高、从库读流量分配不均、主从切换要改一堆应用连接串这些问题会逼着你去找一个能统一收口的中间层。MaxScale就在这种情况下进入我的视野——它是MariaDB官方出品的MySQL/MariaDB代理能帮忙承担读写分离、负载均衡、自动故障转移和查询路由。这篇指南我按一条真实上线路径来写先讲清楚为什么需要它、部署前要想什么再给一套可直接套用的配置接着拆解路由和监控的底层逻辑最后分享上线后踩过的坑以及maxctrl日常巡检方法。不管你是刚接触MySQL的新人还是正在做代理选型的DBA应该都能从里面找到自己需要的那部分。1. 先从选型说起为什么是MaxScale而不是另一套代理1.1 没有代理层之前我经历过的三个痛点最早维护一套单主双从的MySQL集群时我的日常可以用三个词概括改配置、等发布、背锅。业务线直接在配置中心里写下多个从库地址从库扩容时要挨个通知应用方修改连接串某个从库宕机后监控明明红了但应用里的连接池还在向这个死亡地址发起新连接直到超时重试才缓缓反应过来。主从切换更是一场灾难手动把从库提升为主库后还需要在配置中心里改一大圈读写地址期间整个联调环境都处于不可用状态。换句话讲应用层直接面向MySQL裸连接是把架构的脆弱性暴露给了所有上游。我当时的诉求很明确有一个统一入口应用只配一个地址读写路由由入口负责主从切换时入口能自动感知并处理。这个诉求指向的正是数据库代理层。理论上也可以自己在应用里封装一套多数据源路由Java有现成的sharding-jdbc、读写分离插件Go也有各种方案。但问题是公司里不同团队语言不统一、维护成本高一旦有人把事务和读路由的关系搞错线上故障就来了。代理层的价值在于把路由逻辑从业务代码里抽出来收口到基础设施层让应用只关心连一个地址。1.2 MaxScale、ProxySQL、MySQL Router、MyCat的定位差异选型阶段我把市面主流方案都过了一遍。MySQL Router是MySQL官方出品的轻量路由配置简单但功能偏少不太适合做复杂路由和故障转移ProxySQL功能确实很强查询规则、缓存、流量控制都有但配置体系比较重规则写多了之后排查起来累MyCat更偏向分库分表一旦引入就相当于把整个数据访问层都交给它改造量大。相比之下MaxScale走的是“聚焦读写分离和高可用”的路线配置结构清晰和MySQL/MariaDB的复制体系贴合得很紧内置的monitor直接管理主从感知、failover、rejoin不需要额外写脚本。我当时用一个小型压测环境简单对比过下面这张表基本说明了差异方案读写分离自动故障转移配置复杂度分库分表能力我对它的评价MaxScale成熟内置和复制状态联动较低一个conf文件即核心不支持专注路由层读写分离和高可用组合场景最优ProxySQL成熟依赖外部脚本或ProxySQL Admin配置高规则和库表多不支持适合对查询规则有极强定制需求的人MySQL Router基础依赖InnoDB Cluster元数据低不支持轻量场景够用复杂拓扑别指望MyCat有较弱高支持分库分表场景才会考虑如果你和我一样核心痛点就是“读写分离不彻底、主从切换太痛苦”MaxScale是投入产出比最高的一款。它不需要改造业务SQL不需要引入分片键部署形态也足够简单。1.3 什么场景不适合用MaxScale选型教育了我一件事没有万能组件先想清楚它解决不了什么再决定要不要用它。MaxScale解决的是路由和读写分发不解决数据容量问题。如果单库数据量已经到几个T且还在暴涨需要的是拆库拆表MaxScale这个层级帮不上忙它不是分布式数据库中间件不会帮你做分片计算。另外它也不负责修复主从复制本身的故障如果binlog损坏、延迟持续追不上MaxScale能做的只是把那个从库标记为Down或限制它参与路由真正修复复制链路还是得靠DBA自己。还有一个容易被忽略的点MaxScale本身有网络转发成本如果业务对延迟极其敏感每个查询都多一跳代理会产生毫秒级损耗这种场景下需要权衡是否值得。你会看到MaxScale擅长的是把复杂多变的主从拓扑封装成一个稳定入口让应用侧变简单。这正好是业务量上升期团队最需要的。2. 部署前必须做的三件事拓扑、账号与版本2.1 推荐的最小生产拓扑长什么样很多人一上来就装MaxScale结果发现文档里讲了一堆概念反而不知道从哪开始。我建议部署前先在纸上画清楚拓扑。这里给一个最小但完整的生产参考[应用服务] - [VIP: 10.0.0.10] | [MaxScale A] [MaxScale B] - 代理层可做双机 \ / [MySQL Master] [MySQL Slave1] | [MySQL Slave2]如果团队规模不大可以先用一台MaxScale跑起来等稳定了再引入Keepalived或MaxScale自身的多机方案做VIP漂移。关键点是MaxScale不要和MySQL部署在同一个宿主机上否则宿主机宕机时代理和后端数据库一起离开整个入口就彻底没了。2.2 先确认后端主从复制本身是健康的MaxScale的monitor模块很强大但它只负责“观察”复制状态不负责“建立”复制关系。如果你后端的主从复制本身就有问题MaxScale配置得再完美也只是把问题曝光得更明显。在部署MaxScale前我建议先完成这些基本功每台MySQL的server_id全局唯一log_bin开启gtid_modeONMySQL 8建议开启MariaDB 10.11之后也推荐从库的read_only打开复制账号已经建好并确认SHOW REPLICA STATUS里没有报错。只有主从复制链路本身稳定MaxScale基于复制状态做failover才有意义否则它会以为某个节点是健康的结果数据在主从间根本对不上。2.3 MaxScale专用账号的权限设计与创建SQLMaxScale和MySQL之间需要两类账号一类是监控账号monitor模块用它去连接每台后端服务器检测主从状态、计算延迟、判断节点角色另一类是路由账号service在处理客户端连接时会用它去向后端发起真正的数据库连接。实际配置里二者可以共用一个账号但建议分开权限更好控制。下面这套SQL适用于MySQL 8.x和MariaDB直接抄即可CREATE USER maxscale_monitor% IDENTIFIED BY M0nitorPassw0rd; GRANT SELECT ON mysql.user TO maxscale_monitor%; GRANT SELECT ON mysql.db TO maxscale_monitor%; GRANT SELECT ON mysql.tables_priv TO maxscale_monitor%; GRANT SELECT ON mysql.roles_mapping TO maxscale_monitor%; GRANT SHOW DATABASES ON *.* TO maxscale_monitor%; GRANT REPLICATION CLIENT ON *.* TO maxscale_monitor%; GRANT REPLICATION SLAVE ON *.* TO maxscale_monitor%; CREATE USER maxscale_route% IDENTIFIED BY R0utePassw0rd; GRANT SELECT ON *.* TO maxscale_route%; GRANT INSERT ON *.* TO maxscale_route%; GRANT UPDATE ON *.* TO maxscale_route%; GRANT DELETE ON *.* TO maxscale_route%; GRANT SHOW DATABASES ON *.* TO maxscale_route%;不推荐给路由账号配超级权限。REPLICATION CLIENT这个权限很关键MaxScale需要它来读取复制状态如果没有这个权限从库的复制健康度会读不出来节点可能被误判为Down。mysql.user和相关系统表的SELECT权限则用于检查后端账号是否存在、当前账号的权限元数据权限缺失时MaxScale日志里会不断刷“permission denied”的告警。这里要额外提醒一点如果你后端是MySQL 8.0默认认证插件是caching_sha2_password旧版本的MaxScale对它的支持不稳定。稳妥做法是在MySQL 8上建maxscale专用账号时显式指定mysql_native_passwordCREATE USER maxscale_monitor% IDENTIFIED WITH mysql_native_password BY M0nitorPassw0rd;新版MaxScale已经兼容caching_sha2_password但如果你用的是老版本还是建议用这个方式避免连接认证报错。2.4 版本选择与安装方式MaxScale的版本史比较有意思早期是独立版本号6.x、7.x后来跟着MariaDB的节奏切到了23.x、24.x。无论哪个时期6.4都是一个相当稳定的版本线上有一批老集群还在用它新项目我建议直接用24.x配置语法变化不大官方文档也更完整。安装方式上主流操作系统都可以从MariaDB官方源直接安装。RedHat/CentOS系curl -Ls https://rpm.mariadb.com/maxscale/24.2/rhel/9/x86_64/maxscale-24.2.1-1.rhel.9.x86_64.rpm -o maxscale.rpm yum install -y maxscale.rpmDebian/Ubuntu系curl -Ls https://deb.mariadb.com/maxscale/24.2/ubuntu/pool/main/m/maxscale/maxscale-24.2.1-1.ubuntu.22.04.jammy_amd64.deb -o maxscale.deb apt install -y ./maxscale.deb实际上版本号一直在更新上面URL里的具体包名会变化最保险的方式是访问MaxScale官方下载站选择对应系统和架构的rpm或deb包。安装完成后二进制路径通常在/usr/bin/maxscale配置文件在/etc/maxscale.cnf日志在/var/log/maxscale/maxscale.log。如果使用Docker部署注意把/var/lib/maxscale目录持久化不然容器重启后监控数据和管理账号信息会丢失这个坑后面单独展开。3. 从一份最小配置到真正跑通读写分离3.1 先理解MaxScale的四个核心对象刚开始看MaxScale文档容易被listener、service、monitor、server这些词绕晕。我用一个餐厅的类比帮助理解server后厨的灶台对应每台MySQL实例。service配餐规则决定“哪些菜去哪个灶台炒”比如读走A灶台、写走B灶台。listener餐厅门口的接客窗口对应应用连接的IP和端口。monitor巡查员每隔几秒去看每个灶台是否还在正常运转、哪口锅是主灶。一个MaxScale进程可以配置多个service、多个listener、多个monitor但最小可用的配置只需要一套。3.2 最小可用配置文件用vim打开/etc/maxscale.cnf写入下面这段配置[maxscale] threadsauto admin_host127.0.0.1 admin_port8989 admin_usermaxadmin admin_passwordAdmin123 [server1] typeserver address192.168.10.11 port3306 protocolMariaDBBackend [server2] typeserver address192.168.10.12 port3306 protocolMariaDBBackend [MySQL-Monitor] typemonitor modulemariadbmon serversserver1,server2 usermaxscale_monitor passwordM0nitorPassw0rd monitor_interval1000 failover1 auto_rejoin1 [Read-Write-Service] typeservice routerreadwritesplit serversserver1,server2 usermaxscale_route passwordR0utePassw0rd master_accept_readstrue [Read-Write-Listener] typelistener serviceRead-Write-Service protocolMariaDBClient address0.0.0.0 port4006说几个关键字段modulemariadbmon是MaxScale 6.x以后的监控模块名旧文档里mysqlmon已经废弃。monitor_interval1000表示每1秒巡检一次。对普通业务够用对高可用要求更高的场景可以调到500但会增加监控账号的连接压力。failover1开启自动主从切换。首次启动时我建议先设成0手动验证一切正常后再打开避免误判导致自动切库。master_accept_readstrue允许主库参与读路由。主库性能宽裕时开着能分摊读压力如果主库已经是写瓶颈把它设成false所有读尽量走从库。3.3 启动MaxScale并验证读写分离安装完成后注册成系统服务systemctl start maxscale systemctl enable maxscale启动后用maxctrl list servers看节点状态maxctrl list servers正常情况下你会看到类似这样的输出server1的State是Master, Runningserver2的State是Slave, Running。如果显示Down先回头看密码和权限是不是给错了这是90%启动失败的原因。然后从应用视角连一次MaxScale的4006端口mysql -h 192.168.10.10 -P 4006 -uapp_user -p进去后执行SELECT server_id;多开几个会话执行几次你会发现server_id在多个节点间变化说明读流量已经被分发到不同后端。要验证写路由是否走主库可以开一个事务执行SELECT然后看连接始终绑定在同一个节点上。不要指望单条查询就能看到完美的轮流分发MaxScale的后端连接池会影响复现多开几个并发连接体验更明显。3.4 从“最小配置”到“生产配置”缺少的几块拼图最小配置能跑通但离生产可用还差几步。failover1虽然开了但原主库恢复后是否自动重新加入集群靠的是auto_rejoin1。生产上还需要考虑客户端连接上限、从库延迟阈值、大查询隔离等这些在后面章节展开。总之先把链路跑通再逐步调优别一上来就堆满全部参数。4. 路由、连接与故障转移的底层逻辑4.1 SELECT不等于一定走从库这是我见过最多的误解以为配置了读写分离所有SELECT就一定会去从库。实际不是。readwritesplit路由器的判断逻辑是“语句类型 上下文状态”。一个客户端如果开启事务BEGIN或autocommit0事务里的所有语句都会被固定到同一节点避免跨节点读到不一致数据。也就是说事务里第一个语句是SELECT那这个SELECT可能就落在主库上。如果你的应用框架比如某些ORM默认开启事务把大量查询包在事务里结果就是所有读流量全部打在主库从库闲置主库压满。遇到这种情况我先建议去查业务代码里是不是把无关的读操作也塞进了事务。还有几类语句也会被强制发往主库SELECT ... FOR UPDATE涉及存储函数、临时表、GET_LOCK()等有状态操作的语句。另外会话级别变量一旦被SET修改后续语句为了保持一致性也会留在主库。代理能识别语法但无法判断你的存储函数是否“纯读”所以它选择了保守策略。4.2 从库选择策略LEAST_CURRENT_OPERATIONS还是LEAST_ROUTER_CONNECTIONS当多个从库都可用时MaxScale如何挑选目标配置项slave_selection_criteria控制这个逻辑。默认值是LEAST_ROUTER_CONNECTIONS意思是从后端连接池中选当前活跃连接数最少的节点策略偏向“连接均衡”。另一选项是LEAST_CURRENT_OPERATIONS偏向“正在执行的语句数最少”能更快避开瞬时大查询带来的卡顿。我在实践中发现LEAST_CURRENT_OPERATIONS更能反映节点真实繁忙程度因为它统计的是正在执行的SQL操作数而连接数多不代表每个连接都在跑大SQL。配置里加上这一行[Read-Write-Service] typeservice routerreadwritesplit serversserver1,server2 slave_selection_criteriaLEAST_CURRENT_OPERATIONS如果某台从库复制延迟明显高于其他节点可以设置max_slave_replication_lag单位秒超过阈值的从库会被自动移出路由候选池避免读到滞后太久的旧数据。4.3 应用连接池、MaxScale会话、后端连接池三者之间的关系这是连接问题排查中最容易绕晕的地方。连接链路上实际上有三层应用层连接池、MaxScale会话、MaxScale与MySQL之间的后端连接。应用层连接池管理的是“客户端到MaxScale”的连接MaxScale会为每个客户端会话维持一条会话上下文但当多个客户端会话都指向同一个后端节点时MaxScale可以选择让它们共享后端连接这就是它自带的连接复用能力。换句话说应用侧开500个连接后端MySQL不一定真的建500个连接MaxScale会按需复用有效降低MySQL端的连接压力。但这不代表应用侧可以无限开连接。MaxScale的每个客户端连接仍然要消耗文件描述符和内存如果业务侧把maximum-pool-size设成几千单机MaxScale一样会被打穿。建议应用连接池设计遵循两个原则一是压测后确定合理上限而不是随手填一个很大的值二是连接空闲超时要和MaxScale侧的不一致错开避免互相踩踏导致连接提前被回收。4.4 主库宕机时MaxScale到底做了什么主库故障的完整流程值得每个DBA刻在脑子里因为业务感知到的就是“连接闪断了一下”但背后的动作其实很多monitor在下一个巡检周期默认1秒发现主库连接异常或复制状态中断将该节点标记为Down。当failover1时mariadbmon依据master_priority配置或复制拓扑信息从健康从库中选举一个新主。MaxScale将新主标记为Master后续新事务全部路由到新主。已经被故障主库承载的旧连接会中断应用侧需要重试。如果auto_rejoin1原主库恢复后会被配置成新主的从库自动沿着binlog或GTID追数据追平后重新标记为Slave, Running。这个过程中最影响体验的是第4步。应用侧如果没有重试机制一次切换就会造成成批报错。所以在生产环境里我一直强调MaxScale做好故障转移只是前提应用层连接池必须配置短超时快速重试这样用户才能无感知。5. 业务接入阶段最容易翻车的连接问题5.1 应用账号必须在后端每台服务器上同时存在MaxScale本身不存储业务数据它把客户端连接“翻译”到后端MySQL时用的是你这个业务账号在后端执行SQL。也就是说应用连接MaxScale时用的app_user必须已经在server1、server2等所有后端MySQL上都创建好了且权限一致。我曾经在接入阶段遇到一个诡异现象连MaxScale后查询正常但某些页面偶尔报1045 Access denied。最后排查发现新扩容的一台从库上忘了建app_userMaxScale把读请求路由到这台从库时后端拒绝了认证。解决方案很简单把账号创建SQL在所有后端节点都执行一遍或者用配置管理工具统一下发数据库账号避免只在其中一台机器上建。5.2 从库read_only不一致带来的隐患MaxScale通过monitor读取每个节点的角色决定谁是Master谁是Slave。假如某台从库忘了设置read_only1尽管它名义上是Slave但实际上仍然可以写。一旦应用被路由到这个从库执行写入数据就会在主从之间出现分叉且这种分叉不会自动修复最终只能手动重建该从库。所以从库一定要统一加上SET GLOBAL read_only ON; SET GLOBAL super_read_only ON;其中super_read_only在MySQL 8里能防止具有SUPER权限的账号写入保护性更强。这个经验说出来不值钱但生产环境的从库漏配read_only的真实案例多到数不清尤其是大批量初始化从库时脚本少执行了一次。5.3 连接断开的恢复路径应用层重试设计不管MaxScale的故障转移做得多么顺滑主库宕机的那一瞬间旧连接一定是失效的。Java的MySQL Connector/J有一个autoReconnecttrue参数但它只对“连接空闲后重建”有效如果正在执行事务时连接断开这个参数不会救你反而可能让你误以为应用能自动恢复。更可靠的做法是在应用的数据访问层加一层快速重试捕获连接异常后短暂sleep然后重新从连接池获取连接、重发之前失败的语句。重试次数不宜多2到3次即可重试间隔建议100毫秒左右因为MaxScale完成failover通常在1到3秒内如果重试批次太密集反而会叠加成对MaxScale的连接风暴。6. 上线后我们追过的三类MaxScale疑难杂症6.1 一个从库延迟“吃掉”整个读流量有次业务反馈高峰期查询变慢我查MaxScale却看到两个从库都处于Running状态但实际只有一台从库在承担读流量。原因是那台健康的从库发生了严重复制延迟MaxScale按照默认策略虽然没把它剔除可新连接都在蜂拥往另一台从库上挤最终把那个从库也压垮了。排查后我把max_slave_replication_lag5加进了service配置延迟超过5秒的从库自动从路由池摘除。这就好比配餐时只让上菜快的后厨参与出餐慢的灶台先暂停接单。加了这个参数后读流量在多从库间的分配明显均衡了。[Read-Write-Service] typeservice routerreadwritesplit serversserver1,server2,server3 max_slave_replication_lag56.2 认证插件不兼容导致MaxScale连不上MySQL 8新项目搭了一套MySQL 8.0.32把MaxScale和它接好之后日志里持续出现“Unable to authenticate”的报错。一开始以为是密码错反复验证无误后才发现问题出在认证插件上。MySQL 8默认的caching_sha2_password要求连接双方都支持对应的认证流程老版本MaxScale对这种认证的支持并不完整。解决方式有两个方向升级MaxScale到支持caching_sha2_password的新版本或者在不出问题的前提下给MaxScale专用账号指定mysql_native_password。考虑到老集群里的MaxScale版本不好动我当时用的是第二种方式新建账号时显式指定认证插件问题立刻消失。6.3 大查询淹没了某个从库报表团队的几个同事喜欢直接连从库在线跑大查询一遍GROUP BY跑上几分钟直接影响线上读路由。MaxScale本身不会帮你区分“这是报表查询还是业务查询”它能做的就是把路由规则定清楚。我的做法是给报表类场景单独开一条通道拿一台或几台从库单独组成一个新的service监听不同的端口比如4007专供离线查询使用4006端口留给线上业务。这样大查询再猛也只影响报表通道不会拖垮线上读流量。顺便说一句如果你在某个查询前面加/* maxscale route to master */这样的注释MaxScale会识别并把它路由到主库这是一条内置的hint路由适合偶尔需要强制走主库的场景。6.4 Docker部署MaxScale时要注意的目录和数据持久化现在不少团队习惯用Docker起中间件MaxScale也提供了官方镜像。直接docker run虽然能跑起来但有个问题容器销毁后/var/lib/maxscale里的数据会丢包括之前配置产生的监控缓存、管理口令、以及部分持久化状态。结果就是重启后认证状态异常甚至admin账号失效。用Docker部署时一定把配置目录、日志目录、数据目录都挂到宿主机物理路径上docker run -d \ --name maxscale \ -p 4006:4006 \ -p 8989:8989 \ -v /etc/maxscale.cnf:/etc/maxscale.cnf \ -v /var/lib/maxscale:/var/lib/maxscale \ -v /var/log/maxscale:/var/log/maxscale \ mariadb/maxscale:24.2另外容器里通常不会自动启动systemd所以要用docker run的方式托管而不是在容器里执行systemctl start maxscale。这个操作层面的差异容易让第一次用Docker的人卡住半天。7. 日常巡检三板斧maxctrl、REST API与日志7.1 每天上班先看的三个maxctrl命令MaxScale上线后日常巡检不需要天天登到MySQL里看复制状态用maxctrl会更高效。我每天的习惯是先跑三个命令maxctrl list servers maxctrl show services maxctrl list sessionslist servers看每台后端节点的State重点关注有没有节点变成Down以及主从角色是否符合预期。show services看路由服务的整体连接数、路由统计如果连接数比平时高出一截说明可能有应用侧连接泄漏。list sessions列出当前客户端会话遇到问题时要看有没有哪台客户端占着大量会话不释放。这三个命令的输出很短但信息密度极大基本覆盖了“节点健康、路由状态、会话状态”三个关键维度。建议写个小脚本封装成一条命令每天早晨跑一遍。7.2 用REST API把MaxScale接进监控平台MaxScale自带REST API默认监听在admin_port也就是8989端口。公司有统一监控平台的可以直接把MaxScale的指标接进去。简单验证一下API是否可用curl -u maxadmin:Admin123 http://127.0.0.1:8989/v1/servers/返回的JSON里包含每个server的state、connections、replication lag等字段。我一般会重点采集节点状态和复制延迟两个指标一旦state不是期望的角色就告警。对接Prometheus类平台的话还可以用现成的exporter不过直接用REST API拉也一样省去额外组件。7.3 日志里报“replication is broken”时先别急着删server有段时间MaxScale日志里频繁出现“Replica is broken”的告警第一反应是这台从库的复制链路坏了。但我登录MySQL看SHOW REPLICA STATUS复制却是正常的。后来才明白MaxScale的mariadbmon对复制断开的判定条件是多个维度组合包括半同步状态、GTID位置是否持续推进、监控账号读复制状态的权限是否足够某个维度异常就会误报。遇到报错不要急着用maxctrl destroy server把节点移除先按顺序排查监控账号的权限是否完整、后端MySQL的read_only和复制状态是否正常、GTID是否持续更新。如果这些都没问题再把monitor_interval适当调大观察是否还继续误报。我调过一次后日志瞬间安静了。7.4 在测试环境强制演练故障比看十遍文档都管用最后说一个个人习惯我会每隔一段时间在测试环境强制kill掉主库进程观察MaxScale是否在预期时间内完成failover、从库是否自动提升、原主库恢复后是否能重新加入。这套演练做下来maxctrl的常用命令基本就烂熟于心了。真正到生产故障时肌肉记忆比临时翻文档可靠得多。MaxScale的价值只有在“真的出过事”之后才能体会而提前演练就是给自己吃定心丸的最好方式。