☰
MySQL中间件Atlas实战:从读写分离到分库分表的踩坑复盘
2026/10/19 8:49:16 网站建设 项目流程

第一次接触 Atlas,是因为当时线上 MySQL 的压力到了一个非常尴尬的临界点:核心订单表单表数据接近一亿行,平时的写入量虽不至于要命,但读请求的峰值能直接把主库 CPU 打到 90% 以上。团队里当时分了两派,一派说直接把表拆了,另一派说应该换 NewSQL,讨论了几轮都没结论。后来一个朋友问我们,为什么不先在中间层加一个 Atlas 试试。

说实话,我在那之前对 Atlas 的印象只停留在“360 开源的一个 MySQL 中间层”,并不觉得它有多特别。但等真正啃完文档、在压测环境里跑通,再把它一点点推到生产之后,我意识到这个工具的价值被严重低估了。这篇文章不是官方文档的复述,而是我从选型、部署、分表规则设计到线上踩坑的完整复盘。如果你手里也压着一个单表过大、主库读写吃紧,暂时又没精力上大规模分布式数据库的 MySQL 项目,这篇应该能给你省下不少时间。

1. Atlas 是什么:一个能替你挡在前面处理脏活的 MySQL 管家

1.1 用最朴素的话讲清楚 Atlas 的定位

Atlas 是奇虎 360 开源的一个基于 MySQL 协议的数据库中间层,简单说,它站在你的应用和 MySQL 实例之间,应用连接它,它再把请求转发给真正的 MySQL 数据库。它的核心能力是读写分离、分库分表、负载均衡,以及基本的路由转发。

如果你用过 Nginx,那就很好理解了:Nginx 是 HTTP 层的反向代理,Atlas 就是 MySQL 协议层的反向代理。应用不用关心后面到底有几台数据库、哪个库分了几张表,它只需要像连普通 MySQL 一样连 Atlas 就行。底层数据库是 5.6 还是 5.7,主库在哪、从库有几台,对应用来说完全透明。

我记得当时我们给团队做技术分享时打过一个比方:Atlas 就像一个餐厅前台。客人(应用)不需要知道后厨有几个灶台、哪个灶台在做哪道菜,只要把菜单(SQL)递给前台,前台会自己决定这道菜该由哪个灶台做,做完再由他端给你。主库就是大厨,从库就是帮厨,菜品简单就让帮厨干,复杂硬菜必须让大厨动手。

1.2 它的核心功能拆解

Atlas 的功能细细拆下来,主要有四块。

第一块是读写分离。它会把select这类读请求转发到从库,把insert、update、delete这类写请求转发到主库。这是它的基础能力,配置上只需要指定主备库的地址和权重即可。第二块是分库分表。它支持按某个字段的哈希值或范围,把数据分散到多个库、多张表中。这个能力对单表过亿的场景特别管用。第三块是负载均衡。如果后面挂多台从库,它能把读请求按权重打散到不同的从库上,避免某一台从库被打满。第四块是基本的安全控制。它可以配置允许连接的客户端 IP、SQL 白名单等,相当于在数据库前面加了一道轻量防护。

需要说明的是,Atlas 不是一个完整的数据库,也不负责存储数据,它只是把请求合理地分配给你底层的 MySQL 实例。真正存数据、算数据、扛数据量的是 MySQL 本身,Atlas 做的是调度和路由。

1.3 什么样的项目适合引入 Atlas

根据我的实际经验,下面这几类项目非常适合用 Atlas。

  • 读写比例悬殊,比如线上业务读占了 80% 以上,主库压力主要来自读请求,但从库一直闲着。
  • 单表数据量到了几千万甚至上亿,查询开始明显变慢,但还没到需要引入整套分布式数据库引擎的程度。
  • 团队里没有专门的基础设施组,没法从零自研代理层,需要找一个能快速上手、部署成本低的中间件。
  • 项目已经用了 MySQL,不方便把底层存储整个替换掉,只能从架构中间层想办法。

当然,也不是所有项目都适合。如果业务还没起来,单表只有几百万行,主库毫无压力,这时候引入 Atlas 就是纯属给自己找事。中间层一定会带来额外的网络跳数和排查复杂度,没有性能压力就不要硬上。

2. 为什么选 Atlas:中间件选型时的思考过程

