ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MySQL读写分离与分库分表:Atlas中间件部署实战指南

MySQL读写分离与分库分表:Atlas中间件部署实战指南

我折腾MySQL中间件这几年,有一个很深的体会:大部分团队一开始根本不需要引入复杂的分布式数据库,一台主库配上几台只读从库,再架一层轻量级的访问代理,就能扛住绝大多数读写压力。这个代理层,在国内最经典、也最容易被低估的方案,就是360开源的Atlas。

Atlas是基于MySQL官方Proxy 2.0.1版本改造而来的,本质上是一个位于应用和MySQL实例之间的透明访问中间件。它的核心价值,就是帮你把“读写分离”和“分库分表”这两件以前需要改业务代码才能做的事,下沉到数据访问层统一处理。这篇文章不聊虚的,我就从实际部署、核心参数、踩坑经历三个角度,把这个组件讲透。不管你是刚接触数据库中间件的新手,还是正在做技术选型的后端开发,这篇都能给你一些能直接落地的参考。

1. 内容整体设计与思路拆解

1.1 为什么你的架构需要中间件这一层

先想一个问题:当你的业务量涨起来,数据库的读压力远大于写压力时,最通用的扩容手段是什么?绝大多数人第一时间想到的是加从库,做读写分离。但问题来了,业务代码里如果到处写死了数据库连接地址,你怎么让写请求只发往主库、读请求均衡分发到各个从库?改代码,让每个服务自己维护数据源路由规则?这事儿听着就能做,但实际上又繁琐又容易出错。

真正合理的思路,是在应用和数据库之间隔一层透明代理。应用连接代理,代理负责把SQL请求分类——是SELECT就分发到从库,是INSERT/UPDATE/DELETE就固定发给主库。这样业务代码完全不用感知底层数据库拓扑的变化,加从库、切换主库,对上层都不可见。这层代理的角色,就是Atlas这类中间件在设计上的核心出发点。

1.2 Atlas在技术版图里的定位

现在市面上数据库中间件不少,MyCAT、ShardingSphere、Vitess,各有各的侧重。但Atlas有一个鲜明的特点:它走的是传统代理路线,部署形态简单,就是一个独立进程,前端用MySQL协议跟应用交互,后端用标准MySQL协议连接真正的数据库实例。这种形态带来一个天然优势——它对应用语言的侵入性为零,不管你是Java、PHP、Go还是Python,只要程序能连MySQL,就能连Atlas。

我当初选它,还有一个现实考量:团队当时没有精力去改造ORM层,引入那种需要嵌入到业务代码里的中间件意味着每个服务都要改配置、改依赖,工作量太大。Atlas这种纯代理模式,上线周期可以压缩到半天以内,特别适合想快速缓解数据库压力、又不想大动干戈的中小型团队。

1.3 选型之前必须认清的两个边界

任何技术都有适用边界,Atlas也不例外。第一,它不像NewSQL那样具备分布式事务能力,跨库事务需要业务层自己控制,这也是所有分库分表中间件的共同限制。第二,它本身不解决数据同步问题,主从复制仍然依赖MySQL原生的binlog机制。如果你从库延迟过高,读写分离的读一致性就会受影响。搞清楚这两点,你对Atlas能做什么、不能做什么,心里就有底了。

2. 核心功能与方案选型解析

2.1 读写分离:Atlas的核心看家本领

读写分离是Atlas最成熟、用得最广的功能。它的实现原理在上面已经提过,但实际落地时有不少细节值得展开。Atlas在处理SQL时,会做一层轻量的词法解析,判断语句类型。SELECT、SHOW、DESC这类读操作,会按照你配置的负载均衡策略,分发给后端的某个从库;其他写操作及事务语句,则固定走主库。

但这里有个关键点:如果你在一个事务里先执行了UPDATE,再执行SELECT,这条SELECT能不能被路由到从库?答案是不能。Atlas为了保证事务内的一致性,一旦某个连接进入了事务状态,它后续的所有语句都会发往同一个后端数据库,直到事务提交或回滚。这个设计非常务实——它避免了在主从延迟下,事务里刚写入的数据读不到的问题。

