☰
MySQL中间件Atlas详解:读写分离、部署配置与运维排障实战
2026/10/19 13:05:46 网站建设 项目流程

这两年只要聊到MySQL的高可用和读写分离,圈子里总绕不开一个老牌中间件——Atlas。很多刚接触数据库架构的同学一听到“Atlas”第一反应是地图册,再一问才知道这其实是奇虎360开源的一个MySQL数据库中间件项目。我最初接手的那套主从集群里就跑着Atlas,几年下来踩过不少坑,也积累了一些实际经验,这次就围绕这个项目好好聊一聊。

这个Atlas能做什么?简单说,它处在应用和MySQL实例之间,把开发者的读写请求自动分发到不同的数据库节点上,主库负责写入,从库分摊读流量。非常适合那些“读多写少”、希望快速拆分数据库压力、又不想大改业务代码的团队。这篇文章不是官方文档的复读,而是结合我实际部署和维护的经验,把原理、搭建、配置、排障这些东西一次讲透,适合DBA、后端开发以及正在选型中间件的架构师参考。

1. 项目背景与整体设计思路

1.1 为什么会出现Atlas这样的中间件

先聊点背景。MySQL的主从复制能力出来之后,“一主多从”成了很多团队缓解数据库压力的首选方案:主库扛写入,从库分担读流量。但很快大家就发现一个现实问题——业务代码不会自动区分读写。你需要自己实现一套路由逻辑,要么在应用层写多个数据源,要么让ORM框架去做读写分离,这就有几个绕不开的麻烦:

  • 业务代码侵入严重:每个新项目都得重写一套数据源逻辑,非常痛苦。
  • 主从切换时连接管理复杂:一旦主库宕机,应用连接池需要感知并切换,处理起来很繁琐。
  • 从库负载不均衡:多个从库之间的读流量没法均匀分配,某台机器容易成为热点。

在这种背景下,数据库中间件就出现了。它的核心价值就是把“读写路由”这件事从业务代码里剥离出来,放在一个独立的代理层统一处理。Atlas就是这类产品里很有代表性的一员。

1.2 Atlas在整个架构里的角色

画一条简单的链路:业务应用 → Atlas(中间件) → MySQL主库/从库。

应用连接Atlas就像连接一个普通的MySQL实例,端口、协议都兼容。Atlas收到SQL语句后,判断它是SELECT还是写操作(INSERT/UPDATE/DELETE等),再结合配置里的读写权重,把请求转发到后端的真实数据库上。对于应用来说,它并不知道背后有几台MySQL,也不需要关心哪台是主、哪台是从,连接串只需要指向Atlas就行。

这个设计其实是借鉴了LVS(Linux虚拟服务器)的思路:对外暴露一个虚拟VIP,背后挂载多台真实服务器。对调用方来说,整个数据库集群被“抽象”成了一个地址,这是中间件能够显著降低业务改造量的关键。

我当时选择Atlas而不是自己写数据源路由,核心原因就是它的部署成本低、协议兼容度高。Java可以用JDBC直连,PHP有mysqli扩展,Go有go-sql-driver,都不用额外装驱动,只要把连接地址改一下就能接入。对于那种“一堆老项目都要从单库升级到主从集群”的场景,Atlas几乎是成本最低的过渡方案。

1.3 Atlas基于MySQL Proxy的传承与改动

Atlas这个项目不是从零写的。它基于MySQL官方早期推出的MySQL Proxy 0.8.2版本做了深度二次开发。MySQL Proxy本身是一个基于Lua脚本的代理框架,理论上可以用Lua写各种自定义逻辑,但性能和稳定性一直不太理想,在生产环境落地不多。360团队把这个框架C语言内核拿过来,改了协议解析和线程模型,砍掉了很多用不上的Lua扩展能力,换成了更纯粹、更高效的读写分离和负载均衡实现。

这也是为什么你在Atlas的安装包里还能看到一些MySQL Proxy的影子——比如配置文件和启动脚本都比较像。但它的实际使用体验和性能要比裸的MySQL Proxy好不少。简单理解,Atlas就是“站在官方代理这个半成品肩膀上,做了一款真正面向生产环境的中间件”。

1.4 Atlas解决了哪些具体问题