2.1 同类方案对比

当时市面上能选的 MySQL 中间件其实不少,我特地拉了一张对比表,把主流方案放在一起看:

方案部署复杂度分库分表能力读写分离活跃度适合场景
Atlas低支持,规则较朴素支持官方维护节奏慢,社区靠老用户轻量分表、读写分离、老系统改造
MyCat中等支持,规则丰富支持国内社区曾经很热闹需要复杂分片规则的场景
ShardingSphere中等偏高很强,JDBC/Proxy 双模式支持目前最活跃新系统、Java 技术栈、复杂分片策略
Vitess高很强,几乎是一个分布式数据库支持很活跃超大规模,有专业团队维护

从功能矩阵看,Atlas 并不是最全能的,但选它的人大多看重另外几点:部署简单、依赖少、心智负担低、原理清晰。它没有一堆分布式协调组件,不依赖 ZooKeeper,不需要额外引入配置中心,下载编译完改个配置就能跑,对于一个小团队来说是很大的优势。

2.2 从架构演进角度看中间层的取舍

在加中间件之前,应用访问数据库的常见方式有两种:一种是直接在代码里写数据源,写死了主库或从库的地址;另一种是自己封装 DAO 层,在代码里做读写分离和分表。我们的老系统就是第二种,结果就是分表逻辑散落在各个 Service 里,今天这个订单表拆了 64 张,明天那个用户表拆了 128 张,新同事来了根本不敢动那些代码,每改一次就要互相问半天。

引入 Atlas 之后,最大的变化是把这些散落的逻辑收敛到了一个独立的层。分表规则、读写分离权重集中在一个配置文件里,应用代码不用再关心哪张表在哪个库。从架构演进角度看,这相当于把原来藏在应用里的“隐式路由逻辑”抽成了一个显式的基础设施,后续不管是换分表算法还是调整从库权重,都只需要改一处配置。

当然这个收敛也是有代价的:多了一次网络代理的跳数,SQL 里不能再用一些依赖单库特性的写法,排查问题时要多查一个组件。但从整体上看,利大于弊。

2.3 Atlas 相对轻量的路线

和 MyCat 这类功能大而全的中间件相比,Atlas 最大的特点就是克制。它没有把一百种分片策略都塞给你,提供的核心能力就那么几个,规则也很直白。这反而让运维变得比较简单。

举一个细节:MyCat 的配置如果是第一次接触,需要同时理解 schema.xml、rule.xml、server.xml 三套文件分别管什么,还要搞明白逻辑库和物理库之间的映射关系,对新人来说有个学习曲线。Atlas 则是把主要配置都集中在一个 cnf 文件里,表名、分片键、分表数量直接列出来,理解成本低很多。对于一个资深工程师来说可能无所谓,但如果你团队里还有刚毕业的新人,这个差距就很重要了。

3. 一条 SQL 穿过 Atlas 时,它到底做了什么

3.1 连接管理与会话保持

应用连上 Atlas 之后,Atlas 会先和底层 MySQL 建好连接池,然后把前端连接和后端连接做一个映射。我们当时压测时观察过,应用每次发起一个查询,不会直接新建一条到 MySQL 的连接,而是从连接池里复用已有的连接,整体连接数比直连数据库时小很多,主库上max_connections的压力显著下降。

会话保持这一点在一些特殊场景下要格外注意。因为 Atlas 是代理层,一个前端的会话请求经过了 Atlas,在某些情况下可能被路由到不同的后端连接上。对于普通的select没有影响,但如果业务里用了session-level的临时表或者set命令设置会话变量,就要小心了。比如你在一个会话里先set @user_id = 123,下一条 SQL 再用这个变量,路由到不同的后端连接时,变量可能就丢了。我们当时为了避免这个问题,代码里严格禁止在业务 SQL 中使用会话级变量。

3.2 SQL 解析与路由计算

这是 Atlas 最核心的部分。当一条 SQL 进来之后,它会先解析 SQL 语句类型,判断是读还是写,然后再根据表名去找对应配置的分片规则,算出这条数据应该落在哪张分表上。

