Atlas中间件实战:MySQL读写分离与分库分表避坑指南 在业务量冲上来之前你是不会想碰数据库中间件的。等到读流量翻倍、主库CPU告警、DBA半夜打电话问你“要不要拆库”的时候你才会发现代码里那套“读写都打同一个连接串”的做法有多脆弱。我第一次正儿八经研究“atlas”这个项目就是在这种被逼到墙角的场景下。当时团队里的老DBA甩过来一个GitHub链接说“先用这个顶着”我点开一看名字是Atlas第一反应是——这不就是那个机器人吗后来才知道我说的是波士顿动力的Atlas他说的却是360开源的那款MySQL数据库中间件。也正是这次误打误撞让我把这套东西从原理到落地完整摸了一遍。今天这篇内容就围绕这款同样叫Atlas的中间件展开聊聊它到底解决什么问题、怎么部署、有什么坑以及2025年的今天它还有没有使用价值。Atlas本质上是一个介于应用和MySQL之间的透明代理层。你只要让应用把数据库地址指向它它就会根据SQL的类型和规则自动把读请求分到从库、写请求分到主库还能做分库分表。对已经上了MySQL但还没打算大改架构的团队来说这套方案非常“轻”不改业务代码不引入分布式事务让DBA在最熟悉的环境里解决问题。适合后端开发、DBA、架构师参考尤其适合那些被读写分离和分表需求折磨又不想一上来就上全套分布式数据库的团队。1. 为什么说Atlas是被低估的读写分离利器1.1 单库扛不住读流量时中间件解决了什么先复盘一下典型的瓶颈场景。业务早期数据量不大一台MySQL实例既负责写入又负责查询连接数控制在几百CPU使用率常年低于20%。这是最舒服的阶段也是团队最容易麻痹的阶段。等到某天活动上线或者某个接口被外部刷量读请求瞬间翻几倍这台实例的CPU直接冲到90%以上慢查询日志里全是本该几十毫秒返回的SQL。此时最直接的方案是加从库把读流量剥离开。但问题来了——代码里所有DAO层都是直连主库的要改读写分离要么引入复杂的ORM配置要么在业务层写一堆丑陋的路由逻辑。很多团队就是在这个节骨眼上开始寻找数据库中间件。Atlas这一类中间件做的事很简单代理所有数据库请求解析SQL语义发现是SELECT就转发给从库是INSERT/UPDATE/DELETE就转发给主库。应用层完全无感知连接串还是那个连接串只是IP端口从MySQL变成了Atlas。这个思路和我后来接触的ShardingSphere很像但Atlas更“薄”它不侵入应用也不要求你改造任何代码纯C实现部署完就是一个独立服务。对于不想动业务代码、又急需缓解主库压力的团队这是性价比最高的过渡方案。1.2 Atlas的前世今生从MySQL-Proxy到360开源Atlas不是从零造出来的轮子。它的前身是MySQL官方推出的MySQL-Proxy一个基于Lua脚本的代理程序。MySQL-Proxy的思路很灵活——允许你用Lua脚本自定义SQL路由逻辑理论上能实现各种骚操作。但灵活的另一面是性能损耗Lua脚本解释执行的效率不高而且每次请求都经过脚本层在高并发下很容易变成新的瓶颈。360在很早期就发现了一个问题MySQL-Proxy在生产环境跑不起来性能根本顶不住。后来他们基于MySQL-Proxy 0.8.2的代码做了一次大手术把Lua脚本能力整体砍掉把读写分离、分库分表这些常用功能写成固定的C逻辑直接编译进代理核心。少了脚本解释层Atlas的性能比原生MySQL-Proxy高了一个量级把转发规则固化成配置也大大降低了使用门槛——不写脚本只改配置运维人员负担小很多。这个设计思路放到今天依然值得学习通用框架确实灵活但固定场景下的专用工具往往更可靠。Atlas就是那个“为读写分离而生”的专用工具它不管复杂的多租户不管数据加密心思全花在SQL解析、路由和连接管理上。虽然360后来基本停止了对外更新但核心代码在GitHub上依然可查社区里也沉淀了大量踩坑记录。1.3 Atlas适合谁用什么时候该换很多读者会在评论区问同一个问题“这项目都停更好几年了还能用吗”我的观点是要看你所在的场景。如果你的团队满足以下条件Atlas依然值得考虑MySQL版本在5.6/5.7这个区间没有使用8.0的新特性业务以OLTP为主SQL类型简单没有复杂的跨库事务团队成员对运维MySQL比较熟不想引入太重的服务网格或数据中间件现阶段的问题是读多写少、从库连不上来、代码改造成本高。反过来如果你已经用了MySQL 8.0或者需要分布式事务、需要全链路数据脱敏、需要kubernetes环境下动态扩缩容那Atlas就不是合适的选项。这种情况下更适合去看ShardingSphere-Proxy、ProxySQL或Vitess这类还在活跃迭代的项目。选型时切忌“手里拿把锤子看什么都像钉子”Atlas只是过渡方案不是银弹。它适合在“架构改造的中间地带”顶上给你争取半年到一年的时间让你从容规划更长期的数据库体系。2. 核心功能拆解读写分离、分库分表与连接管理2.1 读写分离的SQL分发逻辑Atlas的读写分离不是简单判断“SQL语句是不是以SELECT开头”它在内部维护了两组后端地址一组是主库列表backend一组是只读从库列表read-only backend。当一条SQL到达代理层它会做两件事先做SQL语法解析确认语句类型再通过配置里的正则规则做二次判断。比如“SELECT ... FOR UPDATE”这类语句虽然语义是查询但因为涉及行锁必须走主库否则会出现锁竞争甚至数据不一致。我记得老版本的一些中间件在这块处理很糙把FOR UPDATE也分到从库结果导致未提交的数据被读到。Atlas在这点上是靠谱的它会识别这类特殊SQL强制路由到主库。从库之间则用类似LVS的负载均衡算法做分发默认按权重轮询。每个从库在配置里可以设置不同的weight权重高的实例能分到更多的读流量。这个机制非常适合做主从性能不一的场景比如新加的从库配置高、带宽足给它设个大权重老从库设小权重读流量按比例走比平均分配到每个节点更能发挥硬件优势。运维层面你还能在管理端口随时查看每条后端连接的状态手动把一个从库摘掉或加回来整个过程在线完成不需要重启Atlas。2.2 分库分表的路由规则再说分库分表。Atlas支持两种常见的分表规则range和hash。规则是配置在proxy-tables字段里的格式是“表名.分表列:分表方式:分表名列表”。比如一张用户表users要按照用户的id做分表可以配置成users.id:hash:users_0,users_1,users_2,users_3。代理层拿到SQL后解析WHERE条件里的user_id然后计算hash值决定把这条SQL转发到哪张物理子表。hash方式适合key分布比较均匀的场景比如用户ID、订单号这类雪花ID。range方式则按照指定的边界值做分片比如“order.id:range:order_1,order_2,order_3,order_4”每张子表存储一段连续的ID范围。range的好处是范围查询友好劣势是热点问题——订单量大了之后新数据的ID持续增长最后一张表永远是热点。真实场景里我见过有团队用range分表之后前面三张表几乎没数据所有写入都堆在最后一张表折腾半年又把分表规则改成了hash。所以新项目我更推荐直接上hash除非你有非常明确的冷热分区需求。另外要记住一个硬边界Atlas不支持跨子表的join也不支持子查询关联多张分表。设计分表规则时必须保证业务查询都带着分表键否则SQL会被代理层拒绝并返回错误。这也是所有分库分表中间件的通病设计阶段就得想清楚查询模式不能等上线之后才改。2.3 连接管理与在线平滑变更除了读写分离和分表Atlas还承担了连接池的职能。客户端连接到Atlas时Atlas并不立即在后端MySQL建立真实连接而是维护了一个连接池按需创建、复用数据库连接。这样即使前端突然有大量新连接进来也不会瞬间把MySQL的连接数打满。我见过不少团队在没有中间件时遇到连接风暴直接把MySQL的max_connections顶爆数据库hang住整个服务雪崩。接上Atlas之后由代理来控制连接池的大小和回收策略MySQL侧就从容很多。最让我觉得实用的是它的管理接口。Atlas运行时会监听一个独立的admin端口用管理员账号登录后可以执行一系列管理命令。你可以在线查看当前主库、从库的状态比如连接数、健康状态、主从延迟情况也可以执行“SET BACKEND OFFLINE 1”把某个后端节点临时摘掉等它恢复后再执行“SET BACKEND ONLINE 1”加回来。这个过程完全不需要重启代理服务也没有连接中断对整个业务是透明的。对于需要定期切换主从的运维场景这套在线变更能力非常实用比那种改完配置就重启、临时闪断的方案稳妥太多。3. 从零落地安装、配置与验证3.1 部署前的环境准备部署Atlas前你需要把MySQL主从环境先准备好。主库开启binlog从库配置好复制关系确保主从数据一致。这一步最容易踩坑的是binlog格式——如果用的是MySQL 5.7之前的版本务必将binlog格式设为ROW或者MIXED尽量不要用STATEMENT因为基于SQL语句的复制在遇到不确定函数时会导致主从数据不一致。Atlas本身不负责主从同步它只负责把流量按规则分发所以底层主从的可靠性要靠MySQL自身的复制机制保证。Atlas官方发布的安装包主要是RPM包适合CentOS 6/7系列。如果你使用的是较新的操作系统可能需要编译安装但这会引入一些依赖问题。我这里以最常见的RPM包部署为例讲一遍完整流程。你首先要确认服务器上已经安装了libevent和glib2相关的依赖库老版本的RPM包对这两个库的版本要求不严格CentOS 7自带的版本就够用。安装命令很简单rpm -ivh Atlas-2.2.1.el6.x86_64.rpm。装完之后所有可执行文件都在/usr/local/mysql-proxy/bin目录下注意这个路径容易让人误以为装的是MySQL客户端其实它就是Atlas的主目录。3.2 配置读写分离的完整步骤安装完成后Atlas的配置文件在/usr/local/mysql-proxy/conf目录下默认会有一个test.cnf。配置格式和MySQL的my.cnf类似也是由[mysql-proxy]段开头。一个最小可用的读写分离配置长这样[mysql-proxy] # 主库地址可配置多个用逗号分隔 proxy-backend-addresses 192.168.1.10:3306 # 只读从库地址可配置多个用逗号分隔 proxy-read-only-backend-addresses 192.168.1.11:3306,192.168.1.12:3306 # Atlas对外提供的服务地址和端口 proxy-address 0.0.0.0:1234 # 管理接口地址和端口 admin-address 0.0.0.0:2345 # 管理账号 admin-username admin admin-password admin # 应用连接Atlas时的账号和加密后的密码 pwds root:加密后的密码 # 字符集设置 charset utf8这里的密码不是明文而是通过Atlas自带的encrypt工具加密后的字符串。在bin目录下执行./encrypt 你的密码会输出一串加密内容把这段内容填到pwds字段里。很多人一开始会用MySQL的PASSWORD()函数去加密发现连不上其实Atlas用是自定义的加密方式必须用自带的encrypt工具。配置完成后用bin目录下的mysql-proxy命令启动服务注意要指定配置文件mysql-proxy --defaults-file/usr/local/mysql-proxy/conf/test.cnf。启动后先不要急着让业务切换先手动连一下Atlas的端口执行几条SQL验证路由是否正确。我的做法是在从库上执行SET global general_logON然后在客户端连接Atlas执行一条SELECT语句回去看从库的general_log。如果这条SELECT出现在从库的日志里说明路由成功如果出现在主库里说明配置有问题。注意验证完把general_log关掉不然从库日志会暴涨。3.3 配置分库分表的完整步骤读写分离跑通之后再看分库分表。假设业务里有一张订单表orders数据量预计会非常大现在按订单ID做hash分表拆成4张物理表。物理表需要先在MySQL实例里创建好Atlas只负责路由不负责建表。比如你可以在同一个库里建orders_0、orders_1、orders_2、orders_3四张表表结构和原表保持一致。然后在Atlas配置里增加一行proxy-tables orders.id:hash:orders_0,orders_1,orders_2,orders_3保存配置后重启Atlas。注意这里和读写分离的在线变更不同修改代理规则一般是需要重启的。重启后应用端不需要改任何代码不需要改表名只要继续执行INSERT INTO orders(id, ...) VALUES(1, ...)Atlas会在内部解析出id值计算hash后把数据写入对应的物理表。查询也一样如果WHERE条件里带了id代理层就能定位到具体表但如果不带id查询Atlas无法确定去哪张表会直接报错。这是使用分表功能前必须给业务方交代清楚的最重要约束。为了稳妥起见上线前一定要做一轮全量SQL扫描把不带分表键的SQL都找出来。我们当时的笨办法是在MySQL的慢查询日志里开启log_queries_not_using_indexes配合抓包工具记录所有SQL然后脚本过滤出涉及分表但没带分表键的语句。这个工作看起来繁琐但能避免上线当天才暴露问题。3.4 验证与监控部署只是一个开始真正进入稳定阶段后验证和监控的优先级得排到最前面。除了上面提到的general_log验证法还有几个常用手段。一是通过Atlas的管理接口连上admin端口后执行SELECT * FROM backends会列出所有主从节点的状态、连接数、流量统计等信息。二是用MySQL的SHOW PROCESSLIST观察后端连接确认不存在连接泄漏或者事务长时间未提交的情况。三是在Atlas前面再挂一层监控采集器定时拉取管理接口的数据把主从节点的健康状况、QPS、延迟指标上报到监控平台。我个人的习惯是给Atlas所在服务器写一个简单的shell监控脚本每30秒检查一次进程是否存活、端口是否响应如果发现异常就自动切换到备用节点。这个备用节点也部署了一套相同的Atlas实例两个节点共用同一个VIP主节点故障时VIP直接漂移。虽然Atlas本身不提供高可用方案但这种纯外部的HA策略完全够用因为Atlas是无状态的它不持久化任何业务数据状态全部在MySQL端重启后只要配置正确就能立即恢复服务。4. 常见翻车现场与排查技巧4.1 从库延迟引发的“幽灵读”Atlas把读流量分发到从库后最常见的隐患就是主从延迟。如果主库刚写入一条数据业务侧马上发起读取而这条变更还没来得及同步到从库读到的就是旧数据。某些业务场景里这种“幽灵读”不可接受比如支付结果、库存扣减后的实时查询。解决思路有几个把这类强一致性的查询在SQL上打标让它强制走主库Atlas正好支持规则匹配你可以配置特定表或特定SQL模板走主库或者在应用层加一层短暂缓存几秒钟内不查询从库再或者调整MySQL的半同步复制在主库提交事务后等待至少一个从库确认从根源上降低延迟窗口。多数团队会组合使用这三种方案。我自己在排查这类问题时第一步不是看代码而是先看主从延迟时间。用SHOW SLAVE STATUS查看Seconds_Behind_Master如果延迟持续超过几秒说明从库的复制能力有瓶颈可能是磁盘IO、网络带宽或大事务影响了复制进度。这里特别提醒一句如果一个从库长时间延迟它在Atlas的负载均衡里仍然会被轮询到导致部分用户反复读到旧数据。所以监控脚本里一定要加上“延迟超过阈值自动摘除”的逻辑。4.2 A-B切换后连接不释放怎么办Atlas支持在不重启的情况下把某台后端节点摘除但摘除之后已有的连接并不会立刻断开而是等待连接自然超时或业务侧主动关闭。这就带来一个问题如果主库需要切换你以为已经摘掉了旧主库但某些长连接还挂在旧主库上新的写请求却已经路由到新主库两边数据就断开了。我在一次演练中就撞上过这种情况切换完成后查数据发现丢了最近几分钟的更新吓得赶紧比对binlog才发现是长连接作祟。解决方式是在切换前先手动KILL掉所有挂在旧节点上的后端连接。在Atlas的管理端执行KILL命令或者在MySQL侧直接KILL掉来自Atlas代理服务器的线程。更稳妥的方案是先在应用层做一次连接池预热或重启确保业务连接重新建立再执行主从切换。如果你对MySQL操作熟也可以用pt-online-schema-change这类工具辅助切换把影响降到最低。4.3 分页、排序、事务等SQL兼容性坑Atlas在SQL解析上的能力有限不能和完整的数据库语法解析相提并论。分页查询如果涉及多张分表Atlas只能在单表上执行LIMIT不能做全量归并排序。比如你执行SELECT * FROM orders WHERE user_id1 ORDER BY create_time DESC LIMIT 10Atlas能把这条SQL精确路由到user_id对应的一张分表那么LIMIT和ORDER BY都没问题。但如果你不带上分表键想在所有分表里统一排序分页那必然报错或返回错误结果。这个限制必须在需求评审阶段就讲清楚。另一个容易踩的坑是事务。Atlas对事务的支持比较弱如果在一个事务里既有写又有读并且这条读语句没有被强制路由到主库那么读到数据可能和事务里刚写入的数据不一致。实际上Atlas在检测到BEGIN/COMMIT这类事务控制语句时通常会将会话固定到上一次访问的后端节点但这种固定机制依赖于后端连接的状态如果连接池里连接被复用依然可能出现串事务。因此在项目里如果要使用事务务必让事务内所有SQL都走主库或者干脆不在分表场景下使用跨节点事务。4.4 常见问题排查速查表为了帮你快速定位问题我整理了一张排查速查表覆盖我遇到过的典型故障现象可能原因排查手段应用连接Atlas超时Atlas进程未启动、端口未监听检查进程状态telnet Atals端口写SQL没有生效SQL被错误路由到只读从库查看Atlas管理端backends状态检查SQL类型读SQL总是返回旧数据主从延迟过大查看SHOW SLAVE STATUS设置延迟阈值自动摘除分表查询报“找不到表”SQL未带分表键抓取SQL进行分析强制带分表键连接数飙升连接池参数不合理调整max-idle-connections等参数管理接口无法登录admin-ip白名单限制检查admin-address配置和iptables这张表覆盖了大部分常见问题但真实的故障现场往往比表格复杂得多。如果你也遇到过类似诡异情况建议先从最简单的链路开始逐层排查客户端到Atlas、Atlas到MySQL、MySQL内部主从同步一层层打点基本都能找到问题所在。结尾搞了这么多年数据库相关的工作我的体会是中间件这个东西最重要的不是功能多炫而是稳定、可控、容易排查。Atlas虽然老但它把读写分离、连接池、在线管理这些核心能力做到足够扎实帮我熬过了好几个业务快速增长的阶段。如果你正准备给现有MySQL架构做升级不妨先花一个下午把Atlas部署起来拿测试流量验证一下感受一下“应用不改代码、DBA掌控一切”的踏实感。最后再分享一个小技巧把它作为你的数据库网关雏形后续即使迁移到其他中间件应用层的连接池、超时设置、监控体系这些沉淀依然可以复用这才是它真正留给你的长期价值。