1. 先聊清楚:这个叫Atlas的项目到底在做什么
听到Atlas这个词,有人会先想到那些厚厚的地图集,有人会想起那个能跑能跳的机器人。我这边要聊的Atlas,是放在业务应用和MySQL之间的数据库中间层项目。说人话就是:它把自己伪装成一个MySQL数据库,业务端的SQL先发给它,它再决定把请求转发给真正的数据库,应用层基本感知不到这层存在。
这个Atlas能做什么?核心三件事:读写分离、连接管理、分表路由。它能解决一个很实际的问题——线上数据库的读流量远大于写流量,单库扛不住,但如果直接在业务代码里写一套主从分离逻辑,每个服务都要改,每个团队都要维护,非常不值。引入Atlas之后,开发只需要改一个连接串的IP和端口,读写分离的逻辑全部下沉到代理层,对业务代码几乎零侵入。
谁适合关注这个项目?一类是后端开发,系统到了需要读写分离但不想大动代码的阶段;一类是DBA或架构师,正在做数据库访问层的统一收口,想把连接数、SQL过滤、后端路由这些事情集中管理。即使你目前用不上,理解它的设计思路也很有帮助,因为你会发现很多数据库中间件解决的是同一批问题,只是侧重点不同。
1.1 当数据库流量大起来之后,最痛的是什么
单库单表跑到一定量级,问题会一个个冒出来。首先是连接数,MySQL默认的max_connections通常也就几百,后端服务一扩容,每个实例建几十条连接,数据库很快就拒绝新连接了。其次是读写压力,大部分业务是读多写少,一台主库又要处理写事务又要扛住所有查询,CPU和IO容易先到瓶颈。再往后是数据量,单表几千万上亿行之后,即使走索引,写入竞争和备份恢复也会越来越难受。
这时候最常规的做法是加从库,把读流量分流出去。但问题来了,从库加好之后,业务代码怎么识别哪些SQL走主库、哪些走从库?怎么处理主从延迟?怎么在某个从库宕机时自动摘除?如果这些逻辑放在每个服务里,必然是一地鸡毛。我见过不少团队在应用层写了一个AOP切面,根据方法名前缀判断读写,再手动选数据源,刚开始能用,后面前缀一乱、事务一嵌套就出问题。数据库中间件本质上就是把这类通用复杂度从业务代码里剥离开。
1.2 为什么数据库中间层能解这个局
中间层的思路很朴素:既然每个业务方都连接数据库,那就让它们先连一个代理,由代理统一处理后端路由。Atlas对外完全兼容MySQL协议,开发人员使用JDBC连接它时,和直连MySQL没有任何区别。代理拿到SQL之后,会做词法分析,判断语句类型,再结合配置好的主库、从库列表,决定把这条SQL发往后端哪台机器。
这个模式最大的价值在于集中控制。你可以在代理层统一做SQL白名单过滤、屏蔽特定语句、限制单用户并发,甚至按IP隔离访问。后端数据库扩容时,只要改代理配置,业务方无感知。很多老系统改造的核心诉求就是“尽量少改业务代码”,Atlas这类中间件正好契合这个诉求。当然,中间件不是银弹,它也有自己的局限,比如跨分片的事务和复杂join会很难办,这一点后面我会专门展开。
2. 拆解Atlas的核心功能:读写分离、连接管理和分表路由
2.1 读写分离:主库写、从库读的路由逻辑
读写分离是Atlas最成熟、用得最广的功能。配置上,写库地址放在proxy-backend-addresses,读库地址放在proxy-read-only-backend-addresses,多个从库之间可以用@权重分配流量。比如配置成10.0.1.2:3306@2,10.0.1.3:3306@1,那从库2承担的读流量大约是从库3的两倍。
路由判断的逻辑并不复杂:语句以SELECT开头,不在事务内,没有显式锁,就走从库;其余走主库。但这里有个容易被忽略的细节——事务内读一致性。如果业务在一个事务里先执行了UPDATE,再执行SELECT,按理说应该读到刚写入的数据,但如果此时SELECT傻乎乎地路由到从库,而从库同步有延迟,就会查到老数据。所以Atlas的处理策略是:同一个客户端连接一旦执行过写操作,后续的读也会粘滞在主库上,直到事务结束或连接归还连接池。这个策略很关键,不少中间件没做处理,就会导致数据漂移类的问题。
| 场景 | 路由目标 | 原因 |
|---|---|---|
| SELECT,未开启事务 | 从库(可多库负载均衡) | 不涉及数据修改,可接受短暂延迟 |
| SELECT,事务内且之前有写操作 | 主库 | 保证会话内读到最新数据 |
| INSERT / UPDATE / DELETE | 主库 | 写操作必须落在主库 |
| SELECT ... FOR UPDATE | 主库 | 需要行锁,必须走实际主库事务 |
2.2 连接管理和后端连接池:别让代理变成瓶颈
很多人以为中间层只是转发SQL,性能开销很小,但生产环境跑起来后,连接管理才是最容易出问题的地方。Atlas作为代理,同时面对两拨连接:前端是业务应用发起的客户端连接,后端是它到MySQL的连接。如果应用侧连接池配置不当,每个客户端连到代理,代理就给每个客户端建一条到后端的连接,那么代理和后端MySQL之间的连接数会被无限放大。
实际调优时,需要关注两件事。第一,控制应用侧连接池上限,比如一个应用实例的maxActive压到10到20,而不是默认的50或100;第二,Atlas自身有连接队列和超时机制,可以限制空闲连接回收周期,并限制总连接数。曾经有一个业务方把所有服务的最大连接数都调到了80,几十个实例一上线,代理和后端MySQL的连接数瞬间打满,最后在代理层限制单用户最大并发连接数才稳住。所以,代理不是无脑转发,连接复用和连接数限制这些参数一定要根据压测结果来定。
2.3 分表路由:把一张大表拆到多个后端
Atlas也支持分表,也就是把一张逻辑表按照某个字段的哈希值,分散存储到多个后端实例上。配置时,需要指定哪张表参与分片、分片键是哪个字段、后端有哪些节点。执行INSERT或SELECT时,代理根据分片键的哈希值算出应该路由到哪个分片。
分表功能听着简单,实际用起来限制不少。最大限制是:查询条件里如果不带分片键,代理就只能把SQL广播到所有分片,然后合并结果集。这个“广播”操作非常昂贵,表一旦被拆到8个分片,一次不带分片键的查询就等于同时跑8条查询,性能急剧恶化。除此之外,跨分片的JOIN、全局唯一ID、跨分片分页排序,都是需要业务方自己解决的。所以我的建议是:在决定分表之前,先梳理业务SQL,确保绝大多数查询都带了分片键,否则谨慎使用。
3. 从零落地一套Atlas:完整实操流程与配置要点
3.1 部署方式怎么选
Atlas是C语言实现的,官方会提供编译好的二进制包,部署相对简单。比较推荐的方式是找两台独立机器或虚拟机,分别部署Atlas实例,前面用负载均衡(比如LVS、HAProxy或云上的SLB)提供统一入口。这样即使一台代理宕机,另一台也能接管流量。
部署位置上,要离数据库近一些,最好同机房甚至同网段,因为代理和后端DB之间每次SQL交互都有网络开销,延迟太高会直接影响性能。不要把Atlas和MySQL装在同一个机器上,代理层虽然不算太吃资源,但高并发下CPU和内存占用会明显上升,和数据库抢资源很不划算。
3.2 一份能跑起来的配置长什么样
不同版本、不同发行方的配置字段会略有差异,但整体结构长这样。以下是我在生产环境用过的配置框架:
[mysql-proxy] admin-username=admin admin-password=your_admin_password # 对外提供MySQL服务的地址和端口 proxy-address=0.0.0.0:1234 # 管理端口,DBA通过这里执行管理命令 admin-address=0.0.0.0:2345 # 写库(主库)地址 proxy-backend-addresses=10.0.1.1:3306 # 读库(从库)地址,@1表示权重 proxy-read-only-backend-addresses=10.0.1.2:3306@1,10.0.1.3:3306@1 # 客户端连接账号,密码需要加密 pwds=proxy_user:加密后的密码串 # 后台运行,自动拉起 daemon=true keepalive=true # 事件线程数,一般等于CPU核数 event-threads=4 # 日志配置 log-level=message log-path=/data/atlas/log sql-log=REALTIME # 字符集 proxy-charset=utf8有两个地方特别提醒。第一,pwds后面的密码不是明文,需要用Atlas自带的加密工具生成,拿到加密串之后再写进配置。第二,proxy-address是业务方连接的端口,admin-address是DBA管理端口,两者不要混淆,管理端口一定不要暴露到公网。
3.3 启动与验证:从进程到SQL路由
配置写好之后,启动命令一般是:
/usr/local/mysql-proxy/bin/mysql-proxy \ --defaults-file=/usr/local/mysql-proxy/conf/proxy.conf确认进程起来后,先用管理账号连一下管理端口:
mysql -h127.0.0.1 -P2345 -uadmin -p在管理端可以看到后端实例的状态。正常情况下,主库和从库都应该是UP状态。如果某个从库宕机,可以在这里手动执行set offline把它摘除,修复之后再set online加回来。这种运维手段在处理故障时非常管用。
接着用普通业务账号连代理端口:
mysql -h127.0.0.1 -P1234 -uproxy_user -p执行一条简单的SELECT,再去看后端MySQL的processlist,会发现查询落在了从库上;执行一条INSERT,再去看processlist,会发现写操作落在了主库上。这个验证过程看起来基础,却是判断读写分离是否生效最直接的办法。我每次部署完,都会把这一步走一遍,省得后面业务方反馈说“没生效”再排查。
4. 上线之后最容易踩的坑:问题定位与调优记录
4.1 连接数被打满,业务全部阻塞
我遇到过最典型的故障,不是SQL路由错误,而是连接数被打满。现象是业务方反馈系统变慢,数据库侧大量连接处于Sleep状态,但CPU不高、慢查询也不多。查了一圈才发现,多个应用实例的连接池上限都偏大,而且每个连接都在执行一些低频SQL,导致连接长期被占用。
在Atlas场景下,问题会被放大,因为每一个前端连接在代理层都会对应到一条后端连接,应用连接一多,数据库连接数直接翻倍。解决思路分三层:应用侧压缩连接池上限,把空闲连接超时缩短;代理侧限制单用户并发连接数,超过阈值直接拒绝新连接;数据库侧适当调大max_connections,但不要依赖这个参数,它只是兜底。
4.2 主从延迟引发的读漂移
另一个高频问题是主从延迟。有一次报表业务上线后,用户刚提交一条数据,页面刷新却不显示。从代码看,插入后立刻执行了查询,但这条查询被Atlas路由到了从库,而从库同步延迟大约有几百毫秒,数据还没过来。
处理方案有两个。一是针对这类一致性要求高的SQL,让业务侧在查询时加一个强制走主库的标记,Atlas支持这类hint机制。二是更彻底的做法,把这类高频读写操作放到同一个连接里执行,因为前面说过,同一连接在写操作后读请求会粘滞在主库,自然就能读到最新数据。这里最容易踩的坑是:业务代码用了连接池,写完释放连接后,下一次查询再拿到的可能就是另一条连接,粘滞策略可能失效。所以,如果业务对一致性要求极高,最稳妥的办法还是强制走主库。
4.3 分表键没带上,SQL广播到所有分片
分表场景下的典型事故,是业务方新加了一个查询页面,查询条件里没有分片键。Atlas没办法定位到具体分片,只能把SQL广播到所有后端,等所有分片返回结果后再合并。数据量一大,这个操作会直接把数据库打垮。
排查时,打开Atlas的实时SQL日志,能看到同一条SQL反复出现在多个后端实例上,这就是广播的特征。根治办法只有一个:确认所有涉及分表的SQL都带上分片键。带不上的查询,要么单独建一张冗余表或索引表,要么把这个查询场景从分表范围里挪出去。分表表面上是个技术问题,本质上是一个业务SQL规范问题,这一步没做好,后面会一直被动。
4.4 配置变更和升级:平滑重启的笨办法
Atlas不像一些新项目支持配置热加载,很多配置修改之后需要重启进程才能生效。直接重启,所有存量连接会一瞬间断开,业务方必然报错。我的做法是:先在负载均衡层把这台代理的流量摘掉,等存量连接自然减少,再执行重启,确认进程和配置正常后,再把流量切回来。如果是两台代理,可以轮流操作,一台重启完切回来,再操作另一台。
另外,后期排查问题时,日志的详细级别很关键。平时用message级别就够了,流量不大、临时排查时可以把log-level调到debug,能看到每条SQL的路由结果,对定位“某条查询为什么走了主库”“为什么广播了”这类问题非常有帮助。
| 常见问题 | 排查方向 | 处理结论 |
|---|---|---|
| 数据库连接数暴涨 | 应用连接池配置、代理连接限制 | 压缩应用侧连接池,开启代理限流 |
| 查询和写入后数据不一致 | 主从延迟、连接粘滞策略 | 强制走主库或事务内查询 |
| 分表查询全表扫描 | 实时SQL日志检查是否广播 | 规范SQL,必须带分片键 |
| 修改配置后异常 | 管理端口状态、日志级别 | 先摘流量再重启,避免直接断连 |
5. 什么场景适合用Atlas,什么场景要绕道
5.1 合适的场景和不该碰的场景
以我实际经验来看,Atlas最舒服的应用场景是:老系统已经发展到需要读写分离,但是业务代码结构比较老旧,不可能大改;或者团队规模不大,没有专门的基础设施组去自研数据库代理。这时候引入Atlas,用一个代理层解决问题,业务方几乎无感,性价比很高。
反过来,如果你的系统一开始就处于高速迭代期,并且已经预见到未来需要分布式事务、跨分片复杂报表、动态扩容缩容这些能力,那我建议直接考虑更现代的方案,比如ShardingSphere这类框架。Atlas的定位决定了它在复杂查询、事务一致性方面是有天花板的,强行使用只会不断打补丁。还有一个现实问题是Atlas社区更新不活跃,这意味着一旦碰到源码级Bug,你得有一定的C语言和MySQL协议基础才能搞定。
5.2 我给新人的几条实在建议
第一,中间层的引入优先级应该排在业务稳定性之后。先确保数据库本身是高可用的,主从复制是健康的,再考虑上代理层,否则代理只会放大底层的故障。第二,先梳理SQL再改架构,至少要知道你的核心SQL长什么样、会不会带分片键、有没有跨分片join,这决定了Atlas能帮你多少。第三,监控体系要提前建,代理层的连接数、后端响应时间、每条SQL的路由结果,最好都能可视化,否则出了故障只能靠猜。
我记得第一次给一个老系统接Atlas的时候,最担心的不是功能,而是存量连接怎么平滑迁移。后来我总结了一个笨但有效的办法:先在负载均衡层加一个临时转发端口,让一小部分业务灰度连过去,验证稳定后再全量切换。还有一个排查小技巧,如果想知道某条SQL到底走了哪个读库,把Atlas日志调到debug,同时在后端从库上开processlist盯着,两个信息一对,路由结果一目了然。这个招帮我省了无数次翻代码的时间,你也可以试试。