举个例子,假设我们把订单表t_order按user_id分了 16 张表,分表规则是user_id % 16。应用发来一条select * from t_order where user_id = 20240414,Atlas 会先计算出20240414 % 16 = 6,然后把 SQL 改写成select * from t_order_6 where user_id = 20240414,最后转发到对应的后端库执行。如果 SQL 里的条件没有带分片键,比如where order_no = 'xxx',那 Atlas 就没有办法精确定位到某一张表,只能把这条 SQL 广播到全部分表上执行,然后再合并结果。

这个“广播”机制在数据量小、分表数量不多的时候还能接受,但如果分到了 64 张表、128 张表,一次全表扫的代价就会非常大。所以我们当时的铁律是:所有涉及分表的业务查询,条件里必须带上拆分键,否则禁止上线。

3.3 读写分离的逻辑

路由到具体库的环节,读写分离逻辑会起作用。Atlas 会维护一个主库和多个从库的列表,写 SQL 固定给主库,读 SQL 根据权重随机或轮询地分发给从库。

这里有个容易踩的坑:默认情况下,Atlas 对主从库的区别主要靠配置里的角色标记,它不会自己去检测主从复制的延迟。也就是说,如果你主库刚写入一条数据,从库还没同步完成,此时应用立刻发起读请求,可能读到旧数据。我们当时的对策是在业务层做了一个很短的时间兜底:刚写完需要立刻读回的场景,强制走主库,或者在前端加一层短暂缓存,避免直接命中有延迟的从库。等数据最终一致再走常规读路径。

3.4 返回结果的合并:并非全自动

Atlas 返回结果给应用时,如果 SQL 被广播到多张分表执行,它需要把多个结果集合并。合并这个动作听起来简单,实际很讲究,尤其是带order by和limit的情况。

比如查询select * from t_order where status = 1 order by create_time desc limit 10,由于没有带分片键,被广播到 16 张表上。每张表都要各自执行order by create_time desc limit 10,Atlas 拿到 16 个结果集之后,需要把所有结果合并起来,再做一次全量排序,然后截取前 10 条返回。

这里能发现问题:如果每张表只返回了 10 条,最后排序之后的“前 10 条”并不一定是全局真正的前 10 条。假设某一瞬间表 6 的最新一条记录排在全球第 15 名,那它就被提前裁掉了,最终结果就不准确。Atlas 对这个问题的处理能力有限,做不到像 ShardingSphere 那样精确地做全局改写和归并。因此我们的经验是:跨分片查询尽量只做统计类或小数据量查询,尽量避免精确的大结果集排序分页,必要时把分页改成基于游标的方式。

4. 从编译到上线:Atlas 部署与配置的完整过程

4.1 编译安装与目录规划

Atlas 的安装方式有两种:直接下载官方预编译包,或者从源码编译。我们当时为了能打自己的小补丁,选择的源码编译。整体依赖不算多,只要有 MySQL 客户端库、glibc 基础库,在常见 Linux 发行版上都能编过。

建议先规划好目录,不要装在稀奇古怪的地方。我们当时的目录结构如下:

/opt/atlas ├── conf │ └── atlas.cnf ├── bin │ └── atlas ├── logs └── run

编译完成之后,可执行文件放到/opt/atlas/bin,配置文件放在conf目录。这样做的好处是后续如果要升级或回滚,整目录打包替换就行,不至于搞乱系统的其他路径。

4.2 配置文件的关键项解析

Atlas 的核心配置文件里,最要紧的是下面几项:

  • 代理服务监听的地址和端口:应用连接 Atlas 时使用的端口,比如0.0.0.0:1234。
  • 管理端口:DBA 用来查看 Atlas 状态、在线修改配置的端口,跟业务端口分开,避免暴露给应用。
  • 后端 MySQL 地址列表:需要把主库和从库的 IP、端口、权重都列出来,并标记角色。
  • 分表规则:指定哪些表需要分表,用什么字段作为分片键,分多少张表。
  • 访问白名单:限制哪些客户端 IP 可以连接。

当时我们的一个经验是:配置里的权重值不要拍脑袋填,最好根据从库的硬件配置来定。两台从库,一台是 16 核 64G,一台是 8 核 32G,权重填 2:1 就比填 1:1 合理。压测环境里可以开着监控观察连接分布,再微调。

4.3 启动、验证与基本运维命令