2.2 分库分表:何时能用,怎么配置

Atlas对分库分表的支持,走的是“库表映射”的路子。你需要提前规划好分片规则,比如把用户ID按哈希分到4个库里,配置里把逻辑表映射到物理表。它不像有些中间件那样支持灵活的分布式Join和聚合运算,所以它对SQL本身有要求——单表操作最理想,简单Join还可以,复杂Join和子查询尽量别用。

坦白说,现在新项目做分库分表,我更推荐先考虑ShardingSphere这类功能更全面的方案。但如果你手里有一套老系统,表结构已经基本固化,只是单库容量到了瓶颈,想赶紧把数据拆到多个实例上去,Atlas的分库分表能力仍能派上用场。它的配置形态简单直接,映射关系一目了然,运维上手成本很低。

2.3 负载均衡与健康检查:保障高可用的底座

Atlas后端的每个MySQL实例都有权重配置,你可以根据机器性能分配流量比例。比如主库CPU是8核,从库是4核,那权重就可以设成2比1。同时,Atlas会周期性地对后端实例做健康检查,发现某台从库宕机,会自动把它从可用列表里摘除,应用感知不到任何变化。

这里有个配置技巧:健康检查不只是检测端口通不通,你还可以指定一条检查SQL,比如SELECT 1,确保实例在MySQL层面是真正可用的。端口能连上但数据库内部有问题的情况,在生产环境里发生过不止一次。

3. 实操过程与核心环节实现

3.1 环境准备与安装编译

Atlas的官方发行版默认是用glibc编译的,如果你用的系统是CentOS 6或7,可以下载预编译的二进制包,解压即用。如果你跟我一样用的是比较新的操作系统版本(比如CentOS 8+、Ubuntu 20.04+),直接跑二进制可能会报错,这时就需要从源码编译。我踩过的坑是,新系统缺少libffi-devel等依赖,编译时反复报头文件缺失。提前用yum装好依赖包,能省不少时间。

# CentOS下安装基础依赖 yum install -y gcc gcc-c++ glibc-devel libffi-devel libevent-devel glib2-devel mysql-devel # 解压源码包 tar -zxvf Atlas-2.2.1.tar.gz cd Atlas-2.2.1 # 编译安装 ./autogen.sh ./configure --with-mysql-lib=/usr/lib64/mysql make && make install

编译完成后,默认安装目录是/usr/local/mysql-proxy,bin目录下放着mysql-proxy主程序,conf目录是配置文件,lib目录下是Atlas自己实现的读写分离和分库分表逻辑。

3.2 最小化配置文件实战

Atlas的配置文件是兼容MySQL风格的ini格式,核心配置项并不多。我给你们看一份我实际部署时用过的精简配置,三个后端实例对应一主两从:

[mysql-proxy] # 管理接口的用户名密码 admin-username = admin admin-password = admin123 # 代理服务监听地址与端口,应用连这个地址 proxy-address = 0.0.0.0:1234 # 管理接口监听地址与端口,DBA用这个做管理 admin-address = 0.0.0.0:2345 # 后端主库,write proxy-backend-addresses = 192.168.1.101:3306 # 后端从库,read,多个用逗号分隔 proxy-read-only-backend-addresses = 192.168.1.102:3306,192.168.1.103:3306@2 # 分库分表的规则文件 # proxy-config = /usr/local/mysql-proxy/conf/table_sharding_rule.conf # 对应用户,格式为 用户名:密码@IP段 pwds = dba_user:xxxxx@192.168.1.0/24

注意看一下从库那行的写法。默认每个从库的权重是1,如果你希望第二台从库承担更多读流量,就在IP后面加@符号和权重值。上面配置里192.168.1.103@2代表它的权重是2,接收的读请求数量会是旁边那台从库的两倍。

3.3 启动服务与基础验证

配置写完后,启动方式非常简单:

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

生产环境建议用nohup或systemd做守护,避免进程意外退出。启动后,先确认两个端口在监听:

netstat -tlnp | grep -E '1234|2345'

如果端口起来了,用应用账号试连一下代理端口,执行几条SQL看看路由效果:

mysql -h127.0.0.1 -P1234 -udba_user -p # 查看当前连接的后端地址,在Atlas里可以执行这条命令 mysql> /*proxy*/show proxy backend;

这个/proxy/前缀是Atlas的管理指令,执行后能看到每条连接的当前后端是主库还是某个从库。我第一次看到这个输出时,读写分离的路由逻辑一下就清晰了。

3.4 管理接口的使用技巧

Atlas的管理接口容易被人忽略,但在排障时简直救命。用MySQL客户端连上2345端口,就能执行一些在线管理命令,比如查看连接状态、动态调整后端实例等。

-- 查看后端实例状态 SELECT * FROM backends; -- 手动摘除某台从库 SET BACKEND DOWN 2; -- 重新启用某台从库 SET BACKEND UP 2;

这里的操作不需要重启Atlas,对线上连接没有任何影响。我之前在给从库做系统升级前,就是先手动把对应后端置为DOWN,等升级完再置为UP,全程业务无感知。

4. 配置参数深度解读与调优

4.1 连接池与线程模型参数

Atlas本质上是一个多线程代理,它需要同时维护大量前端连接和后端连接。以下这几个参数,对性能和稳定性影响最大,逐个说明:

参数名说明生产建议值
event-threads处理网络事件的工作线程数CPU核数的2~4倍
max-connections前端最大连接数视业务规模而定,一般为2000~5000
wait-timeout空闲连接超时时间(秒)28800(8小时),与MySQL保持一致
keepalive是否开启TCP保活1(开启)

event-threads设置过大或过小都会有问题。太少了,高并发下事件处理不过来,延迟会飙升;太多了,线程上下文切换的开销反而拉低吞吐。我一般习惯先按CPU核数的2倍设置,压测后根据CPU使用率和延迟曲线微调。

4.2 客户端IP访问控制

Atlas的pwd参数里可以设置IP段白名单。这个能力在混合云架构下特别好用——你可以精确控制哪些网段能访问代理端口,避免数据库端口直接暴露在内网甚至公网。配置里我建议不要把0.0.0.0/0写进白名单,安全上的收益非常有限,风险却大得多。

4.3 SQL日志与慢查询定位

排查问题时,SQL日志是重中之重。Atlas提供了sql-log和slow-sql-log两个开关,配合日志级别控制,可以在不影响性能的前提下,把慢SQL和错误SQL记录下来。

# 开启SQL日志 sql-log = ON # 开启慢SQL日志,慢于1秒的执行会被记录 slow-sql-log = ON slow-sql-time = 1000 # 日志存放路径 log-path = /usr/local/mysql-proxy/log

这里有个经验之谈:sql-log在生产环境长期全开,日志量会非常大,磁盘增长很快。我通常的做法是,平时只开slow-sql-log,出故障时再临时打开全量SQL日志,定位完关闭。相信我,这能替你省下不少磁盘空间和排查日志时折磨人的时间。

5. 常见问题与排查技巧实录

5.1 主从延迟导致的数据读不到

这是读写分离架构绕不开的经典问题。有一次上线活动,用户反馈刚下的订单在订单列表里看不到。查了后端数据库,数据主库里有,从库里还没同步过来。Atlas把这条查询路由到了延迟的从库,导致出现“数据丢了”的假象。

排查思路:先看从库的Seconds_Behind_Master指标,再结合Atlas的连接路由情况判断。解决手段有多条路:

  • 在从库追上主库前,临时把这个从库从Atlas中摘除。
  • 对于强一致的读场景,在业务层把这类查询标记为走主库(可以在SQL里加hint的方式)。
  • 优化复制链路,比如升级实例规格、开启并行复制。

5.2 连接数被打满的诡异故障

有次线上告警,Atlas的连接数一路涨到上限,新连接全部被拒。检查后端MySQL,连接数倒是没满。最后发现是我们的应用侧连接池配置了过大的maxPoolSize,而且连接池没有做有效的空闲回收,导致大量连接挂在Atlas上不释放。

