先交代一下背景。我最早接触Atlas,是因为一个从上到下都是读多写少的电商业务:订单表、商品表、用户表,主库既要扛写又要扛读,一到活动大促,主库CPU先飙到80%,慢查询日志里全是几秒的SELECT。当时团队里没有专职DBA,只能靠应用层硬扛,代码里到处是“这条查询走主库、那条查询走从库”的拙劣判断。后来切换到Atlas,整套读写分离逻辑终于从业务代码里剥了出去,应用只需要连一个地址,剩下的事情全部交给中间件。
Atlas是美团开源的一款数据库中间件,底层脱胎于MySQL-Proxy 0.8.2,但它解决的实际问题比原版MySQL-Proxy多得多:读写分离、从库负载均衡、连接管理、分表、平滑上下线、SQL黑名单。最关键的一点是它对应用层完全透明,客户端连的还是原生MySQL协议,不用改代码、不用换SDK,这在老项目里是个巨大的优势。如果你正在维护一套MySQL主从架构,应用里为了读写分离写了不少if-else,或者DBA每次做机器切换都要凌晨发文通知业务方,那这篇文章应该对你有用。
1. 项目背景与核心思路拆解
1.1 为什么需要Atlas这类数据库中间件
要理解Atlas的价值,得先回到一个非常普遍的场景:业务量涨上来了,主库扛不住了,于是DBA拉起一主两从,开始做读写分离。听起来不难,但真正落地的时候会遇到一连串问题。
首先是应用层怎么感知主从。早期团队通常的做法是在代码里配置多个数据源,根据SQL类型手动路由,或者依赖ORM框架的读写分离能力。这种做法在小规模下能用,一旦SQL种类多起来,就会变成一场灾难:事务里的读操作走了从库导致数据不一致、报表统计查询把从库拖垮、漏掉某条慢SQL的隔离导致主库被拖下水。更麻烦的是,如果哪天主库宕机需要切主,所有配置了多个数据源的应用都要跟着改。
Atlas的思路是从应用和数据库之间插一层透明代理,让应用只认识一个逻辑地址,代理在背后决定这条SQL该发给谁。这个思路和很多数据库中间件是一样的,但Atlas做得比较轻,它是用C写的,走MySQL协议转发,没有引入一堆复杂概念,部署起来就是解压、改配置、启动,适合中小团队快速落地。
1.2 它和自研代理、其他中间件的差别
有人会问,既然要上中间件,为什么不直接用ShardingSphere、MyCat,或者干脆自己基于MySQL-Proxy二次开发?
先说自己写代理,这个坑我算是替大家踩过了。MySQL协议看着简单,真正做起来光是握手认证、字符集协商、事务状态同步、大包拆包这几个点就够折腾几个星期。更麻烦的是异常场景,比如后端连接断掉、主库切换期间连接怎么处理、半关闭状态怎么处理,这些没有长时间的线上打磨根本扛不住。所以普通团队的第一原则应该是:尽量不要自研数据链路中间件,除非你有专门的Infra团队。
再说和MyCat、ShardingSphere的区别。ShardingSphere走的是客户端SDK或者独立Proxy的路线,功能非常丰富,分库分表、分布式事务、数据脱敏都有,但这也意味着它有一定学习成本。MyCat同样功能强大,但配置复杂度也更明显。Atlas的定位恰好是“够用就好”:主要解决读写分离、负载均衡、小规模分表和运维便利性,没有那么多花哨功能。如果你的核心诉求就是读多写少场景下解放后端开发的双手,Atlas的性价比很高。
另外有一点不可忽视:Atlas是基于MySQL-Proxy改的,它继承了一个MySQL-Proxy的传统问题,就是对复杂SQL的解析能力有限,很多分表规则也偏简单。但理解它的边界很重要,知道它能干什么、不能干什么,才不会在项目里用错地方。
1.3 什么样的团队适合引入Atlas
结合我的使用经验,适合引入Atlas的团队大概有这么几个特征:一是正在维护MySQL主从架构,但分库分表需求还不强烈;二是应用层读写分离改造成本高,希望用一个透明代理解决问题;三是团队里没有专门的基础设施开发人员,需要开箱即用的工具;四是对性能和功能的要求不是极致,可以接受中间层带来的一跳网络开销。
反过来,如果你的业务已经到了一眼就能看出需要水平分库的规模,或者需要跨机房容灾、分布式事务等能力,那就别在Atlas上死磕,直接上更重的方案会更合适。工具选型这件事,从来不是越强越好,而是越匹配越好。
2. Atlas核心原理与关键概念
2.1 一次SQL请求在Atlas里是怎么流转的
我觉得理解了这条链路,后面配置参数的坑就能少踩一半。当业务应用发起一条MySQL请求时,连接先打到Atlas监听的proxy端口,Atlas用自己的账号体系做认证,认证通过后,Atlas再作为客户端去向真正后端MySQL发起连接。等后端返回结果集,Atlas再原样翻译回给应用。
听起来很像一个“TCP转发器”,但它比单纯转发多做了一件重要的事:SQL解析。Atlas拿到客户端的SQL文本后,会对SQL做语法层面的分析和分类,判断它是读还是写、是否在事务中、是否命中分表规则,然后决定把请求路由到哪个后端。这里要特别指出,Atlas对SQL的解析并不是完整的语法树解析,更多是基于关键字和语句类型的规则判断,所以它对嵌套子查询、存储过程调用这些复杂情况的支持是有限的。
这条链路上有两个连接池,前端连接池是应用连Atlas的连接,后端连接池是Atlas连MySQL的连接。连接池的存在让后端MySQL不需要频繁地创建和销毁连接,这在高并发场景下能省下不少资源。但也正因为中间多了一层,Atlas本身不能成为性能瓶颈,所以官方版本里对事件线程数、超时时间、最大包大小都提供了配置项,后面实操部分我会专门讲。
2.2 读写分离与事务路由规则
Atlas的读写分离策略,核心规则简单粗暴:INSERT、UPDATE、DELETE以及DDL语句,一律路由到主库;SELECT语句,在开启读写分离的情况下,会按照配置的负载策略分发到只读后端。
但有一个非常关键的细节:如果一条SELECT出现在事务里,它也会被路由到主库。很多人在调试的时候不理解这个行为,以为是中间件出了问题,其实这是有意为之,因为MySQL主从复制有延迟,事务内的读如果走了从库,很可能读到旧数据,导致逻辑错误。所以正确理解是:在Atlas里,事务是一个“必走主库”的强约束条件。
基于这个原理,业务侧如果想考研某条读SQL的实时性,有几种做法。一种是把这条SQL放进事务里,哪怕只是一个只读事务,Atlas也会老老实实走主库。另一种是直接改成SELECT ... FOR UPDATE,不过这样会引入锁竞争,如果不是必须加锁就别用。还有一种是在应用层单独配置一个直连主库的通道,专门承载这类对一致性要求极高的查询。这三种方案我都试过,最省事的其实是只读事务,但要注意不能让这种事务里夹带真正的大查询,否则主库很受伤。
2.3 连接管理与线程模型
Atlas内部使用事件驱动模型来处理网络IO,配置项里的event-threads就是控制这个的。这个参数决定了Atlas用来处理网络事件的工作线程数量。我一开始图省事直接用默认值,结果线上压测发现延迟偏高,后来把event-threads从默认值调大,效果立竿见影。这个参数不是越大越好,一般建议设置为Atlas所在机器CPU核数的2到4倍,如果机器上有其他服务在跑,要适当匀一些出来。
再提一个容易踩坑的点:Atlas所在机器和MySQL所在机器之间的网络质量。因为所有SQL都要经过Atlas转发,这个一跳的延迟是躲不掉的,所以Atlas最好和业务应用、数据库部署在同一机房或同一VPC内,避免跨地域转发带来额外损耗。有些人把Atlas部署在公网环境,然后直连内网MySQL,这种做法不仅延迟高,而且安全风险极大。
2.4 分表功能的使用边界
Atlas的分表功能,刚接触的时候会觉得挺神奇:可以把一张逻辑表映射到一组物理表。比如订单表order_info,拆成order_info_0到order_info_127,配置的时候用类似order_info#0-127的写法批量声明,Atlas在收到SQL后会根据分表键计算目标表,然后把SQL改写后发给对应的物理表。
但是我必须提醒一句,这个分表和ShardingSphere那种成熟方案的能力差距不小。Atlas对分表键非常敏感,如果SQL条件里没有带上分表键做等值匹配,它就可能退化成扫描所有物理表,性能反而更差。另外,跨分片的JOIN、子查询、排序分页等场景,Atlas支持得并不好。所以我的建议是:Atlas适合做单表数据量扩大时的简单水平拆分,如果业务SQL本身很复杂,还是老老实实去用别的方案。
如果你的需求只是读写分离,分表功能可以先忽略。但如果你一定要用分表,请在测试环境把各种复杂SQL过一遍,尤其是带GROUP BY、ORDER BY、LIMIT的查询,跑一遍看结果对不对、延迟是否可接受,再决定要不要上线。
3. 从零到一:手把手部署一套可用Atlas
3.1 部署前准备:确认主从架构和权限
部署之前,先列一下我建议的最小环境:一台Atlas机器,一台主库,至少一台从库。这些机器之间的网络要互通,MySQL的账号要有足够的权限。以我常用的配置为例:
- 主库:192.168.1.10:3306
- 从库1:192.168.1.11:3306
- 从库2:192.168.1.12:3306
- Atlas机器:192.168.1.20,监听1234端口作为业务端口,2345端口作为管理端口
业务账号我通常会创建一个单独的账号,名字比如app_rw,权限不需要太大,具备业务所需库表的SELECT、INSERT、UPDATE、DELETE即可。注意,Atlas配置文件里存的密码要求是加密后的密文,不是明文,这个下一步会讲。
还需要确认一点:从库是否已经开启并正常追主库的binlog。如果从库本身就是坏的状态,提前接入Atlas只会让读流量打到坏实例上,反而增加排查成本。用SHOW SLAVE STATUS确认一下Slave_IO_Running和Slave_SQL_Running都是YES,再继续。
3.2 安装与最小可用配置
Atlas可以从GitHub上的开源仓库下载对应系统的发行包,解压后目录结构大概是这样的:bin下面放着mysql-proxy可执行文件和encrypt加密工具,conf下面放着配置文件模板,log目录用于存放运行日志。
安装其实就是解压、改名到/usr/local/mysql-proxy。接下来最关键的是生成加密密码。Atlas官方提供了一个encrypt工具,用法类似:
/usr/local/mysql-proxy/bin/encrypt App@123456运行后会在终端输出一串密文,把这个密文记下来。注意这个密文在配置文件里要和用户名一起填写,格式是“用户名:密文”。
然后编辑配置文件,我先给一份最小可用配置:
[mysql-proxy] admin-username=admin admin-password=admin@12345 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 pwds=app_rw:5f4dcc3b5aa765d61d8327deb882cf99 daemon=true event-threads=8 proxy-connect-timeout=10 proxy-client-max-packet-size=16777216 proxy-backend-max-packet-size=16777216 sql-log=REALTIME log-level=debug log-path=/usr/local/mysql-proxy/log charset=utf8解释几个关键参数。proxy-address是面向业务应用的监听地址,业务方连的就是这个端口。admin-address是管理端口,DBA用来查看状态和执行运维操作,这个端口千万别暴露到公网。proxy-backend-addresses是主库,proxy-read-only-backend-addresses是从库列表,多个从库用英文逗号分隔。pwds是业务账号和加密密码的映射。sql-log=REALTIME表示实时记录SQL日志,这个在调试阶段非常有用,但生产环境流量大时要注意日志磁盘占用,建议配合日志切割策略一起用。
配置写完后,确保相关目录有权限,然后启动:
/usr/local/mysql-proxy/bin/mysql-proxy --defaults-file=/usr/local/mysql-proxy/conf/mysql-proxy.conf启动后用ps确认进程还在,再检查一下log目录里有没有生成新的日志文件。如果进程秒退,十有八九是配置文件格式写错了,可以用mysql-proxy --help先看下参数名是否对应。
3.3 验证读写分离是否真正生效
启动成功后别急着把业务切过来,先在命令行验证读写分离是否生效。我用的是最土但最有效的方法:在MySQL端打开通用日志。
在主库和从库分别执行:
SET GLOBAL general_log = ON;然后在一台安装了mysql客户端的机器上,通过Atlas的业务端口执行SQL:
mysql -h192.168.1.20 -P1234 -uapp_rw -pApp@123456 -e "INSERT INTO test.t_order (id, name) VALUES (1, 'test');" mysql -h192.168.1.20 -P1234 -uapp_rw -pApp@123456 -e "SELECT * FROM test.t_order WHERE id = 1;"分别执行完后,查看主库和从库的通用日志。预期结果是:INSERT语句出现在主库日志里,SELECT语句出现在从库日志里。如果SELECT也跑到主库去了,检查是不是配置里没有正确设置proxy-read-only-backend-addresses,或者这条SQL被错误地识别成了写语句。
这一步虽然费一点时间,但值得做。我见过不少团队把Atlas部署上去了,却因为账号权限、网络策略、配置项问题,压根没走从库,主库照样被打死,还以为是中间件不给力。
3.4 用管理端口实现平滑上下线
数据库运维里最怕的一件事是“我要重启这台MySQL,但业务不能停”。以前的做法是发公告、等低峰期、快刀斩乱麻。有了Atlas的管理端口,这件事会从容很多。
连接管理端口的方式很简单,还是用mysql客户端:
mysql -h127.0.0.1 -P2345 -uadmin -padmin@12345在这个管理会话里,可以执行一系列类SQL命令来查看后端状态、摘除或恢复后端节点。比如可以用SHOW类似命令看后端健康状态,确认哪台从库处于在线状态。需要下线某台从库做维护时,先把它从Atlas摘除,让新的读流量不再分发过去,等连接排空后再去操作数据库本身。维护完重新挂回去。整个过程业务侧完全无感。
我实际遇到过的典型场景是:从库磁盘满了,需要扩容。以前要冒着影响业务的风险重启实例,现在就可以先在Atlas里摘除这个节点,确认没有新请求打上去,再从容地处理磁盘问题。恢复后再挂载回来,观察一段时间确认同步追平,再逐步放流量。
3.5 几个值得调整的调优参数
Atlas跑起来后,有四个参数我建议根据实际情况调一下。
第一个是event-threads。这个前面说过,它影响并发处理能力。一个小规格的4核机器,设置成8一般没问题;如果是8核,可以设置成16。但不要无脑调大,线程过多反而会增加上下文切换成本。
第二个是proxy-connect-timeout。它控制Atlas和后端MySQL建立连接的超时时间。如果后端MySQL负载很高,连接建立变慢,这个值太小会导致Atlas误判后端不可用。我习惯把它设置在10到30秒之间,太短容易误报,太长会让应用端的等待体验变得糟糕。
第三个是最大包大小。proxy-client-max-packet-size和proxy-backend-max-packet-size分别控制客户端到Atlas、Atlas到后端的最大数据包。如果业务里有批量插入或者大结果集查询,默认值可能不够用,表现为应用端报错说packet too large。这时候把这两个值调大,同时确认后端MySQL的max_allowed_packet也足够大,两边要匹配。
第四个是charset。这个参数一定要和数据库、客户端的字符集保持一致。在实际项目中,我遇到很多次中文乱码问题,排查到最后就是Atlas配置里的charset和数据库不一致,导致字符集转换出错。如果是utf8mb4,也要注意Atlas版本是否支持。
4. 常见问题与排障实录
4.1 连接总是闪断怎么办
应用通过Atlas连接MySQL时,时不时出现“Lost connection to MySQL server”的错误。这种问题我先从三个方向排查。
第一,Atlas所在机器的文件描述符限制。Atlas作为代理,每个客户端连接和后端连接都要占用文件描述符,如果ulimit限制太小,连接数一多就会触发断连。处理方式是调大/etc/security/limits.conf里对应进程的nofile限制,然后重启Atlas。
第二,空闲连接超时。MySQL端有个wait_timeout参数,如果连接空闲太久,MySQL会自动断开。Atlas作为代理,同样要关注这个超时机制。如果业务侧有长连接池,建议设置合理的连接池空闲检测机制,或者定期发送心跳SQL。
第三,后端数据库主动kill连接。有时候DBA会在数据库侧做连接数限制,超过阈值直接kill,表现为业务侧随机断连。这种情况要去数据库端查错误日志,看是否有相关的aborted connection记录。
4.2 主从延迟导致读到了旧数据
读写分离上线后,业务反馈某些页面数据不对,重新刷新又好了,典型的AP读场景。排查后发现,从库的Seconds_Behind_Master已经飙升到十几秒,而Atlas默认会把普通SELECT都分发到从库,读取自然会有延迟。
这个问题不能完全靠Atlas解决,因为延迟的根源在主从复制。我的处理策略是分级管理读请求:核心查询走主库或者走只读事务,非核心查询走从库。在应用侧,会对数据一致性要求高的服务单独配置一个数据源,走主库;普通列表页、统计页则全部走Atlas,接受秒级延迟。这样既利用了从库的压力分担能力,又不会让用户感知到数据异常。
如果非要从Atlas层面想办法,可以考虑把延迟过高的从库临时摘除,等主从追平再挂回去。这个操作我用管理端口做过很多次,效果很好,但前提是要有监控报警告诉你延迟已经超标了。
4.3 SQL黑名单与慢查询治理经验
Atlas有个好用但容易被忽略的能力:SQL黑名单。你可以在管理端口把某条危险的SQL拉黑,Atlas遇到就会直接拒绝执行,不走后端数据库。这个功能在处理突发慢SQL时非常有用。
我经历过一次线上事故,一条SQL因为日期条件写错了,导致索引失效,瞬间把主库CPU打满。当时第一反应是去数据库侧KILL会话,但新的查询还在源源不断进来。如果配置了Atlas黑名单,直接把这条SQL的特征串拉黑,就能快速止血。等应用代码修复后再取消黑名单。
但黑名单是治标不治本,真正要做的还是慢查询治理。建议把数据库的慢查询日志打开,定期分析。Atlas的sql-log也可以配合使用,排查某个业务方的SQL总量和耗时都非常直观。慢SQL治理里的一个隐藏点:隐式类型转换。WHERE条件里字段是varchar,但传了数字,MySQL可能放弃索引做全表扫描。这类SQL在慢查询日志里非常典型,需要业务方改参数类型,或者强制类型转换。
4.4 从库挂了会发生什么
从库宕机是运维里很常见的事故类型。在Atlas体系下,如果一台从库挂了,Atlas的健康检查会把它从可用列表里摘掉,读流量会分摊给其他从库。这个过程业务方通常是无感知的。
但如果所有从库都挂了,那就比较麻烦了。这个时候读请求会失败还是自动转到主库?我的经验是:这取决于版本和配置。有些情况下,Atlas会把读请求继续转发给后端,而后端已经不可达,还是返回错误给客户端。所以不要指望Atlas能解决“从库全部不可用”这种极端问题。合理的做法是在应用侧做兜底,比如配置一个fallback数据源,或者在基础设施层面对Atlas做健康检查,一旦发现它无法提供读服务,就动态调整应用的连接目标。
另外,排查从库故障时,建议先看Atlas管理端口里后端节点的状态,确认是不是已经自动摘除。如果状态显示还处于在线,但实际已经连不上,那可能是健康检查的频率设置不合理,需要调整探测间隔。
4.5 排障速查表
我把几个高频现象整理成了一个速查表,方便大家遇到问题时快速对照:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 应用连不上Atlas | 防火墙未放行端口、proxy-address配置错误、Atlas进程未启动 | 先检查监听端口,再查看日志文件确认启动状态 |
| 连接后马上断开 | 后端MySQL拒绝连接、密码错误、文件描述符耗尽 | 从Atlas机器手动连接后端MySQL验证,检查limits |
| SELECT全部走主库 | proxy-read-only-backend-addresses配置缺失、事务内查询 | 补充从库列表,确认应用是否开启了事务 |
| 中文乱码 | charset参数与数据库不一致 | 统一设置为utf8或utf8mb4并重启Atlas |
| 大SQL执行失败 | 包大小超过限制 | 调大proxy-client-max-packet-size和数据库max_allowed_packet |
| 管理端口无法访问 | admin-address绑定错误、账号权限问题 | 确认能本地登录,再检查防火墙 |
| 从库延迟太大 | 主从复制异常、大事务产生延迟 | 查看SHOW SLAVE STATUS,必要时临时摘除从库节点 |
这张表并不完整,但覆盖了我在接入Atlas过程中踩过的绝大多数坑。排障的整体思路还是先定位问题在哪一层:是客户端到Atlas,还是Atlas到MySQL,还是MySQL本身的问题。把问题分层之后,用日志和数据来判断,而不是靠猜。
老实说,用Atlas这两年,我对数据库中间件的敬畏心变强了。它确实帮我省掉了大量读写分离的重复工作,也多次在数据库维护中实现了平滑切换,但它并不是万能的。中间件能解决路由问题,却解决了不了SQL质量、主从延迟、容量规划这些根因问题。如果DB本身没有治理好,任何中间件都只是把问题往后移了一点。所以哪怕上了Atlas,慢查询日志、监控告警、主从延迟检查这些基本功一样都不能省。最后再分享一个小技巧:新环境接入Atlas后,别急着把流量全切过去,先放一个业务模块试运行几天,把读写分离、事务路由、黑名单这些能力都验证一遍再全量切,这样即使有问题也能快速回退,不至于上来就踩出大事故。