归纳来说,在我的使用场景里,Atlas主要帮我解决了几个实际问题:

  • 读写自动分离:不用在业务代码里做任何判断,写请求自动走主库,读请求自动走从库。
  • 从库负载均衡:多个从库可以配置不同的权重,请求按权重分发,避免某台从库负载过高。
  • 连接池复用:Atlas本身维护了一套到后端MySQL的连接池,避免大量前端连接直接压到MySQL上。
  • 后端实例可动态调整:通过管理接口,可以在不重启服务的情况下上下线后端的MySQL实例。
  • 基础的高可用辅助:虽然Atlas本身不是专职的高可用组件,但配合脚本可以实现主从切换后快速调整读写指向。

这些功能现在看来可能不算新奇,但放在它开源的年代,确实解决了很多团队的燃眉之急。即便今天再看,它的设计思路依然有很强的参考价值。

2. 核心原理与配置细节拆解

2.1 Atlas的路由与负载均衡原理

Atlas的路由逻辑相对简单,但简单不等于粗糙。它内部维护了两个后端节点列表:一个Master组(用于写)、一个Slave组(用于读)。当收到SQL语句时,它会先做SQL解析,判断语句类型:

  • SELECT语句默认走从库,除非事务内或者使用注释强制走主库。
  • INSERT、UPDATE、DELETE、DDL等语句一律走主库。
  • 事务中的读操作也会被路由到主库,避免复制延迟导致数据不一致。

这里有一个容易踩坑的点:默认情况下,即使是SELECT,如果前面开启了事务(BEGIN),也必须在主库执行,否则读到自己还没提交的数据,或者读到从库的旧数据,都会出问题。Atlas在内部会跟踪会话的“事务状态”,一旦发现处于事务中,后续SQL全部送主库。这个设计非常实用,也避免了业务把“读主库”的标记写在SQL里。

在负载均衡上,Atlas 支持给每个从库配置不同的权重。比如两个从库,一个机器配置好、带宽大,可以配weight=2,另一个普通机器配weight=1,那么读请求就会按2:1的比例分发。它内部使用的是平滑加权轮询算法,不会出现某个连接被连续打到一个节点的情况。

2.2 关键配置项逐项解读

Atlas的默认配置文件是/usr/local/mysql-proxy/conf/atlas.cnf。我挑几个最核心的配置项来聊,这些都是日常运维中碰得最多的。

mysql-proxy-backend-addresses

这个配置项用来指定后端写节点,也就是主库。格式是ip:port,多个地址用逗号分隔。注意写节点虽然可以配置多个,但同一个时刻只有第一个可用的节点会被选为主写节点。这里如果你做主从自动切换的脚本,需要同步修改这个字段并重启Atlas,或者用管理接口动态调整。

示例:

mysql-proxy-backend-addresses = 192.168.1.10:3306

mysql-proxy-read-only-backend-addresses

这个配置项指定只读节点,即从库列表。格式同样是ip:port,可以配置多个,每个后面还可以跟@weight来指定权重。

示例:

mysql-proxy-read-only-backend-addresses = 192.168.1.11:3306@1,192.168.1.12:3306@2

上面这个例子里,192.168.1.11的读权重是1,192.168.1.12的读权重是2。我建议给配置高的机器分配更大的权重,给配置低的机器分配更小的权重,让流量匹配机器能力。

mysql-proxy-address

该配置项是Atlas对外提供服务的监听地址和端口,也就是应用要连接的入口。我的习惯是绑内网IP,不要绑0.0.0.0,这样更安全。

示例:

mysql-proxy-address = 192.168.1.20:1234

mysql-proxy-charset

数据库字符集,这个非常关键,如果配置错了,中文写入和读取会出现乱码问题。建议和MySQL实例的字符集保持一致,比如utf8mb4。

admin-username / admin-password

管理接口的用户名和密码,用于通过MySQL客户端连接到Atlas的管理端口执行管理操作,比如动态上下线后端节点。这个密码不要用弱密码,管理接口虽然只允许本地或者指定IP访问,但生产环境最好还是强密码。

pwds

业务账号的加密列表,格式是用户名:加密后的密码。Atlas不会直接把明文密码写在配置里,而是用一个加密工具生成密码串,再填到配置文件里。加密工具在Atlas安装目录下的bin/encrypt。

