资讯动态

基于Atlas的MySQL读写分离与分库分表实践指南

发布时间:2026/10/19 14:15:52 来源:尧图企业网站定制
1. 项目整体设计与适用场景解析1.1 这个项目到底解决什么问题做后端开发的人应该都有这种感觉业务量一上来数据库最先扛不住。单库单表跑到一定量级写入并发和查询延迟都会明显恶化这时候大部分人第一反应是上缓存、做读写分离再不行就分库分表。但真到了分库分表的阶段麻烦事就接踵而至——你希望应用层像访问一个数据库一样访问多个数据库实例而不希望在 DAO 层写一堆路由逻辑。Atlas 就是为这个场景而生的。它本质上是一个基于 MySQL 协议的数据访问中间件部署在应用服务器和 MySQL 实例之间对外伪装成一个 MySQL 服务器应用端不用改任何代码只要把数据库连接地址改成 Atlas 的地址就能获得读写分离、分库分表、连接池管理、SQL 过滤等能力。我在实际项目里第一次接触 Atlas是因为当时接手了一个线上商城系统订单表已经到了千万级单库单表写入经常把主库拖垮。团队讨论过 Sharding-JDBC、MyCat 这些方案但当时 Java 那套还比较重MyCat 引入成本也不小反而是 Atlas 这种中间层代理方式最适合我们当时的架构——应用端改动最小运维也最省心。1.2 适合谁用、不适合谁用Atlas 适合以下场景需要快速实现读写分离主库扛写入、从库扛查询应用层零改动。分库分表需求明确比如订单按用户 ID 分 4 个库、每库再按月份分表路由规则可以配置在 Atlas 层。不想侵入应用代码团队没有精力在 Java/PHP/Go 各个语言里分别实现数据库路由逻辑只想在中间层统一处理。运维能力一般的中小型团队Atlas 配置相对简单部署模式清晰不需要像某些分布式数据库那样搞一堆协调节点。不适合的场景追求极致性能的场景比如每秒几十万 QPS 的纯查询Atlas 的多一层转发确实会带来微秒级到毫秒级的额外延迟。需要复杂分布式事务支持的场景Atlas 本身不擅长跨库事务最多做数据库层面的单库事务。团队如果已经有完善的 Sharding-JDBC 体系没必要为了用 Atlas 而用 Atlas。我在技术选型时的一个重要心得是不要因为某个中间件功能强大就选它而要看你的团队能承担多少维护成本。Atlas 的优势恰恰在于轻量、部署简单、配置直观适合大多数 MySQL 垂直分层的需求。2. 核心原理拆解读 wrote 分离、分库分表与连接管理2.1 读写分离本质是基于 MySQL 主从复制Atlas 的读写分离能力底层依赖的是 MySQL 自带的主从复制机制。理解了这条链路你才能理解 Atlas 到底在“代理”什么。架构通常是这样的主库Master处理写入操作INSERT、UPDATE、DELETE、DDL。一个或多个从库Slave同步主库的 binlog处理查询操作SELECT、SHOW 等。Atlas 位于应用和 MySQL 之间解析应用发来的 SQL判断是读还是写然后转发到对应的数据库实例。判断逻辑并不复杂Atlas 内部对 SQL 文本做解析看语句类型和关键字。比如语句以 SELECT 开头一般归类为读请求以 INSERT、UPDATE、DELETE、ALTER、CREATE 等开头归类为写请求。但这里有个坑SELECT ... FOR UPDATE 和 SELECT ... LOCK IN SHARE MODE 虽然是 SELECT但它们本质是写操作或需要主库数据一致性绝对不能路由到从库。Atlas 这一块处理得比较完善不光是看关键字还会检查 SQL 有没有 FOR UPDATE 这类特殊子句。从库延迟是读写分离最头疼的问题。主库写入一条记录binlog 同步到从库需要时间如果刚写入就去从库查很可能查不到。Atlas 层面没有做延迟感知所以这部分的解决方案通常要落到业务上对实时性要求极高的数据比如支付结果、库存扣减要么强制走主库要么接受秒级延迟。我当时的做法是在应用层维护一个简单的“读主库标记”需要强一致性的查询走一个单独的 DB 连接池这个池直接连到 Atlas 的路由规则里的主库。虽然绕了半圈但效果可靠。2.2 分库分表的路由规则是如何工作的分库分表是 Atlas 另一个核心价值。它的思路是在配置里声明一个“逻辑表”然后告诉 Atlas 这个逻辑表对应的物理库表分布规则。举个例子订单表 order 要做分库分表规则是 userId 取模4 个库order_0、order_1、order_2、order_3每个库 4 张表order_0、order_1、order_2、order_3应用端正常发送INSERT INTO order_order (order_id, user_id, amount) VALUES (…)Atlas 解析 SQL 后根据语句中的 user_id 字段值计算 user_id % 4 得到表后缀再计算出库后缀可能跟表后缀相同也可以用独立规则然后改写 SQL 并发送到对应的库表。这里要特别注意Atlas 的分表算法是基于 SQL 中某个字段的这个字段必须有值且是整数类型常用的是取模运算。如果你的 SQL 里没有这个字段Atlas 无法判断该去哪个库表会直接报错或拒绝执行。所以使用分库分表时所有涉及分表字段的查询条件都必须带上这个字段否则只能做全表扫描式的路由这在生产者设计上是不允许的。Atlas 的配置文件通常是conf/atlas.conf里的分表配置段大概长这样# 分表规则 tbl_order user_id:4:0,1,2,3这里的意思是逻辑表名为 tbl_order分表字段为 user_id一共分 4 个物理表物理表后缀分别为 0、1、2、3。分库规则如果也配置了就按类似格式指定库后缀。当然取模路由只是其中一种方式实际生产环境还会用到范围路由比如按时间范围分表、一致性哈希路由等但 Atlas 本身内置的规则比较简单主要还是取模。如果你需要更复杂的路由算法要么自己在 SQL 层面处理要么换更重的中间件。2.3 连接池与会话管理Atlas 如何维持大量数据库连接应用端连接 Atlas 时Atlas 会为它建立一个前端连接。但同时 Atlas 也要连接后端的 MySQL 实例这部分叫后端连接。如果每个前端连接都对应一个后端连接那连接数会爆炸数据库根本扛不住。Atlas 的做法是维护一个连接池后端连接可以被复用。具体来说一个前端连接执行完一条 SQL 后这个后端连接可能不会立刻释放而是被放回连接池下一个前端连接执行 SQL 时可以复用同一个后端连接。连接池的大小、空闲超时时间、连接存活检查等参数都在配置文件里可调。这并不意味着一对一关系不存在了。如果你有 200 个前端连接而连接池空闲容量只有 100那么 200 个前端连接会竞争 100 个后端连接。执行完 SQL 的释放、等待、竞争都由 Atlas 管理对应用透明。这种设计有一个好处应用端只需要配置一个 Atlas 地址和端口不用担心后端的连接数限制。也有一个坑如果后端 MySQL 的max_connections设置得过低即使 Atlas 做了连接池复用高并发下依然可能出现Too many connections错误。这种时候别光调整 MySQL 配置还是要结合 Atlas 的连接池参数一起调。我在压测时遇到过这个问题。当时 Atlas 连接池设置得偏大后端 MySQL 只有 200 个连接上限结果高并发压测时 MySQL 直接拒绝新连接。优化方案是把 Atlas 的连接池上限调低同时把 MySQL 的max_connections提到 500这才稳住了。3. 实操过程从部署到配置读写分离的完整流程3.1 环境准备与安装部署Atlas 本身是一个独立的服务进程基于 C 语言开发安装包通常是提前编译好的二进制文件。不同的系统版本会有不同的安装包我以 CentOS 7 系统为例来演示安装过程。先下载对应版本的 Atlas 安装包然后解压到指定目录。# 下载示例版本号以实际发布版本为准 wget http://example.com/atlas/atlas-2.2.1.el6.x86_64.rpm # 安装 RPM 包 rpm -ivh atlas-2.2.1.el6.x86_64.rpm如果你使用的是源码包一般步骤是tar -zxf atlas-2.2.1.tar.gz cd atlas-2.2.1 ./configure --prefix/usr/local/mysql-proxy make make install安装完成后的目录结构大致是这样bin/底下有可执行文件核心是mysql-proxyAtlas 基于 MySQL Proxy 二次开发。conf/底下有atlas.conf这是核心配置文件。log/存放运行日志。启动方式也很简单/usr/local/mysql-proxy/bin/mysql-proxy --defaults-file/usr/local/mysql-proxy/conf/atlas.conf如果你需要后台运行可以这样nohup /usr/local/mysql-proxy/bin/mysql-proxy --defaults-file/usr/local/mysql-proxy/conf/atlas.conf /dev/null 21 3.2 核心配置详解atlas.conf 逐行拆解Atlas 的配置是 INI 风格看起来像下面这样我逐段解释。[mysql-proxy] # 管理接口的 IP 和端口用于登录 Atlas 管理后台 admin-address 0.0.0.0:2345 # 管理接口的用户名和密码 admin-username admin admin-password admin123 # 代理监听地址应用连接这里 proxy-address 0.0.0.0:1234 # 后端 MySQL 主库地址 proxy-backend-addresses 192.168.1.10:3306 # 后端 MySQL 从库地址多个从库用逗号分隔 proxy-read-only-backend-addresses 192.168.1.11:3306,192.168.1.12:3306 # 连接池相关配置 proxy-pool-min-size 10 proxy-pool-max-size 200 # 超时配置 proxy-connect-timeout 3 proxy-read-timeout 10 proxy-write-timeout 10 # 日志级别message、warning、critical、error log-level message log-path /usr/local/mysql-proxy/log几个关键参数我再展开讲一下。proxy-backend-addresses配置主库多个主库之间可以做写负载均衡但实际场景中通常只有一个主库。proxy-read-only-backend-addresses配置从库读请求会按权重轮询转发到这些从库。连接池大小proxy-pool-max-size如果你设置得太大后端数据库压力大连接数不够会报错设置得太小高并发时后端连接不够前端 SQL 会排队等待。一般建议先设为 100压测后慢慢调整。管理接口admin-address是独立的通过 MySQL 客户端可以登录这个端口执行一些管理命令比如查看后端状态、手动切换读写状态等。命令格式如下mysql -h127.0.0.1 -P2345 -uadmin -padmin123登录后可以执行SELECT * FROM backends;查看后端状态也可以执行SET offline/SET online来手动下线或上线某个后端库。这个功能在运维排查时非常实用。3.3 配置读写分离的完整示例假设我们的业务场景是主库192.168.1.10:3306从库两个192.168.1.11:3306、192.168.1.12:3306应用连接 Atlas 的地址127.0.0.1:1234那么配置文件中就设置proxy-address 0.0.0.0:1234 proxy-backend-addresses 192.168.1.10:3306 proxy-read-only-backend-addresses 192.168.1.11:3306,192.168.1.12:3306保存配置文件后重启 Atlas。启动后我们在应用服务器上用 MySQL 客户端测试连接mysql -h127.0.0.1 -P1234 -uapp_user -papp_passwd -e SELECT NOW();如果 Atlas 配置正确这个 SQL 会返回成功。此时我们来验证读写分离是否生效。在从库上开启通用查询日志general log或者从库的processlist里观察是否有 SELECT 查询进入。-- 在从库执行 SET GLOBAL general_log ON;然后在应用端执行几条 SELECT 和 INSERT 语句SELECT id, name FROM users WHERE id 1; INSERT INTO users (name) VALUES (test);观察从库日志你会发现 SELECT 走到了从库观察主库日志INSERT 走到了主库。读写分离链路通了。需要说明的是Atlas 不会把同一个连接里的所有语句固定路由到同一个后端它是逐条 SQL 判断的。这意味着你在应用端开启了一个事务事务里先 INSERT 再 SELECT事务前半段走主库后半段的 SELECT 也可能走从库。这在事务一致性上是个隐患。针对这种情况Atlas 提供了一个配置项不同版本有差异或在管理接口中设置强制走主库的会话变量。我们当时的做法是在应用端对需要事务内一致性的业务使用一个独立的数据库账号这个账号在 Atlas 配置里只指向主库。相当于你有两个数据库账号一个普通账号走读写分离一个专用账号全走主库。虽然有点土但在当时足够好用。3.4 分库分表配置实操配置分库分表的核心是把逻辑表映射到物理库表。假设你要对order表做分库分表规则是4 个库order_db_0、order_db_1、order_db_2、order_db_3每个库 4 张表order_0、order_1、order_2、order_3分表键user_id在配置文件的 SQL 解析部分需要设置分表规则。Atlas 的配置格式大致如下不同版本可能略有差异以官方文档为准# 逻辑表名 分表字段:分表数量:分表后缀 tbl_order user_id:4:0,1,2,3如果分库也要做需要指定库后缀的映射规则这取决于 Atlas 版本支持的程度。如果只支持分表而不支持分库那你可能需要预先在应用层或 Proxy 层做库的映射。实际测试时插入一条数据INSERT INTO order (user_id, order_id, amount) VALUES (1001, 20240001, 99.00);根据取模规则1001 % 4 1所以这条记录会进入 order_db_1 库的 order_1 表。查询时也必须带上 user_id 条件SELECT * FROM order WHERE user_id 1001 AND order_id 20240001;这样才能路由到正确的分表。如果不带 user_id 查询Atlas 无法确定路由通常会执行全表遍历如果 Atlas 支持或直接报错。实际生产环境里我会在应用层强制要求所有订单查询都带上用户 ID这也是分库分表方案通用的一条铁律。3.5 启动验证与状态检查配置完成后启动 Atlas 并验证状态。登录管理接口mysql -h127.0.0.1 -P2345 -uadmin -padmin123执行SELECT * FROM backends;这个命令会列出所有后端 MySQL 的状态。重点关注每个后端的 typerw 或 ro、statusup 或 down、权重等信息。如果某个从库状态 down 了读写分离会把它的流量摘除吗Atlas 的自动摘除机制主要靠心跳检测如果探测不到后端存活就自动将它移除出可用列表。这个机制在早期版本里并不稳定有时需要手动登录管理接口执行SET offline ...;所以日常巡检时我习惯写一个定时脚本定期查询 Atlas 管理接口把 down 状态的后端及时处理掉。脚本内容不复杂核心逻辑就是连接管理接口执行SELECT * FROM backends;解析文本检查状态。3.6 在应用端如何优雅切流这是整个接入过程里最关键的一步。新老系统切换时我建议分三步走先在一台测试服务器上部署 Atlas把测试应用连接指向 Atlas验证所有核心链路。在预发环境开启少量流量比如 5% 的请求到 Atlas 环境观察错误率和延迟。全部流量切换到 Atlas 后保留旧连接方式一周以上方便随时回滚。应用端切换非常简单即修改数据库连接配置原来的jdbc:mysql://192.168.1.10:3306/app_db 现在的jdbc:mysql://127.0.0.1:1234/app_db注意用户名和密码必须以 Atlas 配置中的为准而不是后端 MySQL 的真实账号密码。Atlas 会代理认证也就是说应用端的账号密码是 Atlas 自己维护的账号体系跟后端的 MySQL 账号可以不一样。我踩过一个坑当时团队有个老系统数据库连接串里面带了useSSLtrue连 MySQL 没事连 Atlas 却反复握手失败。查了半天发现 Atlas 对 SSL 的支持有限需要把该参数去掉才能正常连接。如果你遇到类似问题记得检查驱动连接串的 SSL、字符集参数是否跟 Atlas 兼容。4. 常见问题与排查技巧实录4.1 连接被拒绝Atlas 启动失败排查症状应用连接 Atlas 提示Can’t connect to MySQL server on 127.0.0.1 (111)。排查步骤检查进程是否存在ps -ef | grep mysql-proxy检查监听端口netstat -tlnp | grep 1234查看日志文件重点看log/目录下的信息。日志中出现类似[ERROR] proxy-backend-addresses is required说明配置文件中必填项缺失补上即可。如果是[ERROR] bind() failed: Address already in use说明端口被占用换端口或杀掉占用进程。启动失败里最高频的原因其实是配置文件写错了。例如 IP 地址后多了一个空格或者端口号写成字符串这些细节错误在 Atlas 启动时未必会立刻报出来但运行时该后端库始终是 down 状态。所以改完配置记得仔细检查一遍格式。4.2 读写分离不生效所有请求都走了主库有时候配置了从库但 SELECT 请求依然打到主库。常见原因有三个proxy-read-only-backend-addresses没写对或者空值被注释掉了。从库权重设置为 0导致不会被选中。Atlas 对某些 SQL 类型做了特殊处理比如带函数的 SELECT、带锁的 SELECT默认走主库。排查时先登录管理接口看后端列表SELECT * FROM backends;确认从库的 type 是否为ro、status 是否为up、权重是否大于 0。再检查 SQL 是否属于特殊类型。最简单的验证方法是在从库开启 general log然后执行几条典型查询看看是否会出现在从库日志里。4.3 从库延迟导致的数据不一致这个问题不算 Atlas 本身故障但 Atlas 会把读请求均匀分发到从库如果从库复制延迟很大用户会看到数据一会有一会没有。我更推荐的做法是根据业务容忍度拆分实时性要求高的读走主库。允许秒级延迟的读走从库。Atlas 没有在配置层面支持“按 SQL 标记强制走主库”所以只能靠应用层拆分连接。比如在 Spring 里配置两个数据源一个数据源指向 Atlas 普通端口读写分离另一个数据源指向 Atlas 的只主库端口或者直接指向主库。4.4 高并发下出现 Too many connections我压测时踩过的这个坑本质原因是连接池和后端 MySQL 连接数上限不匹配。两层都要调MySQL 端提高max_connections同时注意max_connections不是越大越好它会消耗内存。Atlas 端调小proxy-pool-max-size让后端连接数控制在 MySQL 承受范围内。一个粗略的经验公式max_connections Atlas的pool-max-size 其他来源连接数 20%余量如果你有 3 个后端 MySQL 实例每个实例的max_connections是 500那 Atlas 的proxy-pool-max-size可以设置为 300 左右留出运维和备份连接的余量。4.5 长连接被 MySQL 主动断开MySQL 的wait_timeout默认是 8 小时。如果应用通过 Atlas 建立了一个长连接但 8 小时内没有新的 SQL 进来MySQL 可能主动断开连接。Atlas 连接池里的后端连接也可能被这样清理掉但前端应用不知道下次发了 SQL 才发现连接失效。处理方案有三个在应用端连接池如 HikariCP、Druid配置test-on-borrow或connection-test-per-connection发送一个SELECT 1探活。在应用端设置连接最大空闲时间小于 MySQL 的wait_timeout。如果应用无法改动那就把 MySQL 的wait_timeout调大或者配置 Atlas 的连接池定期保活。我比较推荐前两种因为保活机制会让数据库处理更多无意义的探测请求但如果连接数不大效果也不错。4.6 SQL 语法兼容问题Atlas 内置的 SQL 解析器并非 100% 兼容所有 MySQL SQL 语法。我遇到过的典型问题包括带ON DUPLICATE KEY UPDATE的 INSERT 语句某些版本转发时可能解析异常。LOAD DATA INFILE这类文件导入语句Atlas 基本不支持。某些带 HINT 的 SQL比如/* MAX_EXECUTION_TIME(1000) */ SELECT ...可能解析不出来。遇到这类问题最快的排查方式是把 SQL 单独拿出来用 MySQL 客户端直连 Atlas 测试然后观察日志输出。如果确定是 SQL 解析问题一般有三种解法改写 SQL兼容 Atlas 解析。给 Atlas 打补丁或换个版本。这类 SQL 走旁路绕过 Atlas 直接连接真正的 MySQL 实例。我通常优先选第一种因为最简单。比如把INSERT INTO ... ON DUPLICATE KEY UPDATE拆成先SELECT再UPDATE虽然多一次交互但能保证兼容性也更便于后续排查。4.7 Atlas 的高可用部署生产环境里Atlas 本身不能成为单点。如果只部署一个 Atlas 实例它挂了应用就彻底无法访问数据库。常见做法是部署两个或多个 Atlas 实例前面加一层负载均衡LVS、HAProxy、Nginx Stream 都可以。如果 Atlas 实例挂了负载均衡器将流量切到另一个实例。两个 Atlas 实例之间不需要同步会话状态因为数据库状态本身在后端 MySQL 里Atlas 只做转发。这也是中间层代理架构的高可用便利之处不需要额外引入 ZooKeeper 之类的东西来协调状态只要保证至少一个实例存活即可。我当时的部署拓扑是两台 Atlas 实例一台主一台备。前端用 HAProxy 做 33060 端口的 VIP转发到两台 Atlas 的 1234 端口。两个 Atlas 实例配置一致后端都指向同一组 MySQL 主从。切换测试也很顺利杀掉主 Atlas 进程HAProxy 在健康检查失败后自动把流量切到备 Atlas应用侧几乎没有感知只有少量 TCP 连接会报错重连。整体效果符合预期。5. 监控与运维最佳实践5.1 需要重点关注的监控指标Atlas 运行时的健康状态我主要关注几类指标连接数Atlas 前端连接数应用连接 Atlas 的连接数和后端连接数Atlas 连接 MySQL 的连接数。连接数异常增长往往意味着应用端连接池泄漏或高并发到来。请求量Atlas 每秒钟处理的 SQL 数量和类型。通过日志统计可以观察读写比例是否合理如果读比例极高但从库负载很低需要检查路由是否正常。延迟Atlas 转发 SQL 到后端 MySQL 的响应时间。如果延迟突然飙升可能是后端数据库出问题也可能是网络抖动。后端状态每个后端 MySQL 是 up 还是 down主库和从库的角色是否正常。其中没有现成的 Grafana Dashboard 能直接用最简便的采集方式是通过管理接口SELECT * FROM backends;定期抓取再写入自己的监控系统。虽然粗粒度但足够覆盖日常需求。5.2 配置变更和版本升级注意事项Atlas 的配置变更要重新启动进程才生效。这一点容易踩坑因为很多人以为修改配置后会热加载。建议变更流程备份当前配置文件和当前进程状态。修改配置文件尽量用 diff 检查改动。先在后端从库或多实例环境测试。生产环境操作选择一个低峰期重启 Atlas 进程。重启后立刻验证连接和路由。版本升级时先查一下官方 release notes重点看 SQL 解析逻辑、连接池行为、配置项兼容性。有些版本切换后旧的配置项被废弃不检查的话会启动失败。5.3 日志切割与清理Atlas 日志如果不定期清理会占用大量磁盘空间。log-level message级别下每条 SQL 的执行都会记录日志跑一天下来几个 GB 很正常。建议配置 logrotate 做日志轮转保留最近 7 天的日志即可。切割方式类似于/usr/local/mysql-proxy/log/*.log { daily rotate 7 copytruncate compress missingok }copytruncate很重要因为 Atlas 进程一直在写日志文件直接移走文件可能导致句柄问题copytruncate可以拷贝后再清空原文件。5.4 安全加固技巧Atlas 不直接暴露给公网只允许应用服务器访问。安全方面注意以下几点防火墙只放行应用服务器所在网段访问 Atlas 的代理端口和管理端口。管理端口admin-address不要绑定 0.0.0.0最好是只绑定内网运维跳板机的 IP否则任何人能连管理接口就麻烦了。修改默认的管理员密码不要保留 admin/admin123。如果业务对数据敏感Atlas 层面的 SQL 审计日志要开启记录所有经过代理的 SQL。注意这会显著增加磁盘 I/O建议在合规要求下权衡开启。6. 我踩过的那些坑与总结性建议做数据库中间件选型时最忌讳的不是选错方案而是选了一个运维起来完全超出团队能力的方案。Atlas 很轻量但它本质上依然是一个需要认真运维的独立服务。你不仅要多部署一层服务还得为它设计监控、容灾、变更流程这些成本必须提前算进项目里。我在 Atlas 落地过程中最大的体会是三个字先验证。不要直接在生产环境拍板上 Atlas先在测试环境完整走一遍读写分离、分库分表、故障切换的演练。特别是故障切换很多人会在切换过程中发现自己的连接池没有配置重连机制或者 Atlas 备机的端口没有启动这些都是上线后才发现就会手忙脚乱的问题。一个小技巧想分享给后来者给 Atlas 的管理接口写一个简单的 shell 巡检脚本每天定时跑一次输出后端的 up/down 状态和连接数。脚本一共不到 30 行但能帮你节省大量排查时间。我在很多集群里都用了这个笨办法简单但可靠。最后再说回 Atlas 本身。它不像某些新潮的分布式数据库那样自带管理面板和一键扩缩容但它胜在稳定、透明、容易理解。只要你的业务场景是 MySQL 体系下的读写分离和水平分表Atlas 绝对是一个值得认真考虑的选择。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