这个问题的根源不在Atlas,而在应用侧的连接池配置。解决方案是:应用侧合理设置maximumPoolSize,一般建议单实例不超过50;同时开启连接泄漏检测,防止连接被业务代码异常占用后不归还。

5.3 编译安装时的依赖地狱

我开头提过,源码编译Atlas在新版Linux系统上会遇到不少依赖问题。这里补充一个更具体的坑:有些系统没有安装mysql-devel包,configure阶段会提示找不到mysql_config,导致直接退出。还有libevent-devel和glib2-devel缺失,编译时会在链接阶段报各种奇奇怪怪的错误。解决方法是编译前用下面的命令把依赖全部补齐:

yum install -y libevent-devel glib2-devel mysql-devel libffi-devel

如果你用的是Ubuntu/Debian系列,对应的是libevent-dev、libglib2.0-dev、libmysqlclient-dev、libffi-dev,包名略有差异,但逻辑一样。

5.4 分库分表规则不生效的排查

规则文件配置了,但流量没有按预期分发到不同的库。这种问题,八成是规则文件里的schema名或表名跟实际SQL对不上。Atlas在做库表映射时是严格匹配的,大小写、反引号都会影响匹配结果。还有一种情况是配置了proxy-config参数,但规则文件路径配错了,Atlas启动时静默跳过,直连了后端单库。遇到这种情况,启动时加上--log-level=debug,日志里会明确打印规则加载结果。

6. 运维经验与监控体系建议

6.1 进程守护与自动拉起

Atlas作为代理层,它的可用性直接决定了业务的可用性。生产环境必须配置进程守护,我建议使用systemd而不是简单的crontab脚本,因为systemd可以精确控制启动顺序和失败重启策略。

[Unit] Description=MySQL Atlas Proxy After=network.target [Service] Type=forking ExecStart=/usr/local/mysql-proxy/bin/mysql-proxy --defaults-file=/usr/local/mysql-proxy/conf/proxy.conf ExecReload=/bin/kill -HUP $MAINPID Restart=always RestartSec=3 [Install] WantedBy=multi-user.target

6.2 监控指标体系

Atlas自身不带监控面板,但它的状态信息可以通过管理接口抓取。我建议至少监控这几个维度:

  • 进程是否存活、端口是否在监听。
  • 后端实例的状态,是否频繁出现DOWN/UP抖动。
  • 并发连接数趋势,接近上限时要提前扩容或优化。
  • 慢SQL频率变化,突然增多通常意味着后端MySQL出了问题。
  • QPS/TPS的整体趋势,用于容量规划。

可以用自研脚本或接入Prometheus这类监控系统,定时抓取管理接口数据入库,配置告警规则。我第一次把Atlas接入监控体系后,很多潜在问题都能在用户反馈之前暴露出来。

6.3 版本升级与兼容性验证

Atlas本身的迭代早已停止,不建议在它上面追求新功能。如果遇到安全更新或操作系统兼容问题,优先考虑架构升级到更现代化的中间件,而不是在旧版本上打补丁。如果坚持用,至少保证操作系统版本和glibc版本与Atlas编译环境匹配,否则上了生产环境后出现诡异的内存问题,排查会非常痛苦。

7. 写在最后的经验沉淀

折腾Atlas三年多,我对数据库中间件这件事有了更深的理解。中间件永远是一个权衡的产物——它简化了业务侧的数据访问复杂度,但把复杂度转移到了运维侧。引入它之前,一定要想清楚自己的团队有没有能力承接这个运维成本。Atlas本身不复杂,真正的复杂度在后端MySQL的高可用架构、主从复制的一致性保障,以及业务侧对数据一致性模型的理解上。

如果你问我什么时候适合上Atlas,我会说:业务读多写少、MySQL主从复制已经稳定跑了一段时间、不想大改业务代码,那么Atlas值得一试。它在简单场景下的表现,足够让团队用最小的代价撑过业务增长最快的阶段。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进