daemon / keepalive

daemon配置控制Atlas是否以守护进程方式运行,keepalive则控制进程异常退出时是否自动拉起。这两个我都是开着的,保证服务崩溃后能自动恢复。

2.3 读写分离的强制路由与注释透传

有些场景比较特殊,比如某个报表查询耗时很长,你不希望它读从库,担心把从库拖垮;又或者刚写完一条数据,立刻就要读到自己写入的内容,而此时从库复制延迟还没追上。这时候Atlas提供了一种“注释携带路由”的玩法:只要在SQL前面加上/master/这样的注释,无论SQL是不是SELECT,都会强制走主库。

示例SQL:

/*master*/ SELECT * FROM order_info WHERE user_id = 10086;

应用里只要在关键SQL上加上这个注释,就能打破默认路由规则。这个特性非常实用,我经常用来处理“写后立即读”的场景,避免因为复制延迟造成数据不一致。

但要注意,Atlas对注释的解析有要求:强制走主库的注释必须放在SQL语句的最前面,如果前面有空格或者其他注释,可能导致解析失败。踩过这个坑,所以建议在代码里做成常量,统一拼接。

2.4 分库分表能力的边界

Atlas还支持一定的分库分表能力,可以在配置里定义规则,把不同的表分散到不同的后端实例上。不过说实话,Atlas的分库分表能力比较初级,适合简单场景,比如按数字范围取模。一旦涉及复杂的分片键、跨库JOIN,它就力不从心了。

我的建议是这样的:如果项目刚起步,数据量在百万级、千万级,读写分离加索引优化就够用了,完全没必要上分库分表;如果数据量到了亿级,又需要透明分片,那就应该考虑MyCat、ShardingSphere这类更专业的分片中间件。Atlas的分片功能只能作为一个轻量辅助,不要把它当主力。

3. 环境准备与实操部署全过程

3.1 安装前的环境规划

我在生产环境里部署Atlas之前,通常会先画一张表把节点信息理清楚。一个比较典型的规划如下:

角色IP地址端口说明
MySQL主库192.168.1.103306写节点
MySQL从库1192.168.1.113306只读节点,权重1
MySQL从库2192.168.1.123306只读节点,权重2
Atlas节点192.168.1.201234应用接入入口
管理端口192.168.1.202345运维管理入口

这里有个经验之谈:Atlas和MySQL最好分开部署,不要混在一台机器上。Atlas本身虽然轻量,但它要同时维护前端连接和后端连接,系统资源占用并不低。如果和数据库抢CPU和内存,两边性能都会受到影响。

3.2 安装步骤与版本选择

Atlas的安装包已经有几年没更新了,但这并不妨碍使用,很多生产系统跑得都非常稳。我用的版本是2.2.1,这是个比较成熟的Release版本。安装包一般提供RPM和源码编译两种方式,我习惯用RPM,省事且方便卸载。

安装命令示例(这里用RPM包的方式):

rpm -ivh Atlas-2.2.1.el6.x86_64.rpm

安装完成后,默认安装路径通常是/usr/local/mysql-proxy/,目录结构大致如下:

/usr/local/mysql-proxy/ ├── bin/ │ ├── mysql-proxy │ ├── encrypt │ └── ... ├── conf/ │ └── atlas.cnf ├── lib/ │ └── ... ├── log/ │ └── ... └── test/ └── ...

如果你的系统是较新的Linux发行版,安装旧版RPM可能会遇到依赖缺失的问题,比如libmysqlclient版本不兼容。这时候可以尝试用源码方式编译安装,或者找一下社区里适配新系统的补丁包。我在CentOS 7上装过一次,需要手动安装兼容库才能跑起来。

3.3 配置文件的编写与加密密码生成

安装完毕后,最重要的一步就是配置。我先把配置文件的完整示例贴出来,然后一行一行说。

