Atlas数据库中间件:分库分表与读写分离实战解析 先聊聊我为什么写这篇东西。前阵子帮一个做电商的朋友排查线上慢SQL发现他们还在用业务代码里自己拼分表逻辑拼得想骂人。我说你们为什么不上中间件他说怕踩坑。后来我帮他梳理了一遍 Atlas 的路由和配置他自己把分库分表接完了才反应过来原来数据库代理层没那么玄乎。这篇文章聊的 Atlas是开源社区里那套基于 MySQL 协议做数据库代理的中间件。不是地图集也不是波士顿动力的机器人。它主要解决三件事分库分表的自动路由、读写分离、数据库连接池的统一管理。如果你正在面临单表数据量过大、读多写少、库表拆分后应用层越来越难管这类问题这篇文章应该对你有用。Atlas 在技术圈里其实有年头了。早期由奇虎360团队开源后来不少大厂基于它做过二次开发并持续贡献代码社区也积累了大量生产案例。我一直觉得它最大的价值不是在某个特定版本里炫技而是提供了一套“应用无感知”的数据库访问方案业务侧不需要改SQL只需要把数据库连接地址指向 Atlas剩下的路由和分发由代理来完成。这套东西适合谁适合后端开发、DBA、架构师。尤其是那些已经在拆库拆表但发现代码里到处都是按ID取模、到处手动切换数据源的项目组。如果你还没到那个阶段也可以先了解下代理层的工作原理至少在方案选型的时候不吃亏。1. Atlas 到底解决什么问题从单体数据库到代理层1.1 分库分表后应用层为什么越来越难写在有 Atlas 这类中间件之前业务代码要玩分库分表基本是自己在DAO层写路由规则。订单表按买家ID取模拆成 16 张查询的时候先算一下 userId % 16 落在哪张表后台管理要全表统计就只能遍历所有分片再聚合。这还只是查询等到做事务、做批量插入、做分页排序的时候那代码基本没法看了。我自己见过最夸张的项目一个用户查询方法里嵌套了三层switch-case每层对应一个分库策略还专门写了一个工具类解析分片键。后来产品和运营要加新维度查询开发说改不动了因为路由逻辑已经跟业务SQL耦合死了。这类痛点的本质是路由规则本身是一个独立的系统问题不应该散落在业务代码里。Atlas 这类数据库代理把路由规则收敛到配置层让应用只管连接SQL正常写这就是它的核心价值。1.2 代理层 vs 应用层路由 vs 新一代数据库说到方案选型很多人会问为什么不用应用层路由为什么不用 ShardingSphere为什么不用 NewSQL我的理解是这样的。应用层路由就是自己写路由规则最灵活但代价是每个团队都要维护一套自己的规则引擎而且SQL写法会被强制约束。Atlas 这种代理层的方案是把路由做在客户端和数据库之间业务侧几乎无感部署和运维集中在中间件上。它的缺点是功能边界比完备的中间件生态要小一些比如跨分片事务、跨分片JOIN这些能力有限所以团队必须约定SQL规范。ShardingSphere 走的是另一种路它可以在应用内ShardingSphere-JDBC或中间层ShardingSphere-Proxy工作功能更丰富生态也更完整。但对应的学习成本和配置复杂度也更高。NewSQL比如TiDB则是彻底换数据库如果你能接受技术栈更替那自然是最省心的但很多遗留系统根本不可能在短期内把 MySQL 换掉。Atlas 最适合的场景恰恰是那种“暂时还不想换库、又不想把路由逻辑写进业务代码”的过渡期。它能帮团队把分库分表的事情从代码里抽出来让业务先跑顺后续再决定要不要往更重的方案迁移。1.3 它做不到的事先认清楚边界用 Atlas 之前有几个边界必须搞清楚不然上线就是坑。第一它不适合做跨分片的复杂查询优化。代理层能做的是根据分片键把SQL路由到正确的分片但如果一个查询没有带分片键代理只能把SQL广播到所有分片再把结果合并回来。并发一大代理本身可能成为瓶颈。第二跨分片事务是硬伤。Atlas 解决的是单分片内的写操作做不到多个分片间的分布式事务。业务设计时得把事务尽量控制在单分片内。第三它不改变SQL语义。代理层不是查询优化器不会因为你SQL写得烂就把性能变好。慢SQL该优化还是要优化。把这些边界想清楚你才能判断 Atlas 适不适合你的场景。2. 部署与核心配置拆解从零搭起一套可用环境2.1 部署架构与前置条件Atlas 的部署形态不复杂。它本质是一个独立进程对外提供一个 MySQL 协议端口客户端连接它就像连接 MySQL 一样。后端连接到真实的 MySQL 实例或主从集群。最少情况下一台 4C8G 的虚拟机就能跑一个 Atlas 实例但生产环境建议至少两台配合 LVS 或 Keepalived 做高可用。从我的实践来看部署前先把下面几件事做好MySQL 主从要提前搭好主库负责写、从库负责读至少保证主从复制正常。分片表结构要提前在多个库/多张表上建好Atlas 不会自动帮你建表。确定分片键和分片策略。订单表用 order_id 做 hash 还是 range要在配置前定清楚因为上线后改分片规则代价很大。2.2 main.conf 逐项解读路由规则、账号体系与连接池安装完成后核心配置文件在 conf/main.conf。我用一个最小可运行的配置来逐项说明[mysql-proxy] proxy-address 0.0.0.0:1234 admin-address 0.0.0.0:2345 admin-username admin admin-password admin proxy-backend-addresses 192.168.1.10:3306 proxy-read-only-backend-addresses 192.168.1.11:3306,192.168.1.12:3306 pwds report:******** daemon true proxy-pool-no-change true proxy-range-tables order_info:order_id:0-9999,10000-19999,20000-29999 proxy-hash-tables user_info:user_id:0-3 log-path /var/log/mysql-proxy log-level debug几个关键项我重点说一下。proxy-address 是应用连接的地址端口随意但别跟现有服务冲突。proxy-backend-addresses 是写库proxy-read-only-backend-addresses 是只读从库多个从库用逗号分隔Atlas 会做负载均衡。pwds 这里填的是“用户名:加密密码”不是明文密码需要用 bin/encrypt 工具生成这个细节经常有人忽略。分片规则写起来很直观。proxy-range-tables 表示按范围分片order_info 表按 order_id 分成 0-9999、10000-19999 三段分别对应三个分片配置顺序对应后端库顺序。proxy-hash-tables 表示按哈希取模user_info 按 user_id 对 4 取模分成四片。这里要注意range 和 hash 只能二选一同一张表不能同时配两种规则。接下来是连接池。proxy-pool-no-change true 这个参数的意思是让连接池不自动调整后端连接数。我建议生产环境保持这个配置因为自动调整在一些版本里会导致连接数不稳定。启动服务用 service mysql-proxy start 或者直接执行 /usr/local/mysql-proxy/bin/mysql-proxy 都可以。然后用 mysql 客户端连一下mysql -h 127.0.0.1 -P 1234 -u report -p能正常执行 SELECT 1说明代理已经通了。2.3 高可用方案LVS Keepalived 的接入思路单机 Atlas 一旦宕机所有业务数据库访问都会挂掉所以生产环境必须考虑高可用。我常用的方案是两台 Atlas 实例 Keepalived 提供 VIP应用层只连 VIP不感知后端的 Atlas 切换。Keepalived 做的是虚拟IP漂移配置上跟数据库本身关系不大核心是保证同一时间只有一台 Atlas 在工作。需要注意两件事一是 Atlas 的配置要在两台机器上保持一致尤其是 pwds 加密串和分片规则二是切换后旧实例上的 MySQL 连接还挂着需要做好连接超时时间控制否则切换瞬间应用会有一批报错。如果你已经有 LVS 基础设施也可以让 LVS 直接把流量分发给两台 Atlas。Atlas 本身是无状态的它不保存会话状态理论上可以水平扩展。但我实测下来连接数比较高的场景还是 Keepalived 主备模式最稳故障切换时间短也不会出现多实例同时写的问题。3. 关键机制实现与业务侧接入让应用真正跑起来3.1 分表规则设计从订单表说起路由规则的设计是整个接入过程中最需要花心思的地方。我先用一个电商订单表的例子来拆。假设订单表 order_info 月新增千万级数据单表已经扛不住我们决定拆成 32 张物理表分布在 4 个库上。分片键选什么绝大多数情况下选订单号或者买家ID选型原则是这条业务数据访问时是否总能带得上这个字段。如果用户下单后只能拿到 order_id那就用 order_id 做分片键如果管理员后台需要按商户查订单还得有 mch_id那就得权衡一下到底是按买家维度分片还是按商户维度分片。按 hash 取模配置就是proxy-hash-tables order_info:order_id:0-31Atlas 会解析 INSERT / UPDATE / DELETE / SELECT 语句里的 order_id 条件算出模值路由到对应分片。注意一点SQL里必须带着分片键条件而且条件要是等值条件。如果写成 order_id IN (1,2,3,4)Atlas 也能处理但如果是 order_id 1000 这种范围查询走 hash 分片时会广播到所有分片性能就比较难看了。3.2 读写分离与多从库负载均衡Atlas 的读写分离判断很简单SELECT 走从库INSERT / UPDATE / DELETE 走主库事务内语句强制走主库。这个逻辑听起来简单实际操作中有几个关键细节很容易踩。事务内强制走主库是必须的。如果事务里第一条是 SELECT 路由到从库然后 UPDATE 走主库主从延迟时就会出现读到旧数据或者干脆读不到已更新数据。Atlas 一旦发现客户端发了 BEGIN整个连接后续语句都锁定在主库直到 COMMIT / ROLLBACK。所以业务侧要有这个意识长时间挂起的事务会一直占用主库连接导致主库连接池被打满。多个从库的负载均衡是自动做的但算法是轮询还是最小时延不同版本有差异。我的经验是如果你有大量重查询报表、批量导出在从库上跑最好把这类流量隔离开不要把重查询和在线业务复用同一组从库否则负载均衡再均匀也扛不住慢SQL堆积。3.3 业务侧接入注意事项语法限制与SLA约定接入 Atlas 不是改个连接串就完事了团队内部得先定一套 SQL 规范。我总结了几条硬约束所有路由相关的查询都带分片键且使用等值条件。禁止跨分片 JOIN。确需跨分片的数据由应用层做二次组装。事务边界要小尽量控制在单分片内。避免 SELECT * 不带 WHERE这类 SQL 会被广播到全部分片。存储过程、自定义函数、临时表等特性在 Atlas 层支持很有限尽量不用。这些约束看起来多但实际执行下来对业务影响最大的就是第1条和第3条。很多团队在接入时没把这套规范讲清楚上线后才开始返工。4. 常见问题与排障实录运行中踩过的坑4.1 连接数暴涨Atlas 直接拒绝服务我遇到过最典型的问题是应用连接数突然暴涨Atlas 报 “too many connections”。这个问题的根子往往不在 Atlas而在应用侧没有管理好连接池。很多应用框架默认连接池不够用时会新建连接一旦后端接口变慢应用就会疯狂创建新连接把 Atlas 的连接数打满。排查思路分三步先在 Atlas 管理端口执行 SELECT * FROM status 看当前连接数再去应用侧看连接池配置把最大连接数收敛到一个合理范围最后看后端MySQL的 max_connections确认没被顶到上限。Atlas 本身的连接队列容量也可以调大但不能只调这个治标不治本。4.2 分页排序结果不对跨分片 SQL 的隐藏坑有段时间运营反馈后台分页数据偶尔会重复查了很久才发现问题出在跨分片 ORDER BY LIMIT 上。Atlas 会把一条带 ORDER BY 和 LIMIT 的SQL广播到多个分片每个分片各自排序取前 N 条然后由代理合并排序后再取前 N 条。听起来没问题但如果排序字段不是唯一键或者数据分布不均匀合并后的结果和真实全局排序就会不一致。这种情况的解决办法要么是改业务设计避免跨分片全局排序要么是让应用层自己把每个分片的TOP N拉回来再合并。两种方案都不完美但至少要知道根因在哪。4.3 主从延迟带来的读不到数据读写分离后经常听到的一句话是数据刚写完就读不到。这基本就是主从延迟。Atlas 本身不解决延迟问题它只负责把读流量分到从库。要缓解这个问题可以从几个方向入手核心写后读场景业务层强制走主库对延迟敏感度低的查询可以在应用层做个小的延迟重试监控从库 Seconds_Behind_Master超过阈值时临时把这台从库剔除出读组。管理端口操作后端状态很简单SET OFFLINE 1; SET ONLINE 1;把故障或延迟的从库先摘掉恢复后再上线这个操作在 Atlas 管理接口里都有。要注意的是SET OFFLINE 会断开该后端上的现有连接尽量在业务低峰期操作。4.4 监控与告警Atlas 性能指标的观测方法最后聊聊监控。Atlas 管理端口提供了不少状态信息我常用的几条命令是SELECT * FROM status; SELECT * FROM backends; SELECT * FROM help;status 里能看到当前连接数、请求总数、慢查询数等统计backends 里能看到每个后端的状态。生产环境建议把日志级别开成 debug 也不怕只要定期清理日志就行因为 debug 日志在异常排查时是唯一能还原现场的东西。慢SQL记录在 log-path 下可以用来反推业务侧的SQL问题。我自己的监控告警习惯是每5分钟采集一次连接数、请求量、后端状态对连接数设置95%容量告警对后端状态设置非ONLINE即告警。Atlas 适合作为学习数据库代理的第一站。它的源码不复杂读完你能理解SQL解析、路由、读写分离这些概念是怎么落地的。如果后面你们团队真要搞大规模分库分表加分布式事务基于 Atlas 学到的这一套底层逻辑再去看 ShardingSphere、TiDB 这些方案会轻松很多。我个人在实际接入中的体会是选型的关键不在于谁功能多而在于你的团队能不能在它设定的边界内写出清楚SQL规范。一个小建议是正式上线前拉一次全量SQL评审把不带分片键的SQL全部找出来先改掉比上线后再补规范省太多事。