说实话,第一次听到“Atlas”这个名字,我脑子里冒出来的其实是地图集和希腊神话里那个扛天的巨人。后来在业务里真正接触它,才发现技术圈里更常说的 Atlas,是 360 开源的那套 MySQL 中间件——一个基于 MySQL Proxy 改造的、主打读写分离和轻量分表的代理层。
文章写到这里,我想先跟各位交代一下:这篇内容的目标读者,是那些手上已经有 MySQL 主从架构,想把读写流量拆开、又不想引入一套特别重的分布式数据库中间件的团队。Atlas 上线简单、文档直接、运维成本低,只要你愿意在一两个配置文件里花点心思,它基本可以做到“装完就能用”。下面我就从架构设计、部署配置、核心机制到线上排障,把自己折腾过的东西完整过一遍,给你一份可以照着抄的作业。
1. 整体设计与架构思路:一个轻量代理到底解决了什么问题
在真正动手部署之前,我觉得最值得花时间的是把“为什么需要 Atlas”这件事想清楚。因为很多团队一上来就冲着我方引入中间件,结果项目做到一半才发现:要么是它解决不了你的实际问题,要么是它和你现有的架构天然冲突,最后只能推翻重来。Atlas 的定位非常明确:它是一个数据库访问代理,处在应用和 MySQL 集群之间,主要帮你做两件事:把读请求和写请求分流到不同的实例上,以及通过分表规则把单表压力打散。
可能有人会问:MySQL 自己不是有主从复制吗?应用层直接判断一下 SQL 类型,连主库还是连从库不就行了吗?我一开始也这么干过。但业务一旦多起来,每个团队都在代码里写自己的读写分离逻辑,判断规则五花八门:有的是if (sql.startswith("select")),有的是用 ORM 的读写标识,还有的直接在 MySQL 驱动层做文章。结果就是:主库被无意义的select count(*)打到扛不住,从库一堆没建索引的大查询在跑,出了故障谁也说不清这些流量到底是从哪条路径进来的。中间件的价值就在这里——把路由逻辑从应用代码里剥离出来,统一收敛到一个独立组件里去管理和演进。
Atlas 具体能做什么,我用一张表总结一下:
| 能力项 | 说明 |
|---|---|
| 读写分离 | 识别 SELECT 与写语句,SELECT 走从库,写操作走主库,事务内和锁读自动路由到主库 |
| 从库负载均衡 | 支持多个从库,按配置权重分配读流量 |
| 轻量分表 | 在代理层按字段做 Hash 或 Range 分片,透明路由到物理子表 |
| 连接池 | 预建到后端 MySQL 的连接,降低应用频繁建连的开销 |
| IP 黑白名单 | 在代理层做简单的来源访问控制 |
| 平滑管理 | 管理接口支持后端节点上下线,可以在不切应用的情况下变更后端 |
这套能力放到今天的语境里看,可能觉得不算什么,ProxySQL、MyCat、ShardingSphere 哪个不比你功能多?但你要回到它的设计场景里去看:Atlas 的定位从来不是替代分布式数据库中间件,而是“主从架构下的第一层分流器”。如果你的数据量还没到非要分库分表的阶段,只是主库读压力大,想快速把读流量分摊到只读从库上去,那 Atlas 就是很合适的落地方案。它不搞全局自增 ID、不搞分布式事务、不搞跨节点 Join,相反,这些“缺失”正好让它保持简单、稳定、好排查。
拿我自己的选型经历来说,当时也对比了 MySQL Router、ProxySQL 和 MyCat。MySQL Router 很轻,但它主要面向 InnoDB Cluster,对传统主从和分表的支持偏弱;ProxySQL 功能确实强,可首选项多、规则多,对于我们这种希望“看一眼配置就能维护”的小团队来说,反而是负担;MyCat 功能完整,但不是所有业务都需要分布式事务和全局序列,硬上会给自己增加很多心智负担。Atlas 的取舍很简单:核心就是读写分离 + 分表,配上两个管理命令,几分钟就能上手,这恰恰是当时我们最看重的。
2. 核心机制拆解:读写分离、分表规则与管理接口
理解了 Atlas 的定位之后,就要进入它的核心细节了。这部分我按三个层面展开:SQL 路由、分表规则、运维管理接口。每一个点我都会说清楚它背后的判断逻辑,以及你在实际使用中需要注意的地方。
2.1 读写分离是怎么判断的
Atlas 的读写分离核心是解析 SQL 的第一个关键字,同时结合一些额外上下文。这里有几个关键细节,是光看官方文档容易忽略的:
第一,普通的SELECT语句会走从库,但事务内的SELECT会走主库。原因是:事务里你可能先写后读,如果读走了从库,而主从复制有延迟,那你读到的就是旧数据,业务上会出现“刚插入查不到”这种极其诡异的问题。MySQL Proxy 时代的实现逻辑里,事务状态是通过连接状态跟踪的,Atlas 继承了这个设计,所以你在事务里写SELECT,它也会老实走主库,属于安全优先的保守策略。
第二,SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE这两类带锁的查询,Atlas 会强制路由到主库。这好理解——加锁的目的是为了在写之前锁定行,你要是锁到从库上去,主从一不同步,锁就白加了,严重的情况下会导致数据错乱。这类语法识别是写死在解析逻辑里的,不需要你配置。
第三,Atlas 对SQL的判断基于关键字和正则表达式的组合匹配。这也是它和 ProxySQL 这类使用正式 SQL 解析器的产品最大的不同。好处是快,坏处是遇到一些特殊写法可能判断不准。举一个我踩过的例子:存储过程里动态拼接的 SQL,或者SELECT前面带了一些特殊 hint 注释,老版本 Atlas 有概率识别错乱。解决方法是:在应用层规范写法,复杂语句统一走主库,或者升级到新版本,把这类问题尽量规避掉。
还有一个很实用的功能是强制路由注释。你可以直接在 SQL 里写/*master*/前缀,Atlas 看到这个注释就会把这条 SQL 无条件送到主库。这在处理“允许最终一致,但刚写完立刻要读到准确数据”的场景下特别有用。比如用户支付成功后的订单详情页,数据必须实时准确;评论列表可以走从库,允许延迟几秒。别把所有读流量都压到主库,也别迷信“从库永远不延迟”,要根据业务容忍度理性分流。
2.2 分表功能:比你想的要简单,也没你想的强大
Atlas 的分表功能和 MyCat 这类重度分库分表产品不一样,它是在单库内部做分表,不做跨库分片。意思是说:一张逻辑表user_info会按照你的规则映射到user_info_0、user_info_1、user_info_2等物理表,但所有这些物理表都在同一个 MySQL 实例或同一组主从上。所以在数据量还没到“单库容量已经撑不住”的情况下,这个能力尤其有用。
分表规则的配置在tables.cnf里,语法格式是:
逻辑表名 = 分表字段:分表方式:分表总数比如:
user_info = id:hash:4 order_record = user_id:range:12第一种是 Hash 分片:按照分表字段计算哈希值,再对分表总数取模,决定数据落到哪张物理表。第二种是 Range 分片:把字段值的范围切分成若干段,每段对应一张表。Hash 分片适合id这种均匀分布且没有明显区间的字段;Range 分片适合时间、日期这类天然有区间特征的字段,做冷热数据分离方便。从线上经验看,除非有明确的时间维度查询需求,否则绝大多数场景都建议优先用 Hash,因为分布更均匀,不容易出现某张表数据量特别大导致的分片热点。
配置好之后,Atlas 会在收到 SQL 时做改写。你应用层写的还是SELECT * FROM user_info WHERE id = 123,Atlas 会算出 123 应该到user_info_3这张物理表上,然后实际去执行SELECT * FROM user_info_3 WHERE id = 123。这个过程对应用完全透明,Java 这边装的还是普通的 MySQL 驱动,连接串指向 Atlas 的地址和端口就行。
但分表这件事,水比想象中深。Atlas 的分表有不少使用边界,你必须心里有数:
一是查询条件必须带上分表字段。如果应用写了一条SELECT * FROM user_info WHERE name = 'xxx',Atlas 无法确定该去哪张物理表,它的处理方式是发到所有物理表再合并结果。功能上没问题,但这是“扫全表”的操作,数据量大了性能会很难看。所以分表字段的选择要非常谨慎,必须是业务查询里“必然出现”的字段。
二是跨表 JOIN 基本不可行。你要 join 的两张表如果分片规则不同,Atlas 没法在代理层做数据合并。因此分表设计通常在业务规划期就把关联关系拆干净,尽量做到单表内闭环;实在做不了的就让应用层自己拼装。
三是插入数据必须携带分表字段。没有分表字段,Atlas 不知道新数据落在哪张表,直接报错。这类错误在联调阶段非常常见,通常错误信息也不会特别友好,所以业务上线前最好把分表字段作为必输项做校验。
四是分表之后若要调整分表数量,原生是做不到的。比如一开始分了 4 张表,数据量上来了想扩到 8 张,Atlas 不会自动迁移数据,需要你自行做数据重分布。我见过有团队把分表数量当成预留容量来设计的,一上来就分 64 张表,物理表先建好,哪怕现在只有 1 万条数据也无所谓——这是最省事的规避扩容风险的思路。
2.3 管理接口:运维人员的另一个入口
Atlas 的运维通过一个独立的管理端口来做,不依赖 SSH 登录服务器,这设计我非常喜欢。你只需要用 MySQL 客户端连接管理接口,就能查看和修改后端节点状态。
管理接口默认端口是 2345(这是配置文件里的admin-address决定的),连接方式和普通 MySQL 没区别,只是用户名密码不再是业务账号,而是配置里的 admin 账号:
mysql -h127.0.0.1 -P2345 -uadmin -p123456在这个管理接口里,最常用的几个操作是:
查看后端节点状态:
SELECT * FROM backends;输出结果大致长这样:
+-------------+-------------------+---------+------+-------------------+ | backend_ndx | address | state | type | connected_clients | +-------------+-------------------+---------+------+-------------------+ | 1 | 192.168.1.10:3306 | up | rw | 0 | | 2 | 192.168.1.11:3306 | up | ro | 0 | | 3 | 192.168.1.12:3306 | up | ro | 0 | +-------------+-------------------+---------+------+-------------------+state 是节点状态,up 表示正常在线,down 表示已下线;type 字段 rw 表示主库,ro 表示从库。想做变更操作时有几个常用指令:
SET OFFLINE 3; -- 平滑下线编号为 3 的从库 REMOVE BACKEND 3; -- 从后端列表删除编号 3 的节点 ADD MASTER 192.168.1.20:3306; -- 动态添加主库 ADD SLAVE 192.168.1.13:3306; -- 动态添加从库 SAVE CONFIG; -- 把当前配置持久化到配置文件这里我要特别说一个细节:SET OFFLINE和REMOVE BACKEND的区别。SET OFFLINE是把节点标记为下线,但节点还留在列表里,Atlas 会等这个节点上的存量连接自然释放完;REMOVE BACKEND则是直接把这个节点从后端列表里移除,后续路由不会再分配新连接过去。正确的运维顺序应该是先SET OFFLINE,等 connected_clients 变成 0,再REMOVE BACKEND。如果你上来就 REMOVE,还存在活跃事务的连接可能被强行断开,应用侧会偶发 connection lost 报错。
3. 从零部署 Atlas:配置逐行注释与踩坑笔记
讲完架构、设计和核心机制,接下来终于是动手环节。我从安装依赖、编译配置、参数选型到启动检查,完整走一遍。这里先说明一下:下面的部署步骤基于我长期在 CentOS 7 和 Linux 服务器上的实际操作,大部分内容直接适用于常见 Linux 发行版。如果你的生产环境是纯内网、没有外网访问,需要提前把依赖包和 Atlas 安装包离线准备好。
3.1 基础依赖与安装方式
Atlas 底层依赖 libevent 和 glib2,安装前先把系统环境检查一下。Debian/Ubuntu 系和 CentOS 系的命令不太一样,但无非就是这几个包:gcc、make、autoconf、automake、libtool、libevent-dev、libglib2.0-dev。CentOS 上对应的包名可能是 libevent-devel、glib2-devel。
官方提供了两种安装方式:一种是用编译好的 RPM 包直接装上;另一种是源码编译。前者只适用于 RPM 系的系统,后者适用于所有环境。我个人推荐源码编译,因为可控性更强,且遇到问题排查起来方向更明确。整个编译安装流程如下:
# 1. 下载源码包(以 v2.2.1 版本为例) wget https://github.com/Qihoo360/Atlas/archive/refs/tags/v2.2.1.tar.gz # 2. 解压 tar zxvf v2.2.1.tar.gz cd Atlas-2.2.1 # 3. 生成 configure 文件 autoreconf -i # 4. 配置安装目录,--with-mysql 指向 MySQL 的 include 路径 ./configure --prefix=/usr/local/mysql-proxy --with-mysql=/usr/include/mysql # 5. 编译并安装 make make install编译时间取决于机器性能,一般几分钟到十几分钟不等。编译过程中最常见的坑是缺少依赖报错,看到configure: error: ... libevent ... not found之类的提示,先别急,按提示补齐对应的-devel或-dev包再重新执行 configure 就行。
装完之后,/usr/local/mysql-proxy目录下会看到bin、conf、log等子目录。bin里面有两个重要工具:mysql-proxy是启动主程序,encrypt是用来生成密码密文的工具。
3.2 配置主文件逐行讲解
Atlas 的配置集中在conf/mysql-proxy.cnf,注意不是my.cnf。这是一个 INI 风格的文件,我用一个完整示例带你逐行过:
[mysql_proxy] # 管理接口的账号密码 admin-username = admin admin-password = 123456 # 代理服务监听的地址和端口,应用连的就是这个 proxy-address = 0.0.0.0:1234 # 管理接口监听的地址和端口,DBA 连的就是这个 admin-address = 0.0.0.0:2345 # 后端主库(可写实例),可以配置多个,但同一时间只有一个能作为主库 proxy-backend-addresses = 192.168.1.10:3306 # 后端从库(只读实例),可配置多个 # 后面 @1 和 @2 是权重,数字越大被分配到的读流量比例越高 # 语法是 ip:port@权重,多个从库之间用逗号分隔 proxy-read-only-backend-addresses = 192.168.1.11:3306@1,192.168.1.12:3306@2 # 用户密码文件,存的是代理层允许访问数据库的业务账号 pwds = /usr/local/mysql-proxy/conf/pwd.txt # 分表规则文件 tables = /usr/local/mysql-proxy/conf/tables.cnf # 日志目录和日志级别 log-path = /usr/local/mysql-proxy/log log-level = message # 工作线程数,一般建议设置为 CPU 核数的 2~4 倍 event-threads = 8 # 最大连接数 max-connections = 512 # 连接池相关:空闲连接超时时间(秒) # 这个参数控制代理和后端之间的连接保持时长 # idle-timeout = 86400配置文件里有几个参数你必须得懂它的底层逻辑,不能只会抄。
proxy-backend-addresses是主库。Atlas 在 TCP 层并不知道 MySQL 的复制拓扑,它只是根据你的配置,把rw类型的请求发给它。如果这里配置了多个地址,Atlas 默认第一个是主库。主库状态变更时,通常需要用管理接口动态调整,而不是只改配置文件重启。
proxy-read-only-backend-addresses是从库列表。这里面的@1、@2表示权重,不是端口,也不是序号。Atlas 分配读流量时,会按照权重比例来:配置了@1和@2,那第二个从库分到的读流量就是第一个的两倍。这个机制特别适合“从库配置高低不同”的场景,比如新从库硬件好一点,就把权重调高;老机器跑不动,权重调到 0 都行——前提是这个从库“只读请求可以暂时不放量”。
pwds指向的是用户密码文件。这个文件格式要先提一下,因为很多人第一次在这里栽跟头。它是一个纯文本文件,每行一个账号,格式是用户名:密码密文,可选第三个字段@客户端IP表示只允许特定来源 IP 使用这个账号。密码密文需要用 Atlas 自带的 encrypt 工具生成:
# 在 mysql-proxy 的 bin 目录下执行 ./encrypt your_password它会输出一串加密后的字符串,你把原始密码和密文对应关系记录下来,填进 pwd.txt,例如:
app_user:8f2d8f2b2d4381a9b6102e4b0a5f7b8c@192.168.1.0/24 read_user:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6这里要提醒一句:Atlas 的这个加密算法在实现上是可以逆推的,所以 pwd.txt 文件本身不算绝对安全的敏感文件。生产环境上要严格控制这个文件的读写权限,别一不小心chmod 777,那就等于把数据库口令挂门口了。
event-threads是代理的工作线程数量。这个参数直接决定 Atlas 能并发处理多少客户端请求。设置太小,CPU 利用率上不去,请求排队;设置太大,线程切换开销反而拖慢速度。我一般建议先看机器核数:4 核就设 8,8 核就设 16,然后压测观察 CPU 使用率,再微调。
max-connections是代理层接受的最大连接数,超过之后新的连接会被拒绝。这个值不要孤立地随便填,要结合后端 MySQL 的max_connections一起考虑。如果 Atlas 设了 512,后端 MySQL 只允许 200,那从库连接数一旦超了,MySQL 会先挂。按我的习惯,Atlas 的max-connections要小于后端 MySQL 的总连接上限,留出 20% 的余量给直连管理、监控等其他通道。
3.3 分表配置文件与启动检查
分表规则写在tables.cnf里,文件内容和 MySQL 注释格外相似,但并不是所有语法都支持。Atlas 的分表规则文件实际是一个 Lua 风格或者key = value风格的简单配置,不要在里面写CREATE TABLE,只写规则映射。例如:
user_info = id:hash:4 order_record = create_time:range:12上面的规则含义是:逻辑表user_info按id字段做 Hash,总共 4 张物理表;逻辑表order_record按create_time字段做 Range,总共 12 张物理表。物理表的命名规则是逻辑表名加下划线加序号,如user_info_0、user_info_1、user_info_2、user_info_3。
规则文件改完后,需要重启 Atlas 才能完全生效,或者用管理接口做热加载——但热加载分表规则这个操作在不同版本里支持度不一,稳妥起见还是重启。
配置完毕之后,启动前有两件事必须做。第一件事:改配置文件的权限。Atlas 的设计很严格,mysql-proxy.cnf中含有明文的管理密码和数据库密码,如果文件权限是全局可读的,它会拒绝启动。你需要在启动之前执行:
chmod 660 /usr/local/mysql-proxy/conf/mysql-proxy.cnf chmod 660 /usr/local/mysql-proxy/conf/pwd.txt chmod 660 /usr/local/mysql-proxy/conf/tables.cnf第二件事:确认日志目录存在且有写权限。用默认配置的话,日志会写到log-path指定的目录,如果目录不存在,Atlas 启动会直接报错:
mkdir -p /usr/local/mysql-proxy/log完成之后就可以启动了:
# 前台启动,适合第一次调试,日志直接打到终端 /usr/local/mysql-proxy/bin/mysql-proxy --defaults-file=/usr/local/mysql-proxy/conf/mysql-proxy.cnf # 确认无报错后,用 nohup 或者 systemd 托管到后台 nohup /usr/local/mysql-proxy/bin/mysql-proxy --defaults-file=/usr/local/mysql-proxy/conf/mysql-proxy.cnf >/dev/null 2>&1 &第一次启动强烈建议先用前台模式跑一下,看到日志里没有任何 error 再转后台。启动后的第一次健康检查,至少覆盖这几项:
第一,进程是否存活:
ps -ef | grep mysql-proxy | grep -v grep第二,代理端口是否监听:
ss -lntp | grep 1234 ss -lntp | grep 2345第三,能否通过代理端口正常连上数据库:
mysql -h127.0.0.1 -P1234 -uapp_user -p'password' -e "select 1;"如果上面三条都通了,恭喜你,Atlas 代理已经进入了可用状态。
4. 日常运维与性能调优:线上稳定运行的关键细节
很多团队把 Atlas 部署起来之后,以为就万事大吉了。实际上,中间件是一个“上线容易、维护难”的组件,日常工作里涉及的运维细节非常多。这一章我分享几个核心运维场景和我的实际操作经验。
4.1 从库节点上下线的标准作业姿势
规划后端变更是一件很常见的事情,比如从库要升级硬件、要换机器、要调整 MySQL 参数,这个时候不可能直接停应用,需要在 Atlas 层做平滑操作。我建议你形成一个标准作业流程,别每次凭着记忆敲命令。
上来先把当前后端状态查一遍:
SELECT * FROM backends;假设要下线编号为 3 的从库,第一步先把节点置为 offline:
SET OFFLINE 3;执行之后,Atlas 会把该节点从路由表里去掉,但已存在的连接还维持着。之后你隔一段时间查一次connected_clients,等到它变为 0,说明所有存量请求都结束了,这个时候再执行最终的移除操作:
REMOVE BACKEND 3; SAVE CONFIG;这里头有个技巧:如果SET OFFLINE之后connected_clients长时间不为 0,多半是应用连接池里有长连接长期空闲导致。这时候不用干等,直接看应用连接池的空闲超时时间,或者主动让应用方发一次连接池重启即可。另外,Atlas 对长连接的处理不像 LVS 那样有优雅关闭的机制,所以严格来说“平滑下线”只是尽可能降低影响,无法做到 100% 无感知。
4.2 主库故障切换时需要做好的额外动作
Atlas 本身不负责主从自动切换,它只是按配置把写流量指向某个后端。所以当主库发生宕机时,你需要有一个外部的切换机制。我在生产环境中通常的做法是:数据库层面用 MHA 或 Orchestrator 完成主从切换,选出新主库,然后把 Atlas 后端列表里的主库地址更新掉。这个动作可以手工执行,也可以由自动化脚本调用管理接口完成。
此时容易出现的问题有两个。第一个是切换脚本忘记更新 Atlas,导致前端应用还在往已经宕机的旧主库写数据,报一堆连接错误;第二个是更新之后没有做SAVE CONFIG,于是 Atlas 一重启,配置又回到老地址,写流量再次命中已经没问题的新主库时才会恢复,中间却出现了配置漂移的困惑。因此,我建议把主库切换脚本和 Atlas 节点更新脚本写在同一套发布流程里,并且每一次切换后都做一次配置校验:
SELECT * FROM backends;同时检查配置文件里的proxy-backend-addresses是否已经是新主库。
4.3 性能调优的关键参数与监控方式
Atlas 到 MySQL 之间用的是标准 MySQL 协议,所以你在连接 Atlas 后,照样能执行SHOW GLOBAL STATUS查看很多运行时期的状态变量。一些 Atlas 自定义的状态变量名以Atlas_开头,这些对定位问题很有帮助。比如:
SHOW GLOBAL STATUS LIKE 'Atlas%';你会看到类似Atlas_client_connections、Atlas_server_connections之类的计数项,能直观反映当前代理层的连接规模,监控系统可以定时拉取这些数据,作为容量评估的依据。
性能调优方面,我整理几个实际有用的点:
第一,event-threads以及操作系统的文件描述符上限要一起调。高并发下 Atlas 打开的 socket 数量非常多,如果ulimit -n默认是 1024,连接数一上来,就报Too many open files。建议把nofile调到 65535 以上:
ulimit -SHn 65535第二,合理利用从库权重。从库的硬件性能不会是均匀的,有的机器 SSD 快一些,有的老机器机械盘慢一些。我在配置里会给性能好的从库更高的权重,让读流量尽量打在有能力的机器上,而不是所有从库平均分配。这个权重对业务是透明的,随时用管理接口调整并SAVE CONFIG即可。
第三,减少代理层的网络损耗。Atlas 和应用之间的网络延迟、Atlas 和后端 MySQL 之间的网络延迟,都会被加到每一次查询的 RT 上。最理想的部署形态是应用服务器、Atlas、MySQL 三层都在同一个内网机房或同一个云 VPC 内,避免跨地域调用。
第四,对后端 MySQL 做好连接数配额。在没有 Atlas 的时候,应用直连 MySQL,每个应用连接数不受控;上了 Atlas 之后,应用只连 Atlas,后端 MySQL 的连接全部来自 Atlas。这其实是个好事,因为连接被统一收敛了,但也要注意:如果多个业务共用一个 Atlas 实例,连接数会聚合放大。我的做法是给不同业务分配不同的代理端口或者不同的proxy-backend-addresses,在 Atlas 层就做好连接隔离,避免一个业务把连接池占满拖死其他业务。
5. 常见问题与避坑指南:把线上的教训一次性说给你听
最后这部分,我从这些年实际运维中筛了一批高频坑。老读者都知道我的习惯,文章最后一定是要把踩过的坑摊开来讲,因为这些问题如果你不知道,踩进去再看文档,往往已经晚了。
常见问题速查表
| 现象 | 根本原因 | 处理方法 |
|---|---|---|
| Atlas 启动直接退出,日志提示 permission denied | 配置文件权限过于开放 | chmod 660所有 conf 下的敏感文件 |
| 应用连接 Atlas 提示密码错误 | pwd.txt里的密文格式错误,或账号未配置 | 用encrypt重新生成密文,核对格式 |
| 写完数据立刻查询查不到 | 主从延迟导致读走了从库 | 关键读请求加/*master*/强制路由主库 |
| 从库节点下线后连接数一直不降 | 应用连接池保留了空闲长连接 | 检查应用连接池空闲超时,做一次池重建 |
| 主库故障切换后应用仍连旧主库 | Atlas 后端列表未同步更新或未SAVE CONFIG | 更新proxy-backend-addresses并执行SAVE CONFIG |
| 分表查询报找不到物理表 | 分表规则配置错误,物理表名不一致 | 核对tables.cnf和实际建表语句 |
高并发时报Too many open files | 系统文件描述符上限太低 | ulimit -n调大,或改 limits.conf |
| 某个从库慢查询拖慢整体 | 从库硬件差但权重过高 | 调低该从库的@权重,或暂时下线 |
接下来挑几个值得展开的,我讲讲具体场景和判断思路。
主从延迟的经典困境。我们的一个订单系统刚接 Atlas 时出现过一个问题:用户下单后,前端跳转到订单详情页,偶尔会看到“订单不存在”。查了半天,发现订单写主库后立即去读,读请求被分流到了从库,而从库复制延迟了大约 1-2 秒。当时我直接在订单详情页的所有查询前面加了/*master*/注释,问题立刻消失。当然这不是唯一解法,你也可以在事务里执行读,但更轻量的方式就是强制路由。需要说明的是,这个/*master*/注释是 Atlas 特有关键字,要求 SQL 开头就带这个注释,且中间不能有空格分隔缺失,所以应用改造时最好封装成一个统一方法,别到处手写。
分表规则配置错的隐蔽场景。有次维护一个老系统,发现部分查询走代理时偶尔报“table doesn't exist”,但直接连 MySQL 又正常。最后定位发现是 Atlas 分表规则把逻辑表名映射到的物理序号区间和实际建表表名对不上,比如规则写的是user_info = id:hash:8,但库里只建了user_info_0到user_info_3四张物理表,命中 4-7 的请求自然就报错。这类问题在配置里很难直接发现,因为没有强校验。所以上线前最好自己写个脚本,造几条测试数据分别打到各物理分片,全部跑通了再切流量。
大事务和大结果集不要走代理。Atlas 是代理,所有数据都要经它转发。如果一个后台任务发起一次UPDATE涉及 100 万行,事务日志和行锁相关信息都要经过代理,代理的内存和 CPU 很可能瞬间被打满,进而影响其他正常业务。我建议把这类批量任务单独开直连通道,或者限定在业务低峰期执行,不要走 Atlas 这条公共链路。
日志怎么看。排查问题首先看/usr/local/mysql-proxy/log/mysql-proxy.log。默认log-level = message级别下,日志里能看到连接建立、后端切换、错误堆栈等信息。如果你需要更详细的分析,可以临时把 log-level 调成debug,但是生产环境建议别长期开,因为 debug 日志量非常大,很容易把磁盘打满。
最后说点实际的
Atlas 这套东西,用了这么些年,我的体会是:它不适合作为一套“万能数据库网关”来期待,但你如果正好处在一个传统主从架构、读写分离需求明确、短期没有分库分表计划的阶段,它确实能用一个非常轻的代价把事情办好。反而是那些功能更全、更重的中间件,上手成本高,排障链路长,小团队很容易陷进去。
一个小建议:在正式接入 Atlas 之前,把它的定位和边界想清楚,特别是分表字段的选择和后端节点动态切换的预案。这些东西比安装配置本身更重要,提前想明白,后面能少踩一大半坑。