[mysql-proxy] # Atlas后端主库地址 mysql-proxy-backend-addresses = 192.168.1.10:3306 # Atlas后端从库地址及其权重 mysql-proxy-read-only-backend-addresses = 192.168.1.11:3306@1,192.168.1.12:3306@2 # Atlas对外服务端口 mysql-proxy-address = 192.168.1.20:1234 # Atlas管理端口 admin-address = 192.168.1.20:2345 # 管理接口用户名和密码 admin-username = admin admin-password = admin123 # 业务账号加密后的密码 pwds = user1:加密后的密码串 # 守护进程和自动拉起 daemon = true keepalive = true # 日志路径 log-path = /usr/local/mysql-proxy/log # SQL日志开关,建议开启,便于排查问题 sql-log = ON # 实例名称 instance = atlas_1 # 字符集 mysql-proxy-charset = utf8mb4

生成业务密码的方法是进入bin目录执行encrypt:

cd /usr/local/mysql-proxy/bin ./encrypt your_password

执行后会输出一串编码过的字符串,把它填到pwds字段里,格式是用户名:加密后的字符串。这个加密是单向的,配置文件里不会出现明文,这是一个很好的安全习惯。

注意:配置文件修改后需要重启Atlas才会生效。重启前最好先执行mysql-proxy --defaults-file=/usr/local/mysql-proxy/conf/atlas.cnf check做语法检查,确认配置没问题再重启。

3.4 启动Atlas并验证基本功能

配置完成后,启动命令如下:

/usr/local/mysql-proxy/bin/mysql-proxy --defaults-file=/usr/local/mysql-proxy/conf/atlas.cnf

启动后检查进程是否存活:

ps -ef | grep mysql-proxy

看到进程存在后,再用MySQL客户端连接Atlas的对外端口,测试读写分离是否生效:

mysql -h 192.168.1.20 -P 1234 -u user1 -p

登录后执行一条查询,如果一切正常,你会看到结果正常返回。接着在MySQL从库上开启通用日志,或者用SHOW PROCESSLIST观察,确认连接是从Atlas转发过来的。我用过一个小技巧:在从库执行SHOW PROCESSLIST,如果能看到来自Atlas IP的连接,说明读流量确实走到了从库。

3.5 管理接口的常用操作

Atlas的管理接口非常实用,日常上下线后端节点不用改配置重启。我总结了几条最常用的操作:

-- 登录管理接口 mysql -h 192.168.1.20 -P 2345 -u admin -padmin123 -- 查看后端节点状态 SELECT * FROM backends; -- 将某个从库下线(不再接收新读请求) SET OFFLINE 2; -- 将某个从库重新上线 SET ONLINE 2; -- 动态添加从库 ADD SLAVE 192.168.1.13:3306@1; -- 动态移除从库 REMOVE BACKEND 3;

这里的数字是后端节点的ID,可以用第一条SQL查看到。这个管理接口是我最喜欢Atlas的地方,配合自愈脚本,可以在从库故障时自动把它踢出集群,恢复后再加回来,整个过程对业务完全透明。

3.6 与业务系统的对接要点

应用接入Atlas,本质就是换一个连接地址和端口。但有几个细节一定要处理妥当,不然上线容易出问题。

第一,连接池的initialSize和minIdle别设太大,因为Atlas本身也维护连接池,业务侧再搞几十个空闲连接,资源就浪费了。我一般建议最小空闲连接控制在2~4个。

第二,JDBC连接串里不要启用自动提交优化。有些框架会默认把autoReconnect设为true,这会让连接断开再重连时对业务无感知。但Atlas对后端MySQL的连接可能有状态上下文,重连后事务状态并不稳定,最好还是靠连接池的“借出前校验”来做健康检查。

第三,超时时间要调短。业务连接Atlas的connectTimeout和socketTimeout建议不要超过30秒,因为中间件层出问题时,如果应用侧一直傻等,很容易引发雪崩。我踩过一次坑,就是业务侧超时设置太长,Atlas因为后端MySQL主库切换卡住连接,结果应用线程全卡在获取连接上,核心链路直接瘫痪。

第四,保留SQL前部的强制读主注释。如果业务里本来就用了/master/这种注释,确认应用没有在中间截断或者转义掉。Atlas对注释的识别是硬解析,注释被破坏等于路由失效。

4. 常见问题与排障实战

4.1 配置无误却连接报错:检查字符集和密码加密

遇到“Access denied”或者连接直接超时,先别慌。我遇到最多的情况其实有两种:一是pwds里的密码没有用encrypt工具加密,直接填了明文,导致认证不通过;二是charset配置与后端MySQL不一致,握手阶段就失败。