启动 Atlas 很简单,二进制执行起来就能跑。但我们验证它是否正常工作,从来不只看进程有没有起来,而是做三步检查。

第一步,在管理端口连上去,执行show databases之类的命令,确认 Atlas 能正常拿到后端的库列表。第二步,从应用服务器模拟一次读写请求,分别走读库和写库,确认路由方向和预期一致。第三步,直接用 tcpdump 或 MySQL 的通用日志,在底层数据库上看是否存在来自 Atlas 的错误的 SQL 转发。这一步很笨,但能发现很多配置上的低级问题。

关于日志,一定要把 Atlas 的日志级别调成合理档位。生产环境日志级别太低会刷出大量调试日志,把磁盘写满;级别太高出了问题又看不到有效线索。我们最后是保留 info 级别,并配合 logrotate 做日志轮转。

4.4 平滑上线的操作顺序

我们上线的时候做了一个还算稳妥的步骤:先让一台不重要的应用服务器把数据源切到 Atlas,跑几天观察;确认稳定之后,再逐步把其他应用服务器的连接切换过去。整个过程没有选择某个凌晨一次性全部切换,因为那样一旦出问题,回滚范围太大,所有人都会被折腾得一宿别睡。

切换期间最需要注意的是连接的释放。很多中间件切换之后不会立刻断开已建立的旧连接,而是等它自然超时或应用重启,这时候新旧链路并存。我们当时利用这个特性观察了两套链路的流量比例,确认新的链路没有报错之后,才重启应用完全释放旧连接。

5. 分库分表不是把表拆开就完事:规则与设计经验

5.1 拆分键的选择是分表成败的关键

分库分表最怕的不是技术不会,而是拆分键选错,导致后续所有查询都变成广播查询。拆分键的选择标准其实是老生常谈,但我还是要强调:一定要选择业务查询中最常见、最稳定的那个筛选字段。

拿订单场景举例,最常见的查询是“查某个用户的订单列表”,所以按user_id拆分是合理的。但如果你经常需要通过order_no精确查询订单详情,而order_no又没放在分片键里,那每次查询都会变成全分片广播。我们当时的做法是额外建了一张订单号到用户的映射表,或者把user_id冗余到订单表的每个字段里,查询时先通过映射表查出user_id,再走精确分片路由。

拆分键的另一个要求是分布要足够均匀。比如用user_id % 16,如果平台的大用户只有那么几个,每个大用户产生的订单量级远超普通用户,那很可能出现某些分片数据特别多、某些分片几乎空闲的倾斜问题。如果业务上能预见到这种倾斜,可以考虑使用一致性哈希或者范围分片,让数据分布更平滑。但要注意,一致性哈希的实现复杂度比取模高不少,Atlas 对这种更复杂算法的支持能力有限,需要先做验证。

5.2 单库分表和多库分表的选择

Atlas 的分表支持同一个库内分多张表,也支持把分表分散到多个库上。两种方式在查询能力上有区别:如果只是单库内分表,那所有分表还在同一个 MySQL 实例上,单实例的 CPU 和 IO 仍然可能成为瓶颈;如果分到多库多实例,则要面对跨库 join 等问题。

我们当时没敢一上来就多库分表,因为业务里有一些关联查询,跨库之后基本没法做,所以第一步先做的单库 64 张表。等后面确确实实单库扛不住压力了,再按同样的规则拆到两个库上,数量不多,迁移也好控制。这里我给一个建议:分表方式要留好扩展余地,表名规则一开始就设计成可以平滑平移的,不要让库的物理位置写死在代码里。

5.3 分布式 ID 的生成不能依赖自增主键

分表之后,MySQL 的自增主键就不能再用了,因为多张表的自增序列会产生重复。我们早期就是没意识到这个问题,上线后才发现分表之间的主键撞了,最后只能用脚本修历史数据,非常痛苦。

分布式 ID 的生成方式有很多:雪花算法、号段模式、UUID。我们最终用的是号段模式,也就是每次从数据库申请一段 ID 区间,应用内存里按顺序分配。这种方案的优点是趋势递增,对 InnoDB 索引友好,生成的 ID 都是整数,索引占用小。雪花算法也很好,但需要保证机器 ID 唯一,一旦配置错会有风险。UUID 因为太散列、太长,在 InnoDB 里做索引会很吃亏,能不用尽量别用。

