资讯动态

数据库中间件 Atlas 实战:读写分离与分库分表全解析

发布时间:2026/10/19 3:48:44 来源:尧图企业网站定制
做后端的人应该都经历过这么一段业务量涨上来了单库单表开始扛不住慢查询一批接一批你想着拆库拆表又不敢动业务代码因为牵一发动全身。Atlas这个名字我第一次看到的时候是在一份数据库中间件选型清单里。它最早是360内部用来扛海量请求的MySQL代理层后来开源出来定位非常明确让业务层只把它当成一个普通MySQL实例去连读写分离、分库分表、故障切换全部在这个中间层解决。这篇就围绕Atlas这个项目从原理到部署到踩坑把我知道的完整捋一遍。如果你正在做数据库容量规划或者想找一个对业务侵入最小的分库分表方案这篇应该能帮上忙。 ## 1. 项目定位与整体设计思路 ### 1.1 Atlas是什么它解决的是哪一类问题 Atlas说白了就是一个基于MySQL协议的代理层部署在应用和真实MySQL集群之间。应用不需要感知背后有几台数据库、哪些是主库哪些是从库、数据被拆成了几个分片它只需要连接Atlas监听的前端端口把SQL扔过去剩下的路由、转发、合并结果这些脏活累活Atlas都替你干了。 这个设计解决的是最核心的一类问题当数据量和请求量增长到单库瓶颈时你希望以最小的成本把容量摊开。如果不用代理业务代码里就要自己管理数据源、自己判断读写走哪个库、自己处理分库分表的路由逻辑一旦拓扑调整还得跟着改代码。用Atlas这类中间件业务只需要维护一个连接地址后端加一台从库、拆一个分片对应用来说完全透明。 我之前在一个日活几十万的项目里就是用了这个思路。客户端请求打到应用应用只连一个代理地址代理后面挂了四台MySQL一主两从加一个分片。对应用来说它连的始终是一个数据库但这个数据库的吞吐量已经是单机的好几倍。这就是Atlas存在的意义把复杂度隔离在独立一层不让它腐蚀业务代码。 ### 1.2 为什么选Atlas而不是ShardingSphere或MyCat 很多人会问市面上分库分表中间件那么多ShardingSphere在Java圈子普及度很高MyCat也有一票老用户为什么还要用Atlas我当时的判断标准其实很朴素看三点协议兼容性、业务侵入程度、团队技术栈。 | 对比维度 | Atlas | ShardingSphere | MyCat | | --- | --- | --- | --- | | 对业务代码侵入 | 完全透明连类MySQL地址即可 | 需要引入JDBC驱动或Proxy模式 | 相对透明需复杂XML配置 | | 协议支持 | 原生MySQL协议PHP/Go/Java通吃 | JDBC模式仅限JavaProxy模式支持多语言 | 原生MySQL协议 | | 读写分离 | 支持自动分离事务内自动走主库 | 支持需显式配置 | 支持延迟控制需手动 | | 分片策略 | 哈希、范围 | 丰富支持复杂分片算法 | 范围、哈希、取模等 | | 运维复杂度 | 低单进程配置文件 | 较高依赖注册中心 | 中依赖ZK | | 社区活跃度 | 更新节奏一般稳定够用 | 高功能迭代快 | 中 | 我当时团队是PHP为主、Java和Go混着用ShardingSphere那种依赖Java生态的方案直接出局因为PHP没法用JDBC模式Proxy模式等于又套了一层重的东西。MyCat当时给我的感觉是功能确实多但配置项太多上手成本高遇到问题定位很费劲。Atlas胜在轻量编译出来就一个二进制和一份配置文件核心功能覆盖了我需要的读写分离和哈希分片够用且不折腾。 当然成熟的方案没有绝对的优劣关键是匹配你的场景。Atlas的定位是稳不是全。它不会给你几十种分片算法但核心那几种已经经过了大规模流量验证。 ### 1.3 整体架构里每一层在干什么 完整落地时不是只跑一个Atlas就完事它前面通常还要叠一层负载均衡架构大概长这样 客户端连接过来先打到LVS或者Keepalived暴露的VIP上VIP后面挂两台Atlas节点做主备或者负载均衡。Atlas再往下连的是真正的MySQL集群主库负责写和事务内的读从库负责纯读流量分片场景下后端按分片键拆成多组。 每个节点做的事情要理清楚 - LVS/Keepalived层负责把前端连接分发到多台Atlas某一台Atlas挂掉后自动踢出保证应用永远只认一个VIP地址。 - Atlas层承担SQL解析、路由决策、读写分离、连接池管理、后端MySQL故障剔除。这是整个架构的交通枢纽。 - MySQL集群层真正存取数据的地方主库还是从库、分片还是不分片由上层路由规则决定。 这个结构最关键的设计思想是每一层只干一件事。LVS只做流量分发Atlas只做路由和代理MySQL只负责存数据。出了问题也好排查连不上先查VIP在不在VIP没问题看Atlas有没有活着Atlas活着就看后端MySQL主从是否正常。层级清晰故障定位成本极低。 ## 2. 核心功能拆解与原理 ### 2.1 SQL解析与路由它怎么知道这条SQL该去哪 Atlas接受到一条SQL后第一步是解析。它不是简单地判断SQL前缀是不是SELECT而是会做一个完整的语法解析构建出这条语句的语义然后根据语义决定路由。 整个判断流程大概是这样的先判断语句类型INSERT/UPDATE/DELETE这类写操作直接走主库SELECT操作看当前会话是否处于事务中事务内的读为了保持一致也必须走主库事务外的读走从库。接下来再判断是否涉及分片表如果这张表配置了分片规则就从WHERE条件里提取分片键的值根据哈希或范围计算出它落在哪个分片上。 这里有个很重要的点分片键的选择决定了路由能不能生效。我在一个订单系统里用的是订单ID作为分片键哈希取模之后平均散到四个分片里。但有一次业务方做了一个后台查询条件是查某个用户最近一个月的订单SQL里根本没带订单IDAtlas没法计算分片位置只能把这条SQL转发到所有分片上去聚合。一次两次没问题量大之后全分片扫描的代价就体现出来了。 所以分片键的选型原则是高频查询尽可能都带这个键键的分布要均匀键值最好不可变。用订单ID这种业务主键是最常用的方案因为它天然唯一、分布随机、且大多数查询都是围绕主键展开。 ### 2.2 读写分离从库延迟是最大的坑 读写分离的逻辑本身不复杂写主读从问题全出在延迟上。主库刚写完一条数据从库还没同步过去这时候读请求到了从库读到的还是旧数据。这在某些业务场景是不能忍的比如下单后立刻查询订单详情发现订单不存在这在用户侧就是个实打实的bug。 Atlas处理这个问题的机制比较实用。默认情况下它会自动判断当前会话状态一旦进入事务也就是执行了BEGIN这个连接后续所有的读写都固定走主库直到事务结束。这个设计从根本上规避了事务内先写后读读到旧数据的问题因为同一个事务里所有操作都在同一台主库上执行。 但事务外的读写分离就没法百分之百保证强一致了。我的做法是双保险第一在业务代码层对实时性要求极高的数据查询显式加上强制走主库的标识比如在SQL前拼一个特殊注释第二在Atlas层配置从库延迟阈值超过阈值的从库会被临时标记为不可用读流量自动切回主库等它追平后再恢复。这两层叠加下来线上能感觉到延迟问题的概率就非常低了。 还有一点容易被忽略从库不是只用来读数据的它还承担着主库故障时的接管任务。所以从库尽量不要设置超出主库太多的查询量要保持它的延迟在可控范围内。否则真轮到主库宕机、从库晋升主库的时候它自己已经累得半死接管质量是要打问号的。 ### 2.3 连接池与会话绑定 Atlas本身不是一个无状态的应用它维护了前后两端两套连接前端连接来自业务应用后端连接指向MySQL集群。前端几十个应用连接不可能每人持有一条后端连接那就得做连接复用。 它的做法是类似连接池的机制但有一个很关键的设计会话绑定。同一个客户端会话在处理它的请求时会尽量在后端连接池里选择一个固定的连接来使用。这样做的好处是能保持会话内的状态一致性比如临时表、会话变量、锁这些MySQL里的状态都是跟连接绑定的。如果每一条SQL都随机换一个后端连接那会话级的临时表瞬间就丢了业务会出各种莫名其妙的怪问题。 但连接池不是越大越好。我见过有人把Atlas的前端最大连接数配成几万以为这样能扛住更大的并发结果后端MySQL的连接数也被撑爆了数据库先挂了。连接数的设计必须从上到下成比例应用连接数不能超过Atlas的最大连接数Atlas的总连接数不能超过MySQL能承受的最大连接数。一般经验是每台Atlas的前端最大连接数设置在2000到5000之间后端连接数按业务实际并发评估留20%到30%的余量。 ### 2.4 高可用主库挂了以后发生了什么 Atlas在做高可用时核心能力是感知后端MySQL节点的存活状态。它会定期向后端MySQL发送探测指令如果主库连续多次没有响应就把这个节点标记为不可用然后将写流量切换到备库上。 但这里面有一个容易被忽略的细节主从切换不是Atlas单方面就能完成的。主库宕机后从库可能还有一部分binlog没来得及应用直接切换会丢数据。所以生产环境里主库和从库之间必须开启半同步复制。半同步复制的意思是主库在提交事务后要等从库确认收到了binlog才算本次写入成功。这个机制虽然会让写请求多一次等待但能在很大程度上保证主从切换时数据是完整的。 还有一个血泪教训一定要加一台独立的监控节点用来盯双主或者脑裂的情况。我有一次遇到主库网络抖动Keepalived误判主库挂了把VIP切换到备库结果网络恢复后原主库还活着业务瞬间出现两个主库同时接受写请求数据冲突一片混乱。事后我们加了一个专门做交叉检查的监控脚本确认一个集群里同时只能有一个主库对外提供写服务这个坑才算填上。 ## 3. 部署与实操配置 ### 3.1 安装与环境准备 生产环境我建议从源码编译安装Atlas虽然编译过程多花几分钟但能保证和你操作系统的glibc版本是匹配的避免在线上运行时踩动态链接库的坑。编译机器上需要准备gcc、make、glibc-devel这些基础工具链。 bash # 以CentOS 7/8类的环境为例 yum install -y gcc gcc-c make glibc-devel # 下载源码并解压 wget Atlas源码包地址 tar -zxvf atlas-src.tar.gz cd atlas # 编译安装指定安装目录 ./autogen.sh ./configure --prefix/usr/local/mysql-proxy make make install安装完成后目录结构大概是这样的/usr/local/mysql-proxy/ ├── bin/ # 主程序mysql-proxy和加密工具encrypt ├── conf/ # 配置文件目录 ├── log/ # 运行日志和SQL日志 ├── lib/ # 依赖的动态库 └── share/ # 文档和脚本编译安装这件事本身不难但我踩过一个坑编译时要留意内存大小源码编译过程中如果内存不够会莫名报一些奇怪的错误然后编译中断。建议编译环境内存至少给到2GB以上否则排查问题会很痛苦。3.2 后端MySQL主从搭建Atlas本身不负责数据同步所以后端MySQL的主从需要你自己先搭好这里快速过一遍。以MySQL 5.7以后的版本为例GTID模式的复制配置起来非常简单。主库上创建复制账号并开启binlog-- 主库执行 CREATE USER repl% IDENTIFIED BY your_password; GRANT REPLICATION SLAVE ON *.* TO repl%; FLUSH PRIVILEGES;从库上指定主库并启动复制-- 从库执行 CHANGE MASTER TO MASTER_HOST主库IP, MASTER_PORT3306, MASTER_USERrepl, MASTER_PASSWORDyour_password, MASTER_AUTO_POSITION1; START SLAVE;从库千万别忘记设置成只读不然一条误操作写进从库主从一致性就毁了SET GLOBAL read_onlyON; SET GLOBAL super_read_onlyON;我一般还会特意打开半同步复制插件并把超过特定秒数未确认的同步降级为异步。这个降级开关很关键不然一旦从库卡住主库的写入也会被拖住形成全链路阻塞。3.3 Atlas核心配置逐项解读Atlas的配置文件是一份ini风格的文本。我贴一份生产上跑过的精简配置然后把关键项逐个讲清楚。[mysql-proxy] # 管理接口用于查看状态 admin-address 127.0.0.1:2345 admin-username admin admin-password admin123 # 前端入口应用连接这个地址 proxy-address 0.0.0.0:1234 # 后端MySQL节点列表 mysql-address 192.168.1.10:3306backend, 192.168.1.11:3306backend # 应用访问账号用encrypt工具加密 pwds app_user:9F2B9C7A3E1D5A0B # 分片规则 tables testdb.orders hash(order_id) 0-1023 # 进程与日志 daemon true event-threads 8 log-level message sql-log REALTIME log-path /usr/local/mysql-proxy/log逐项说几个重要的mysql-address这一项就是后端真实MySQL的地址列表backend是角色标记表示这些节点是后端数据节点。多台节点之间Atlas自己会做读写分离调度。pwds这一项里的密码不是明文是用Atlas自带工具生成的加密串/usr/local/mysql-proxy/bin/encrypt your_plain_password它会输出一串密文把这个密文填到配置文件里。这里很容易踩坑有人直接把明文填进去结果应用连上来一直报认证失败排查半天发现是密码格式不对。tables是分片规则配置语法值得细品。testdb.orders hash(order_id) 0-1023的意思是testdb库里orders这张表按order_id字段做哈希散列散列范围是0到1023也就是分成1024个分片。实际部署时你不需要物理建1024张表这个数字对应的是逻辑分片Atlas会根据MySQL节点的数量把分片集合分配到各台机器上。event-threads是Atlas内部的事件线程数建议和CPU核数保持一致不需要盲目调大。sql-logREALTIME会让Atlas记录每一条经过的SQL日志这项在生产环境建议打开后面排查问题会非常有用。3.4 启动、验证与上线配置好后启动Atlas/usr/local/mysql-proxy/bin/mysql-proxy \ --defaults-file/usr/local/mysql-proxy/conf/test.cnf然后检查进程和端口ps -ef | grep mysql-proxy netstat -lnp | grep 1234验证读写分离是否生效我是这么做的在应用连接字符串时单独起一个mysql客户端连到Atlas的前端地址分别执行查询SQL和写入SQL然后去后端MySQL上看日志看这两条SQL分别落在哪台MySQL上。mysql -h 127.0.0.1 -P 1234 -u app_user -p执行SELECT * FROM testdb.orders WHERE order_id 100;然后去从库上看慢查询日志或者通用日志如果这条SELECT出现在从库上说明读流量分离成功。再执行BEGIN; INSERT INTO testdb.orders(order_id, user_id) VALUES(100, 1); COMMIT;如果这条INSERT出现在主库上且事务结束后下一次SELECT又回到了从库那读写分离和事务内路由就都验证通过了。这个验证方法我每次上线新环境都会做一遍虽然简单但能快速把最核心的路由链路确认掉。3.5 压测与容量评估上线前不做压测等于裸奔。我用的工具是sysbench这里给一个粗略的压测思路。# 准备一张测试表 sysbench /usr/share/sysbench/oltp_read_write.lua \ --mysql-host127.0.0.1 \ --mysql-port1234 \ --mysql-userapp_user \ --mysql-passwordyour_password \ --mysql-dbtestdb \ --tables10 \ --table-size1000000 \ prepare # 执行压测开64个线程跑5分钟 sysbench /usr/share/sysbench/oltp_read_write.lua \ --mysql-host127.0.0.1 \ --mysql-port1234 \ --mysql-userapp_user \ --mysql-passwordyour_password \ --mysql-dbtestdb \ --threads64 \ --time300 \ run压测结束会输出QPS、TPS、延迟分位数据。我实测单台Atlas在8核16GB配置下OLTP读写混合场景能稳定跑到四万到五万QPS单条SQL平均延迟在2毫秒以内。这个数字可以当场算容量假设业务高峰峰值需要20万QPS那理想情况下至少要准备5台Atlas节点再考虑一台宕机、一台维护实际部署6到7台比较稳妥。别忘了压测时后端MySQL的负载也要一起观察很多时候瓶颈根本不在Atlas而是后端数据库先被打满。4. 常见问题与排查实录4.1 无分片键查询被拒绝Atlas对于分片表的查询如果SQL里没有带分片键的条件默认行为是拒绝执行并报错避免全分片扫描导致后端雪崩。第一次遇到这个问题的人会觉得中间件怎么这么不智能但等你线上出过全分片扫描拖垮所有库的事故就会发现这个不智能恰恰是最重要的保护。解决方案有两个。一个是业务侧尽量把分片键带上比如查询订单就尽量带订单ID这是最健康的做法。另一个是如果确实存在不带分片键的低频查询就单独建一张汇总表或者走搜索引擎不要在中间件层面硬扛。我遇到过最典型的场景是运营后台的筛选功能条件多、组合多、完全不从主键出发。这种需求我就没让Atlas去扛而是直接同步了一份宽表到独立的分析库让后台查询走另一条链路互不干扰。4.2 跨分片事务的处理跨分片事务是中间件方案里绕不开的痛点。Atlas对单分片内的事务支持得很好但一个事务里同时操作多个分片它没办法保证原子性和隔离性这是轻量级代理方案的通病。规避方案没有银弹只能从业务设计上拆。我常用的思路是让一个事务尽量只操作一个分片。具体做法是把业务维度绑定到分片键上比如同一个用户下的所有操作分片键都用用户ID而不是订单ID那用户维度的多表更新大概率落在同一个分片内事务自然成立。如果业务确实需要强一致地跨分片操作那就需要考虑XA分布式事务或者TCC方案但代价是性能损耗和复杂度上升。我的建议是除非业务强约束要求否则在OLTP链路里尽量避免跨分片事务通过最终一致性和消息队列去消化那些跨分片的数据变更。4.3 主从延迟导致的数据不一致读不到刚写入的数据这个问题前文提到过最直接的解法是强制走主库。具体在Atlas里你可以通过配置和SQL注释配合使用。我当时的做法是在业务代码里封装一个数据访问方法凡是刚写完就要立刻读的请求自动在SQL前加上走主库的注释标记其他读请求走从库。这样既保证了实时性要求高的场景不出bug又不会把所有读流量都压在主库上。还有一个细节Atlas会对从库做延迟探测当从库延迟超过配置的阈值时会自动摘除该从库的读权重。这个阈值我一般设置不超过3秒。设置太短主库瞬间压力剧增设置太长读到旧数据的窗口就被拉大了。3秒是我在业务容忍度和主库压力之间找到的一个平衡点。4.4 通过日志定位慢SQL与错误路由Atlas开启sql-log之后每一笔SQL都会记录时间、来源连接、目标后端、执行耗时。有一次线上反馈某接口变慢我第一件事就是打开Atlas的SQL日志grep这个接口对应的SQL片段一秒钟就定位到有一条带分片键的查询在某一个分片节点上耗时异常。顺着查下去发现那个分片上的磁盘IO被打满了因为另一个统计任务正在全表扫描。日志真的是中间件排查的第一利器。我强烈建议把SQL日志落盘并对接日志采集系统保留至少7天的历史。这样不仅能在出问题时快速回溯还能定期从日志里挖出慢SQL做治理。相比如大海捞针地找业务方要现场日志才是最有说服力的证据。4.5 连接不通、权限报错排查速查表运维过程中常见的连接类问题我整理了一张速查表按症状、原因、对策三列排开方便排查的时候直接对号入座。症状常见原因处理对策应用连接超时LVS或Keepalived没配置好先测VIP是否通畅再检查Atlas节点健康检查脚本认证失败pwds配置里是明文密码用encrypt工具重新生成密文并替换能连上但查不了分片表客户端IP不在白名单检查client-ips配置添加对应IP段写操作全部失败主库节点被标记为down检查MySQL主从状态确认主库是否存活读流量全部压在主库从库延迟过大被摘除排查从库复制线程追平延迟后自动恢复管理接口连不上admin-address绑定了127.0.0.1如需远程管理改为内网IP并加强认证这张表是我现场出问题时最常翻的工具省了很多重复排查的时间。5. 演进方向与项目适用边界Atlas不是万能的用了一年多之后我对它的边界有了很清楚的认知。它在OLTP场景、请求量没有夸张到天量、分片规则相对简单的场景下确实又稳又省事。但如果你的业务需要极其复杂的分片策略比如动态分片、多级分片、或者数据需要在分片间迁移Atlas的配置方式就显得捉襟见肘。此时可以考虑往ShardingSphere这类功能更重的框架演进但代价是引入更多的组件和运维复杂度。流量再往上走一个量级之后中间件分片也不是终极答案。我更倾向于的演进路径是先垂直拆分再水平拆分第一步把核心业务和边缘业务拆成独立的服务各自用独立的数据库集群这一步能让数据量细粒度地变小第二步在仍然膨胀的核心集群上用Atlas这一类中间件做读写分离和简单分片如果第三阶段单分片还是撑不住再把热点分片进一步拆分。最后再分享一个我自己的体会选型一个中间件不要只看它有多少功能要看你是否真正理解它的运作机制。Atlas把数据库路由问题简化到极致但它并不会替你兜住所有设计缺陷。我在实际使用中最受益的一件事不是学会怎么配置它而是被迫在业务设计阶段把数据访问方式想得更清楚哪些查询必须带分片键哪些事务不能跨分片哪些读可以接受延迟。这些思考沉淀下来才是中间件带给团队最大的长期价值。如果你正在做类似的技术选型也希望你能在这条路上多踩出一些自己的经验。

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

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

免费获取报价 →
↑