MySQL读写压力大?Atlas中间件实现读写分离与分表实战
发布时间:2026/10/19 3:27:16 锦皓数字建站

我用一个比较偏的场景开头吧。早年我维护过一套电商系统的MySQL主库每天凌晨的订单报表一跑主库CPU直接飙到80%从库在一旁闲着。后来我引入了Atlas中间件把读写流量在主从之间做了分离主库负载一下子降到了30%以下整个系统才算是稳住了。如果你也在为MySQL读写压力大、应用层连库信息散落各处而头疼这篇文章可以花十分钟看完。Atlas是奇虎360开源的一款MySQL数据库中间件基于MySQL-Proxy 0.8.2二次开发核心解决了三个问题读写分离、分表、IP白名单过滤。它适合MySQL体量在千万级以内、暂时不想引入分布式数据库、只需要快速做读写分离和简单分表的团队。我下面写的内容全部是我在真实环境里部署、压测、踩坑后的总结不是搬运文档。1. 项目背景与整体方案选型1.1 为什么需要数据库中间件先说一个很现实的场景。业务刚起步时一个MySQL实例撑起所有读写没问题。但到用户量上来之后数据库的瓶颈会先于应用代码暴露出来。通常第一个瓶颈就是连接数应用实例一多每个实例都维护几十个数据库连接池MySQL默认的151个连接很快就打满了。第二个瓶颈是读流量一个典型的互联网业务读写比例大概在7比3甚至9比1也就是说大量查询请求压在同一台实例上而写请求反而不是主要矛盾。这时候最简单的方案是搭主从复制让从库分担读压力。但问题来了应用层的连接是写死的你怎么让它知道“写走主库、读走从库”改代码那得每个服务都改一遍而且后面主从切换时又要改一遍维护成本很高。数据库中间件就是在这个环节插进来的它挡在应用和数据库之间应用只连接中间件中间件内部根据SQL类型自动路由到主库或从库对应用透明。1.2 为什么选Atlas而不是其他中间件市面上做读写分离的中间件不少我当年在选型时对比过Mycat、ProxySQL还有Atlas。Atlas的优势在于轻量和稳定它继承了MySQL-Proxy的Lua脚本架构核心功能用Lua实现性能开销小。360内部大规模使用过属于经过生产环境验证的产物。Mycat功能更全支持分布式事务、全局序列号、跨库Join但配置复杂度也高对于简单读写分离场景来说有点杀鸡用牛刀而且它的SQL兼容性偶尔会出问题。ProxySQL功能强大支持查询缓存、流量重放、丰富的管理接口但配置文件多上手曲线陡。Atlas则简单直接部署完之后一个配置文件就搞定读写分离规则内建在Lua脚本中不需要额外写规则文件适合我们这种“想快速解决主库压力”的团队。另外一个很实际的原因是Atlas支持分表。它可以在中间件层做简单的分表路由比如按照用户ID取模把一张逻辑表映射到多张物理表。虽然分表能力不如专业的分布式数据库但对于单表数据量过千万、已有分表预规划的场景Atlas足够用。这个决定性的优势让我当时定了Atlas。2. 环境准备与核心配置详解2.1 部署架构与端口规划我在生产环境中的部署架构是这样的两台MySQL一主一从做数据层Atlas部署在一台独立的代理服务器上前端应用统一连接Atlas的代理端口Atlas后端分别连接主库和从库。这里要说明Atlas本身是无状态服务理论上可以部署多台前端用负载均衡去接形成一个代理集群。但如果只是中小规模一台Atlas就够用了配置得当的话它自己不会成为瓶颈。我当时的业务峰值QPS在3000左右Atlas所在机器是4核8GCPU利用率最高也就40%。Atlas监听两个端口一个代理端口用于应用连接我用默认的1234一个管理端口用于管理员执行维护操作我用2345。这两个端口要分开避免管理操作暴露给应用侧。MySQL主从复制这边需要提前把主从搭建好。需要注意的一点是Atlas本身不负责做主从复制它只做读写流量的分发。所以主从复制的健康状态需要DBA另行监控如果主从延迟太大读请求可能读到过期数据这个坑后面细说。2.2 下载安装与配置项解读Atlas的安装包在GitHub上可以找到基本是二进制包。以我当时用的版本为例下载后解压到/usr/local/mysql-proxy目录目录结构很简洁bin、conf、libLib目录下就是Lua脚本和依赖库核心逻辑都在这里。配置文件的默认路径是/etc/mysql-atlas-proxy.cnf格式是INI风格我必须说这个文件里每一个参数都值得细看很多小坑都出在这里。[mysql-proxy] # 代理端口和管理端口 proxy-address 0.0.0.0:1234 admin-address 0.0.0.0:2345 # 后端主库和从库 proxy-backend-addresses 192.168.1.10:3306 proxy-read-only-backend-addresses 192.168.1.11:3306,192.168.1.12:3306 # 管理账号 admin-username admin admin-password your_admin_password # 代理账号 proxy-backend-username proxy_user proxy-backend-password your_proxy_password # 主从延迟阈值单位是秒 proxy-max-conns 200 proxy-worker-threads 8 # 日志 log-path /usr/local/mysql-proxy/log log-level info注意proxy-backend-addresses配置的是主库proxy-read-only-backend-addresses配置的是一组从库多个从库用逗号分隔。Atlas会在多个从库之间做负载均衡每个从库收到的读请求量基本是平均的。这里还有一个容易被忽略的参数proxy-max-conns。它限制的是Atlas到后端数据库的最大连接数而不是应用连接到Atlas的连接数。这个值配小了高并发时会连接超时配大了Atlas可能会占用过多数据库连接数。我当时按应用实例数 × 每个实例连接池大小估算再加上20%冗余设成200比较合适。2.3 启动管理与基础验证启动命令很简单/usr/local/mysql-proxy/bin/mysql-proxy --defaults-file/etc/mysql-atlas-proxy.cnf 启动后先看日志确认没有任何报错。日志默认输出在log-path目录下如果看到proxy-plugin.c:1949 ... started之类的记录基本就是启动成功了。接下来用MySQL客户端验证代理端口是否工作正常mysql -h 127.0.0.1 -P 1234 -u proxy_user -p这个连接走的是Atlas的代理端口。进去之后执行select version;看到的应该是后端MySQL的版本号说明链路已经通了。再验证管理端口mysql -h 127.0.0.1 -P 2345 -u admin -p管理端口相当于一个特殊的MySQL协议入口进去之后能执行Atlas专用的管理命令比如查看后端状态SELECT * FROM backends;正常的话返回结果里会看到主库和从库的状态状态字段为up表示在线。3. 核心功能实操读写分离与分表实战3.1 读写分离功能落地读写分离是Atlas最核心的能力它本质上做的事情就是“判读SQL类型然后路由”。SELECT、SHOW、DESC这类读语句路由到从库INSERT、UPDATE、DELETE这类写语句路由到主库。事务内的读写都固定在主库上避免主从延迟导致事务内读到不一致的数据。这个判定在哪里做的Atlas内部有一个Lua脚本层它在后端数据库连接上拦截客户端发过来的SQL用正则和语法特征分析SQL前缀^select、^show、^desc、^explain等都会被归类为读请求。如果SQL被包在START TRANSACTION和COMMIT之间那么事务内的所有SQL无论读写都走主库。我当年部署完成后直接拉了一台测试应用连上Atlas同时在后端主库和从库上分别用SHOW PROCESSLIST观察连接来源。跑一轮业务后从库的连接数明显多于主库说明读流量已经被分流过去了。再用SET autocommit0; SELECT ...; UPDATE ...; COMMIT;这样的事务在主库的processlist里能看到全部SQL说明事务内的请求没有走错库。这里有一个非常容易踩的坑DDL操作比如ALTER TABLEAtlas默认会把它当作写操作路由到主库这个没问题。但有些ORM框架在启动时会执行SHOW FULL TABLES或SELECT session.tx_read_only这类探查语句它们被识别为读请求后路由到从库而某些低版本MySQL的从库在复制未完成时对tx_read_only的返回可能和主库不一致应用启动就会报错。解决办法是在应用层面设置连接初始化脚本或者直接在Atlas的Lua脚本里对特定语句做强制主库路由这个后面会讲到。3.2 分表功能的实现方式Atlas的分表功能说实话没有完整的水平扩展能力它只能做“表名映射”也就是在前端建立一张逻辑表对应后端至少两张物理表路由规则是固定的。以我当时的用户订单表为例订单表t_order数据量预估会上亿提前规划分为16张物理表t_order_0到t_order_15。分表规则配置在Atlas的Lua脚本里。我们修改/usr/local/mysql-proxy/lib/mysql-proxy/lua/atlas/proxy/partition.lua文件在分表配置部分加入function partition.partition_function(key, partitions) local hash tonumber(key) % partitions return hash end同时要告诉Atlas哪张表是分表的以及分表键是什么。Atlas支持按第一个条件的整型值取模比如SELECT * FROM t_order WHERE order_id 10086中间件会计算10086 % 16 6然后把SQL改写成SELECT * FROM t_order_6 WHERE order_id 10086发送给后端。这里要强调Atlas分表只支持等值查询不支持范围查询、IN查询或JOIN。因为中间件无法在不扫描所有物理表的情况下正确地处理这些语句。所以你业务里的分表查询必须都是“按分表键等值查询”的否则只能走全表扫描所有分片。我见过有团队引入了Atlas分表后发现IN查询返回结果缺失查了很久才发现是这个限制。另外Atlas的分表是在主库和从库上同时映射的。比如后端主库和从库上都需要创建t_order_0到t_order_15这16张物理表Atlas会根据逻辑表名和分表规则把读和写都路由到对应的物理表。物理表的创建建议用脚本批量生成一张张手动CREATE太容易漏。3.3 管理端口实用命令管理端口是Atlas的运维窗口我实际用得最频繁的命令有四个SELECT * FROM backends;查看后端数据库连接状态包括主库和从库注意看state字段是否为up。SET OFFLINE $backend_id;把一个后端节点摘除比如要重启MySQL进行维护时先执行这条让Atlas不再把新请求路由给它。SET ONLINE $backend_id;维护完了再把节点加回来。SELECT * FROM clients;查看当前连接到Atlas的客户端IP和连接数排查连接数打满时用。这里有一个平滑上下线的技巧。正常流程应该是先通过管理口SET OFFLINE摘除节点注意这个操作不会断开已建立的连接只影响新连接。所以已经建立的长连接还会继续往这台数据库上发请求需要等一段时间让连接池中的连接自然老化或者手动在应用侧重启连接再对数据库做维护。我自己踩过一次坑在没有把从库SET OFFLINE的情况下直接重启了从库MySQL结果Atlas还在持续把读请求发过去应用侧瞬间报了一大堆“Could not connect to MySQL server”。后来我养成了习惯所有数据库实例的维护操作都先走Atlas管理口摘除再操作数据库本体。4. 常见问题与排查技巧实录4.1 问题速查表为了让大家少走弯路我把自己和身边同事在Atlas使用过程中遇到的典型问题整理成了表格方便对照排查。问题现象可能原因解决方法应用连Atlas报Access denied代理账号密码错误或账号不存在检查proxy-backend-password配置和后端MySQL中proxy_user%账号权限连接正常但SQL执行缓慢请求被路由到了延迟较大的从库检查主从延迟关闭延迟超过阈值的从库或临时SET OFFLINE从库CPU不均多个从库之间负载不均衡检查是否有大量长事务固定在某个从库连接上或按proxy-worker-threads调优事务内读取到不一致数据主从复制延迟事务本应走主库但走了从库确认事务SQL是否被正确识别必要时在Lua中调整事务识别逻辑启动Atlas失败日志提示bind: Address already in use端口被占用用netstat -lnp分表查询结果缺失查询条件不是分表键或SQL不被分表规则支持修改业务SQL确保所有分表查询都带分表键等值条件4.2 高频问题详细排查第一个高频问题是主从延迟。Atlas本身没有检测主从延迟的内置机制它不知道从库的数据落后主库多少秒。如果业务对数据一致性要求高比如“用户下单后立即查询订单详情”这个查询一旦被路由到从库而主从复制还没把那条订单记录同步过来用户会看到“订单不存在”这就很尴尬了。我的处理方式是在应用层做补偿写了一个简单的“写后读”策略用户在完成下单后应用会在Redis里写一个标记比如order:read_after_write:10086 1设置2秒过期。查询订单详情的接口先检查这个标记如果存在就直接走一个专门的数据库账号直连主库查询否则才走Atlas代理。这个方案的实现成本不高但能彻底解决主从延迟带来的写后读一致性问题。第二个高频问题是连接数打满。Atlas的连接数虽然是基于MySQL的线程池模型做的但管理和维护的连接还是有限的。当应用侧连接池把连接扩展到最大而后端数据库的max_connections又不够大时Atlas作为中间层会出现连接等待。此时的表现是应用偶发连接超时而数据库侧自带的连接数并没有打满因为Atlas连接池与数据库之间是复用连接的。排查方法是用管理口看clients表确认是不是某个服务的连接数异常膨胀。如果是检查这个服务的数据库连接池配置把maximum-pool-size调小或者排查是否有连接泄漏。有一个小技巧在Atlas所在的机器上用show processlist连到后端数据库看来自Atlas的线程数量是否持续增长。如果持续增长多半是有SQL在执行时卡住了连接一直没有释放优先排查慢SQL。4.3 一个容易忽略的坑字符集与排序规则Atlas在转发SQL时默认使用后端MySQL的字符集设置。如果你在应用里显式执行了SET NAMES utf8mb4Atlas会把这个会话级配置透传给后端这个没问题。但如果应用没有执行这句而后端数据库默认字符集是latin1恰好你存储的数据是表情符号emoji那么经过Atlas转发后数据可能变成乱码或Incorrect string value错误。解决办法是在后端MySQL的my.cnf里统一把character-set-server和collation-server设为utf8mb4_unicode_ci同时Atlas后端连接账号的默认字符集也要确认是utf8mb4。在Atlas的Lua脚本中可以加一句话强制连接使用utf8mb4proxy.global.backend_default_charset utf8mb4这个配置在atlas/proxy/backend.lua里可以找到修改后重启Atlas字符集问题就没有了。5. 性能调优与高可用扩展5.1 参数调优实践Atlas性能调优核心是调整proxy-worker-threads和proxy-max-conns这两个参数。proxy-worker-threads控制的是Atlas内部处理网络事件的工作线程数。默认值我没记错的话是4在4核机器上可以调到8。这个值不是越大越好它实际上起到了一个事件循环分发的效果线程数超过CPU核数时反而会因为上下文切换增加性能损耗。我当时的机器是4核实测8线程时QPS最高16线程时CPU利用率上升但QPS没有明显变化所以最终还是稳定在8。proxy-max-conns的设置原则前面说过不能盲目调大。它影响的是Atlas向后端数据库建立连接的最大数量如果设得比数据库的max_connections还大高并发下后端数据库会先扛不住报Too many connections。通常设为应用侧可能需要的最大连接数乘以1.2倍且不超过后端数据库max_connections的60%留出余量给DBA直接连库做运维操作。还要注意一个细节Atlas所在机器的文件描述符限制。Linux默认的ulimit -n是1024而Atlas在高并发下打开的socket数会远超这个值导致连接建立失败。需要在/etc/security/limits.conf里给运行Atlas的用户调高限制mysql-proxy soft nofile 65535 mysql-proxy hard nofile 65535改完记得重新登录或重启进程ulimit -n才会生效。5.2 高可用与容灾拓扑单台Atlas如果宕机所有数据库访问都会中断所以生产环境通常要部署两台Atlas前面挂一个LVS或HAProxy做流量入口。我当时的方案是HAProxy 双Atlas Keepalived具体拓扑是HAProxy监听一个虚拟IP后端挂两台AtlasKeepalived负责虚拟IP在主备HAProxy之间漂移。任何一台Atlas挂掉HAProxy会自动把流量切换到另一台应用无感知。这里要特别提醒Atlas本身不提供主从自动切换的能力。也就是说如果主库MySQL宕机Atlas检测不到应用写请求会被发到一个不可用的主库上直接报错。所以主库HA需要在MySQL层面做比如用MHAMaster High Availability或Orchestrator把VIP自动切换到新主库。Atlas配置的后端地址如果写的是VIP那么MySQL主从切换对Atlas就是透明的。我在实际运维中发现Atlas对后端节点状态的感知有延迟默认心跳检测周期比较长故障发生后要等几秒才会把节点标记为down。这个时间窗口内的请求会失败所以应用侧的数据库连接池必须要有重试机制手工操作是补不过来的。5.3 监控体系建设中间件一旦上线就必须纳入监控体系我用的方案是Prometheus mysqld_exporter 自定义脚本。mysqld_exporter本身是监控MySQL的但Atlas的管理端口也实现了MySQL协议所以可以用一个特殊的账号连上Atlas管理口把SELECT * FROM backends的返回结果抓出来暴露给Prometheus再在Grafana里画一个面板展示每个后端节点是up还是down。我写了一个简单的shell脚本每分钟执行一次#!/bin/bash res$(mysql -h 127.0.0.1 -P 2345 -u admin -padmin_pwd -e SELECT state FROM backends 2/dev/null) if echo $res | grep -q down; then echo Atlas backend down | mail -s Atlas Alert opsexample.com fi当然这是最小实现实际生产环境里建议直接用Python加Prometheus client库封装一套 exporter配合Alertmanager做告警路由比邮箱发邮件更及时。监控的意义不只是告警还能通过历史趋势判断Atlas是否需要扩容比如持续观察proxy_max_conns的使用率如果长期超过80%就该考虑加机器或调大连接数了。6. 写在最后的两个实战补充6.1 关于“Atlas”这个名称的澄清写到这里顺便提一句。搜索“Atlas”时网上大量信息指向华为Atlas AI计算产品还有Atlas部署YOLO之类的教程那是另一个完全不同的生态和本文讲的MySQL中间件没有关系。如果你是为了部署深度学习模型来搜Atlas那请移步AI推理部署的专题如果你和我一样是为了解决MySQL读写压力那本文这套流程可以直接照搬。6.2 个人经验总结我在生产环境用Atlas跑了两年多大的故障遇到过两次一次是主库磁盘满导致写失败另一次是某条慢SQL把从库拖垮。这两个问题其实都和中间件本身无关更多是后端数据库的运维问题。但有了Atlas之后处理这两次故障比没有中间件时从容得多因为可以随时通过管理口摘除故障节点把流量切到健康的库上。最后再分享一个小技巧对Atlas进行版本升级前一定要先在一台不承载线上流量的测试机上把旧的排障脚本和管理命令全部跑一遍。Atlas的Lua脚本、管理命令在版本迭代之间偶尔会有行为变化比如某个管理命令的输出列名变了。提前测一遍能避免升级当天手忙脚乱。我自己就因为在升级后没有重跑排障脚本导致故障时执行了一个旧版的管理命令耗了十分钟才定位到问题。十分钟的故障窗口在线上业务高峰期已经够用户投诉好几轮了。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。