5.4 历史数据迁移与双写策略

分表规则确定之后,另一个大头是存量数据的迁移。我们当时是把停止写流量后导出一份全量数据,按新分片规则重新计算归属,再导入新表。这个过程必须做数据校验,不能光靠 DBA 说“导完了”就放行。一条简单但很有效的数据校验方法是:记录迁移前后每个分片的行数总和,再抽样对比某些关键业务字段的 hash 值。

为了降低停机时间,我们后来做了一个双写策略:业务写入的时候同时写老表和按新规则计算后的新分表,读走新表,持续一段时间之后做数据对账。对账没问题再关掉老表写入,彻底下线。双写逻辑虽然只能在应用代码里做,比较丑,但确实把停机时间压缩到了几分钟内,对线上业务影响小很多。

6. 生产环境里的那些坑:连接、事务、慢查询排查记录

6.1 连接池被打满:根因在一个被忽略的慢查询

上线一段时间后的一天下午,监控告警突然提示 Atlas 的连接数飙到了上限,紧接着应用侧开始报获取连接超时。我们第一反应是业务洪峰,于是赶紧加从库,但加了之后并没有明显好转。

后来排查发现,罪魁祸首是一条没有带分片键的统计 SQL。它被广播到 64 张分表上,每一张分表都在做大范围扫描,整体耗时接近几十秒。前端因为拿不到响应,不断重试,连接越积越多,最终把连接池全部占满。把这条 SQL 改成先通过映射表查出user_id列表,再按精确分片查询之后,连接数瞬间降了下来。

这个案例给我的教训是:中间件再强大,也兜不住代码里的烂 SQL,恰恰因为多了代理层,烂 SQL 的杀伤力会被放大很多倍。上线前一定要对 SQL 做 Review,重点排查有没有不带分片键的查询。

6.2 主从延迟引发的数据旧读:不能全靠中间件兜底

另一个高频问题是从库延迟。我们当时的从库配置不高,主库压力大的时候,复制延迟最快能到几百毫秒甚至几秒钟。应用在用户下单之后马上跳转到订单详情页,查从库很容易查到旧数据,用户就投诉“我明明下单了,怎么详情里没有”。

解决这个问题的方案我们在前面提了一句,这里展开细说:我们在 DAO 层做了一个简单的路由标签,对强一致读的查询强制走主库。判断标准很简单,只要这个请求需要在写入后立刻读回,就给它标上“走主库”,其他普通列表查询继续走从库分散压力。用这个方案之后,旧读投诉基本清零。

想提醒一点:强制走主库的接口占比一定要控制好,如果所有读都强制走主库,那读写分离就形同虚设了。可以统计一下哪些请求真的需要强一致读,把范围收敛到最小。

6.3 事务边界模糊:分库分表和分布式事务之间的尴尬

Atlas 对于单个分片上的事务是支持的,但如果一个事务里操作涉及多个分片,比如同时往t_order_3和t_order_7写数据,这就变成了跨分片事务,Atlas 并不能保证分布式一致性。当时我们梳理业务时发现,有一些看似正常的 Spring 事务,内部确实会跨分片操作多张表,只是数据量小,出问题概率低。

面对这种情况,如果没有专门的分布式事务框架,我们能做的就是从业务上规避。把所有跨分片写入的场景拆成单分片内操作,或者改造成最终一致性的消息队列方案。如果业务强依赖分布式事务,那建议认真评估 ShardingSphere + Seata 那条路,Atlas 不是为这种场景设计的。

6.4 慢查询排查路径:从应用到 Atlas 再到 MySQL

加了 Atlas 之后,排查慢查询的链路比之前多了一层,很多同事一开始不习惯,遇到问题就一头扎进 MySQL 里查慢日志,结果什么都查不到。正确的排查路径是:先看 Atlas 日志里有没有超时的 SQL,再顺着这条 SQL 到后端 MySQL 查慢查询日志,定位是索引失效还是数据倾斜。

我们当时定制了一个小工具,每天定时从 Atlas 日志里捞 top 10 的慢 SQL,汇总之后发到群里。这样不用等业务方反馈问题,我们能提前发现一些隐藏的慢查询,提前优化掉。这个习惯一直坚持到了后来的其他数据库中间件项目。中间件层做性能分析,比单纯在数据库层看慢日志更接近真实业务,因为可以关联到具体应用和分片。