排查方法很简单:先看Atlas日志,通常会有认证失败的记录。

tail -f /usr/local/mysql-proxy/log/sql_log.log

如果日志中提示字符集不对,就把mysql-proxy-charset改成和后端MySQL一样的字符集,重启后再试。如果是密码问题,重新生成加密串,注意每次生成的密文可能不同,这是正常的,直接把新密文替换进配置即可。

4.2 读写分离没有生效,查询全走了主库

这是一个出现频率很高的问题。表现是主库负载很高,但从库闲置。原因通常有这么几个:

  • SQL语句虽然以SELECT开头,但在前面有显式事务(比如Java代码里使用了@Transactional),Atlas会把整个事务期间的SQL都路由到主库。
  • 业务代码里走了存储过程或者带有不确定结果的语句,Atlas的SQL解析器无法识别,可能默认路由到主库。
  • 配置文件里read-only-backend-addresses没有配置正确,或者从库地址不可达,Atlas自动放弃了读负载均衡。

排查思路是:先在管理接口执行SELECT * FROM backends;,确认从库节点的状态为UP。如果节点正常,再通过从库通用日志确认是否有查询到达。如果从库侧没日志,那大概率就是事务问题或SQL解析问题。

针对事务问题,解决方案有两条:一是业务代码检查是否真的需要事务,二是对只读操作去掉事务注解。事务内读主库是合理的安全设计,不建议为了性能强行把事务内查询踢到从库。

4.3 主从切换后Atlas仍指向旧主库

MySQL主从切换在生产环境是大事,Atlas这边如果处理不好,会导致写入失败。Atlas本身不感知主从角色变化,所以你需要在切换后手动更新配置。

我的处理流程是这样的:

  1. 通过管理接口把所有写流量暂时切到新主库上。
  2. 修改atlas.cnf里的mysql-proxy-backend-addresses,把新主库IP写进去。
  3. 如果旧主库变成从库,就把旧主库IP加入mysql-proxy-read-only-backend-addresses。
  4. 重启Atlas使配置生效。

更有经验的团队会写一个切换脚本,把上述操作自动化。脚本逻辑大致是:监控MySQL主从状态,发现主库故障后自动触发切换,然后调用Atlas管理接口动态调整后端节点列表,最后校验Atlas是否成功感知到新节点。这类脚本不复杂,但能省下很多半夜救火的功夫。

4.4 性能瓶颈:Atlas所在机器成了短板

运行久了你会发现,Atlas的CPU、内存占用会逐步上升。这背后通常是下面几个原因:

  • 开启了sql-log日志,高流量下日志写入消耗大量IO。
  • 连接数太多,Atlas需要维护的后端连接池膨胀。
  • 后端慢SQL多,所有等待都积压在Atlas连接池里。

我的经验是给Atlas所在的中间件机器预留足够的CPU和内存资源,通常至少2核4G起步。同时,sql-log可以在生产环境关了,或者按天轮转。慢SQL的治理应该在数据库侧做,而不是依赖Atlas扛住所有压力。

还有一个易被忽视的点:TCP内核参数。Atlas作为代理,会大量产生网络连接请求。我通常会在服务器上调整以下参数:

net.ipv4.tcp_tw_reuse = 1 net.ipv4.ip_local_port_range = 1024 65535

这些调整能让TIME_WAIT的连接更快复用,减少端口耗尽的风险。

4.5 连接池报警:Too many connections

有时候业务方会反馈数据库连接数告警,但你去MySQL看,连接数并没有爆。这种情况往往是因为Atlas把后端连接池撑得太满。Atlas的后端连接池参数是默认的,在高并发场景下可能会和后端MySQL的max_connections起冲突。

我的建议是调小Atlas的并发连接控制,同时在后端MySQL上把max_connections放宽。但不要无脑放宽,连接数太多反而会拖垮MySQL。最理想的状态是定量分析:一个应用实例50个线程,部署了10个实例,那Atlas到后端的连接总量控制在200~300之间就够了,完全不用到1000以上。

4.6 常见问题速查表