6.5 故障切换:主库宕机时的处理

Atlas 本身没有完整的 HA 机制,主库宕机后它不会自动把某个从库提升为主库。我们的做法是在 Atlas 前面加了一个 VIP(虚拟 IP),后端 MySQL 用 MHA 或类似工具做主从切换,切换发生时 VIP 漂移到新的主库上。Atlas 的配置里指向 VIP,而不是物理主库地址,这样数据库节点发生切换时,Atlas 这一侧不需要做任何改动。

这个方案不算完美,切换期间会有秒级的中断,但对大多数业务来说已经够用。如果你的业务完全不能接受中断,那应该往真正的分布式数据库方向走,Atlas 加 MHA 的组合只能满足“短时间可容忍”的场景。

7. 站在今天回头看 Atlas:它还香吗

7.1 Atlas 和 ShardingSphere 的本质差异

如果今天有朋友问我新项目能不能选 Atlas,我的回答会比较谨慎。如果完全是从零开始的 Java 项目,我可能会更推荐 ShardingSphere。两者的差异不仅是功能多寡,而是设计理念不同。

ShardingSphere 提供了 JDBC 和 Proxy 两种模式。JDBC 模式直接嵌入应用,不走代理,性能损耗更小;Proxy 模式和 Atlas 类似,独立部署。分片策略上,ShardingSphere 支持标准哈希、一致性哈希、interval 范围、hint 强制路由等,归并算法也更严谨,能更好地处理跨分片排序和分页问题。再加上它对分布式事务的适配,现在的 Java 生态里确实是更顺手的选项。

但 Atlas 也有它的不可替代之处。第一,它不绑定语言,你的应用是 PHP、Go、Python 还是老掉牙的 Java,只要会用 MySQL 客户端协议就能连接。第二,它的依赖非常轻,没有 Java 运行环境的要求,部署成本低到离谱,在一个网络隔离的老旧环境里,它的易用性尤其突出。第三,它的代码结构清晰,出了问题可以直接看源码定位,不需要在一堆框架概念里绕来绕去。

7.2 哪些老系统适合继续用 Atlas

我见过程序员听到 Atlas 就摇手,说这是上一个时代的东西,恨不得立刻把线上的 Atlas 换掉。但实际情况是,很多老系统运行得非常稳定,业务没有新的分片需求,代码也没有团队愿意大改,这时候盲目迁移到新中间件反而会引入大量回归风险。

如果项目满足这几个条件,继续留在 Atlas 完全没有问题:表结构基本固定,未来没有大规模的分片策略变化;分片规则简单,无外乎按用户或按订单哈希取模;团队里有人能看懂 Atlas 的配置和日志,遇到问题知道怎么排查;底层 MySQL 版本还在 Atlas 支持的范围内。在这种“稳定压倒一切”的系统里,工具不老,合适才重要。

7.3 从 Atlas 学到的东西可以迁移到任何中间件

最后说一点私心话。就算 Atlas 有一天彻底被淘汰了,我在维护它的过程中学到的东西也不会过时,比如 SQL 解析的基本思路、分片路由的设计原则、连接池的管理方式、读写分离下的一致性问题。这些概念在 ShardingSphere、Vitess、甚至一些云数据库里都在用,只是换了一套外壳和实现语言。

我当时花了一个多星期把 Atlas 的源码粗略过了一遍,最大的收获是理解了一个看似简单的select背后,中间件层面要处理多少细节。后面再接触其他中间件,基本都能很快上手,因为它们面对的问题是同一类问题。如果你现在正好在用 Atlas 或其他数据库中间件,我建议你花点时间把它的日志格式、配置结构、路由规则彻底搞明白,这些东西才是真正值钱的资产。

我在实际运维中的体会是,中间件选型从来没有绝对的“最好”,只有“当前阶段最合适”。Atlas 帮我度过了老系统最艰难的扩容期,虽然它不再时髦,但那段经验让我在数据库架构这件事上有了更立体的判断力。如果你也对中间件有兴趣,不妨从 Atlas 开始读读源码,它比很多包装精美的商业产品更容易看透。

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

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

立即咨询