问题表现可能原因快速处理办法
应用连接Atlas超时Atlas未启动/网络不通/防火墙拦截检查进程,telnet 端口测试
认证失败pwds密码未加密或密码错误重新生成加密密文,更新配置
中文乱码charset配置不一致统一设为utf8mb4,重启Atlas
查询全部走主库事务内执行SQL/从库地址错误查看事务、检查backends节点状态
主库切换后写失败Atlas仍指向旧主库修改配置文件或动态调整backend
内存持续上涨sql-log开启/连接数过多关闭sql-log,优化连接池参数

5. 从Atlas迁移到其他中间件的一些体会

人到后期总会考虑是不是要换掉Atlas。原因不是它不好用,而是技术栈演进后需求变了。比如用了云数据库自带的代理,或者引入了Kubernetes后希望有更灵活的Service Mesh方案,又或者团队希望使用ShardingSphere这样能嵌入应用端的中间件。

我在迁移过程中的体会是:不要一次性替换,最好灰度。因为Atlas对业务代码的侵入几乎为零,你只是换了个连接入口。具体操作上,可以先让一部分服务连新中间件,观察一段时间,确认没问题再全量切换。同时,连接池里的URL要预留好回滚方案,一旦新中间件出问题,马上切回Atlas,影响面要控制在最小。

如果你问我什么时候该换、什么时候不该换,我的标准是这样:只要Atlas还能满足负载均衡和读写分离的需求,且没有频繁的故障,继续用完全没问题。但如果团队开始做分库分表、分布式事务,那确实该换更专业的中间件了。工具应该是为业务服务的,不是为“用新工具”而换。

6. 运维心得与作业化建议

6.1 状态监控和日志体系

不要等出故障才去看Atlas日志。我建议从一开始就把监控和日志接入一体化运维平台,至少要盯这几个指标:

  • Atlas进程是否存活。
  • Atlas CPU使用率和内存占用。
  • 后端节点状态(UP/OFFLINE/DOWN)。
  • 读写流量比例,如果发现写流量长期占比较高,说明读写分离效果不佳。
  • 连接数曲线,提前预警连接数逼近上限。

Atlas的管理接口其实已经暴露了节点状态,用脚本定期采集SELECT * FROM backends的结果,就可以做到基础监控。日志方面,如果开启了sql-log,可以用它来做慢查询分析,但注意控制日志量,我建议至少按天切割,保留7天就够。

6.2 灾备与高可用方案的组合

Atlas是个中间件,不是高可用组件。一个成熟的方案是让前端的keepalived负责Atlas本身的故障切换,两个Atlas节点一主一备,共享同一个VIP。当主Atlas宕机,备用Atlas自动接管VIP,应用几乎无感知。然后再配合MySQL主从的MHA或者Orchestrator方案,构建出一套全链路的数据库高可用体系。

这套组合拳的关键在于:运维脚本要提前写好,预案要定期演练。不要等到真出事了,半夜爬起来现查文档,那体验我深有体会,真的很酸爽。

6.3 团队协作与知识沉淀

Atlas作为一个开源中间件,网络上的资料并不算多,很多问题要靠自己踩坑总结。所以我特别建议团队内部建立一个“Atlas运维手册”,把常用的管理命令、切换脚本、排障案例沉淀下来。新人来了不用从零摸索,遇到问题先查手册,再不行才去翻代码。

我自己的习惯是每次踩坑都会顺手记到wiki里,哪怕只有几行字,例如“配置字符集不一致导致中文乱码”这种,下次遇到直接搜索关键词就能定位。这种知识沉淀的价值,在关键时刻比任何架构设计文档都管用。

7. 写在最后的一个小经验

如果只让我分享一条关于Atlas的实操心得,我会说:生产环境里一定要把sql-log和实时日志分开,或者干脆默认关闭sql-log。很多人在排查问题时第一反应就是开sql-log,结果高流量下直接把磁盘打满,引发二次故障。日志是辅助手段,不是排查的唯一途径,优先用管理接口看节点状态,用MySQL的general_log在从库侧抽样,这样既不影响性能,又能快速定位问题。

Atlas这个项目也许不再是当今数据库中间件舞台上的主角,但它的设计思路、运维理念,放在今天依然值得借鉴。希望我这篇文章能帮你少走一些弯路,如果你正在部署或者维护Atlas,欢迎在评论区一起讨论踩过的坑和解决方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询