最近在整理一套老系统的数据库访问链路,项目代号就叫 Atlas。这套东西本质是一个放在应用和 MySQL 之间的访问中间层,用来统一解决读写分离、分库分表、连接管理这些烦心事。你可能也遇到过类似情况:业务量上来之后,单库单表扛不住,应用层到处写死数据库地址,一到大促就要临时拆库,代码改到想骂人。Atlas 要做的就是让应用侧只认一个入口,真正落在哪台库、哪张表,由中间层去决策。这篇东西我打算从项目整体设计讲到核心原理,再到部署实操和排障经验,全程用我实际踩过的坑来铺垫。适合正在做数据库拆分、读写分离,或者被连接数打满折磨过的后端开发、DBA 和架构师参考。即便你是第一次接触中间件,只要跟着走一遍,也能理解它到底怎么工作、能解决什么问题。1. 项目定位与方案拆解1.1 Atlas 在整个数据链路中扮演什么角色先打个比方。以前应用直连数据库,就像每个员工都直接跑到仓库去拿货,仓库窗口就那几个,人一多就排队,偶尔有人拿错货还没人管。Atlas 就好比在仓库门口加了一个分拨台,所有取货请求先到这里登记,分拨台根据货单内容决定你去哪个窗口,顺带还能帮你把一些零散的小请求合并成大订单。放到技术链路上看,Atlas 的位置非常清晰:应用层通过标准的 MySQL 协议连上 Atlas 的端口,Atlas 解析 SQL 之后,再往后端真实的 MySQL 实例转发。应用侧根本感知不到后端有几台机器、哪些是主库、哪些是从库。这个方式的优势在于,数据库拓扑变更不用再通知所有业务方,只需要改中间层配置。我在做这套项目时,最直观的感受是:连接数和流量可控了。以前几百个服务实例每个都保持几十个长连接,数据库光握手就要耗尽 CPU。Atlas 收口之后,后端连接数变得非常平稳,应用扩容也不用担心把数据库连接池打爆。说白了,它让数据库从“被很多人直接操作”变成“被一个有脑子的人统一调度”。1.2 核心功能清单与方案选型对比Atlas 的核心功能我整理成了一张表和直连方案对比,大家看得更清楚:功能维度直连数据库Atlas 中间件后端地址管理每个应用配置自己的连接串统一入口,后端变更对应用透明读写分离应用自己实现,代码侵入大中间层按 SQL 类型自动路由分库分表应用层判断并拼接表名按分片键路由,应用无需关心连接池复用每个服务维护一堆连接中间层统一复用,降低连接数故障摘除依赖应用侧重试机制健康检查后自动剔除故障节点监控审计分散难汇总统一日志与统计,便于排查为什么不直接选应用层改造或者引入其他重量级方案,而是用 Atlas 这类中间件?我当时的考虑有几点。第一,应用层改造成本太高,几十个服务都要动一遍,光排期就要跨两个迭代。第二,很多轻量级方案对分库分表的支持很弱,尤其是一致性事务场景,自己写容易挖坑。第三,Atlas 这类基于 MySQL 协议的代理方案对业务代码基本透明,切换成本低,上线失败也容易回滚。当然它也有短板,比如跨库 JOIN 支持不好,分布式事务需要业务配合。但这不是 Atlas 的问题,而是所有分库分表都要付出的代价。你选的不是完美方案,而是在当前阶段收益最大、代价最小的方案。1.3 整体架构拓扑与数据流向Atlas 的部署形态不复杂,核心就三层。最上层是接入层,也就是应用服务器,它们只需要配置一个虚拟 IP 或者域名,指向 Atlas 所在的主机,端口可以是常见的 3306,也可以换成别的。中间是 Atlas 节点,建议至少部署两个,一台为主、一台为备,配合 Keepalived 之类的工具做高可用切换。下面是真实的 MySQL 实例集群,分为主库和若干从库,主库负责写入,从库负责读取。数据流向大概是这样的:应用发出 SQL 请求,Atlas 先解析这条 SQL 的类型和涉及的表,然后根据配置的路由规则,以及当前各节点的健康状态,决定把请求转发给哪个后端实例。如果是SELECT,默认会挑一个负载较低的从库;如果是INSERT、UPDATE、DELETE,或者会话开启了事务,就走主库。如果配置了分库分表,还需要根据分片键算出具体落到哪个库、哪张表。这里有个容易被忽略的点:Atlas 自身是无状态的,也就是说它不存业务数据,只是做转发和路由。这样设计的好处非常明显,一旦某台 Atlas 宕机,另一台可以直接顶上,数据不会丢。坏处是配置要同步好,不然两个节点的规则不一致,会引发路由错乱。我后续会专门讲怎么用脚本同步配置和管理节点。2. 核心机制与关键原理2.1 读写分离的实现细节与路由策略读写分离是 Atlas 最常用的能力,但很多人只知道“SELECT 走从库,其他走主库”,实际做起来没有那么简单。路由的判断不仅是看 SQL 的第一个关键词。比如SELECT ... FOR UPDATE,虽然以 SELECT 开头,但它带了行锁,必须走主库,否则你在从库加锁,主库的数据还在被别人改,业务就出大问题。再比如事务内的所有语句,只要你在一个显式事务里,哪怕全是 SELECT,也应该全走主库。原因是事务的隔离级别和快照读在从库上可能拿到过期数据,尤其主从延迟明显的时候,容易引发脏读。所以 Atlas 在做路由决策时,至少要参考这三个维度:语句是否为SELECT、是否在事务中、语句是否包含锁语义或函数调用。我见过不少业务事故都是因为没有处理事务内路由,比如先开启事务插入数据,再查询刚插入的记录,结果查到了旧值,因为查询走了从库而主从还没同步。这类问题非常隐蔽,线上复现难,用户又催得急,排查起来特别费劲。路由策略还需要考虑主从延迟。正常情况下,读流量全部打到从库无可厚非,但一旦从库延迟超过阈值,比如 relay log 应用积压超过几秒,Atlas 最好把部分读请求临时切回主库。我在配置里就预留了这样的开关,不是说一定要用,而是关键时刻能救命。2.2 分库分表的规则设计与扩容思路讲完读写分离,再聊分库分表。Atlas 支持按某个字段做分片,常见的就是哈希取模和时间范围分片。哈希取模是最简单也最常用的方案。假设订单表要拆成 16 张物理表,分片键是订单号,路由算法就是hash(order_no) % 16。关键是选择一个分布足够均匀的分片键,订单号、用户 ID 都算不错,但如果你按地区分片,就会出现头部地区数据量巨大、尾部地区几乎没数据的倾斜问题。这里我要提一个规划层面的教训:分片数一开始就要预留余量,最好设置成 2 的幂次方,比如 8、16、32。为什么?因为后面扩容时,如果分片数不能按倍数扩,数据迁移的范围会非常大。比如从 16 扩到 24,几乎所有数据都要重新分布;但从 16 扩到 32,则只有一半的数据需要迁移,迁移逻辑也更简单。我做 Atlas 项目规划时直接定成了 32 个分片,初期只用 8 个分片,剩下的暂时空着,后续加机器直接把新分片启用就行。分库分表的代价也要提前想清楚。跨分片 JOIN 基本别想了,IN 查询需要拆成多个查询再在内存里合并,分布式事务需要业务方配合做最终一致性方案。我一般建议团队把分片边界设计在业务层面,比如同一个用户的所有数据始终落在一个分片内,这样大部分查询都能限定在单分片内执行,复杂度会小很多。2.3 连接池复用与高可用保障再说连接管理。数据库连接很贵,从创建连接到认证,再到 MySQL 内部分配线程、初始化会话,开销都不小。应用侧如果每个请求都新建连接,数据库很快就会被拖垮。Atlas 在中间层做了一件很巧妙的事:它对外暴露的连接和后端实际建立的连接并不是一比一对应的。应用连上 Atlas 之后,Atlas 从后端的连接池里取一条空闲连接出来复用,请求处理完再把连接还回去。这样一来,后端只需要维持比应用连接数少一个数量级的连接,就能支撑大量并发请求。我实测过一个场景,一个有 50 个节点的应用集群,原来每个节点 30 个连接,直连时 MySQL 要维持 1500 个连接;接入 Atlas 后,后端只保留了 200 个连接,负载直接降下来一大截。高可用方面,Atlas 会周期性对后端每个实例做健康检查。检查方式可以是很简单的SELECT 1,也可以是自定义的探活语句。如果某个实例连续几次失败,它会被标记为不可用,读流量自动转移到其他可用节点。等到实例恢复健康,会重新放回池子里。这套机制在从库宕机、主从切换两种场景下都非常有用。主从切换的场景尤其要小心。Atlas 必须具备配置热更新的能力,不然你手动把主库切换成了新机器,中间件还傻傻地往旧地址发请求,服务就全挂了。我的做法是在 Atlas 管理接口上预留动态调整后端的命令,切换完成后立刻更新后端节点列表,不需要重启进程。3. 部署与实操全过程3.1 环境规划与安装前的准备先看机器规划,这是我实际部署时用的一个标准方案,大家可以参考:角色机器配置数量说明Atlas 节点4C8G,SSD2一主一备,Keepalived 做 VIP 漂移MySQL 主库8C16G,SSD1承载写入流量MySQL 从库8C16G,SSD2承载读流量,延迟监控监控/日志机2C4G1收集 Atlas 日志和 MySQL 慢查询安装前有几项系统设置必须处理,否则 Atlas 跑起来就会出现各种莫名其妙的连接问题。打开文件描述符上限:修改/etc/security/limits.conf,把nofile调高到 65535 以上,不然高并发下会报 “Too many open files”。关闭透明大页:数据库链路对延迟敏感,透明大页会导致内存分配不稳定,建议在/etc/rc.local里执行echo never > /sys/kernel/mm/transparent_hugepage/enabled。同步时间:主从和中间件节点都要配置 NTP,时间不一致会导致日志分析困难,甚至认证异常。Atlas 的运行依赖 MySQL 客户端库和 glibc,安装前确认系统里有libmysqlclient.so,没有的话用系统的包管理器装上即可。我用的是 CentOS 7.9,直接安装编译好的二进制包,解压到/usr/local/atlas,没有遇到依赖地狱的问题。3.2 配置文件逐项解读Atlas 的配置是我觉得整个项目里最值得花时间研究的部分。配置文件结构不复杂,但每个参数都暗藏玄机。我贴一份我实际使用的精简配置,并逐个讲解。[mysql] # Atlas 对外监听地址和端口 proxy-address = 0.0.0.0:3306 # 后端 MySQL 实例列表,主库在前,从库在后 backend-address = 10.0.0.11:3306 backend-address = 10.0.0.12:3306 backend-address = 10.0.0.13:3306 # 主库标识,用于区分读写角色 master-address = 10.0.0.11:3306 # 客户端连接 Atlas 所需的账号 pwds = admin:xxxxx # 管理接口地址和账号 admin-address = 0.0.0.0:2345 admin-username = admin admin-password = xxxxx # 分库分表规则文件 tables = /usr/local/atlas/conf/table.rule # 日志配置 log-path = /usr/local/atlas/logs log-level = INFO # 主从延迟阈值,超过则延迟读切主 max-delay = 5先看backend-address,注意主库要写在前面还是后面其实不重要,重要的是列表里必须包含主库。master-address是单独指定的,Atlas 用这个字段来区分哪个实例承担写流量。从库在列表里通常排在主库后面。pwds这个字段很多人会踩坑,它存的是加密后的密码,不是明文。我第一次配置时直接写了明文,结果客户端连接一直认证失败。后来查了文档才知道要先通过工具生成加密串再写进去。admin-address是中间件的管理口,和业务端口分开,防止外部误连。日常运维、查看状态、动态调整后端地址都靠这个端口。max-delay是主从延迟阈值,单位是秒。前面提到过,延迟超过阈值就要把读流量切回主库,这个参数就是那套机制的触发条件。设得太小会导致读流量频繁抖动,设得太大又会出现数据新鲜度问题,我一般从 5 秒开始调,根据业务容忍度再动态改。3.3 启动验证与压测观察配置完成之后,启动过程非常简单,执行启动脚本就行。但启动成功不代表路由已经符合预期,必须做一套完整的验证。第一步,用 MySQL 客户端连接 Atlas:mysql -h 127.0.0.1 -P 3306 -u admin -p能正常连上,说明基本链路通畅。第二步,执行SHOW DATABASES;,能列出后端 MySQL 的真实库,说明 Atlas 成功代理到后端实例。第三步,验证读写分离是否真的生效。我在从库上执行下面的 SQL 来查看当前连接来源:SHOW PROCESSLIST;然后在应用侧不断发起SELECT请求,观察从库的Info字段里是否持续出现来自 Atlas 转发的查询。如果从库一点动静都没有,大概率是路由配置有问题,所有流量都打到了主库。紧接着做一轮简单压测。我用 sysbench 造了大概 100 万条数据,分别测试直连 MySQL 和通过 Atlas 访问的吞吐差异。结果如下:压测场景连接数QPS 峰值平均延迟(ms)直连主库2001200012走 Atlas 读从库2001050015走 Atlas 读写混合200830018看这个结果,走 Atlas 后单次请求延迟确实有微幅上升,大概 2 到 3 毫秒,这是代理层的正常开销。但换来的是整体连接数大幅下降,后端单实例负载压力小了很多,系统的扩展性和稳定性才是更大的收益。3.4 业务接入与快速回滚策略验证通过后就是接业务。Atlas 最让我喜欢的一点是,业务接入成本几乎为零,只需要把应用里的数据库连接地址从原来的 MySQL 实例 IP 改成 Atlas 的地址即可。端口如果没有特殊要求,可以继续用 3306 端口,这样连驱动里的 URL 都不用改。不过接入顺序还是有讲究的。我建议先挑一个读多写少、不影响核心交易链路的服务做灰度,跑一两天,观察日志里有没有路由异常、连接中断、主从延迟导致的脏读报警。确认稳定之后再逐步扩大到所有服务。回滚方案我一般提前准备好。所有服务在变更配置时保留旧连接串,一旦发现 Atlas 这边有问题,直接用发布系统回滚配置,应用连接重新指向原 MySQL 实例。整个回滚过程不会超过五分钟,中间件的无状态特性在这里发挥了巨大作用,因为 Atlas 本身不落业务数据,回滚不会造成数据不一致。我还特意写了一个一键回滚脚本,核心就是把应用配置里的atlas.host改回mysql.host,然后重启服务。这种看似很笨的方案,在线上紧急故障时往往最管用。4. 常见问题与排查技巧实录4.1 高频故障速查表我把这段时间遇到的问题整理成了一张速查表,按“现象 -> 可能原因 -> 解决思路”的方式排列,方便大家直接对照。现象可能原因解决思路客户端连接 Atlas 报认证失败pwds里写的是明文而非加密串用工具生成加密密码后重写配置应用启动后查询偶发超时后端连接池耗尽调大 Atlas 后端连接上限,同时检查 MySQL max_connections从库 CPU 飙高但主库很低读流量全部打到主库检查master-address配置以及路由规则主从切换后新主库不接收写请求Atlas 后端列表未更新通过管理接口动态更新,不需要重启SELECT 查询看到旧数据主从延迟超过容忍阈值调低max-delay或部分读请求强制走主日志里大量连接被断开后端 MySQLwait_timeout过短调整 MySQL 会话超时参数Atlas 进程内存持续增长连接泄漏或慢查询堆积排查慢 SQL,定期释放空闲连接每一个问题我都在自己环境里踩过一两遍,尤其是主从切换后的写请求丢失,这个坑影响最大。当时我以为 Atlas 会把新主库自动识别出来,结果没有,因为它不知道你外部已经把主库切换了。后来我在切换脚本里加入了通知 Atlas 更新后端的步骤,才彻底解决。4.2 排查命令与日志分析实战排查问题不能瞎猜,我分享几个最常用的命令和方法,遇到异常时可以快速定位。第一招,连接 Atlas 管理端口看全局状态。管理端口不是给业务用的,是给运维和管理员用的。登录后直接输入show status,可以查看当前的前端连接数、后端连接池使用情况、路由次数统计等。如果没有这些指标,问题的定位会非常困难。第二招,看日志里的关键字。Atlas 的日志路径在配置文件里可以指定,正常运行时输出到/usr/local/atlas/logs。排查时重点看三类关键词:ERROR表示有请求执行失败;TIMEOUT表示连接或查询超时;BACKEND表示后端实例的状态变化。比如下面这行日志:[ERROR] cannot get connection from backend pool, timeout这行日志一出现,基本可以断定是后端连接池满了或者 MySQL 真的无法建立新连接。优先检查 MySQL 的max_connections和threads_connected,再看 Atlas 到 MySQL 之间的网络是否正常。第三招,用tcpdump抓包看 SQL 流向。在执行查询的同时抓包,能非常直观地看到 SQL 到底发给了哪个后端 IP。这招在处理路由怀疑的争论时最有效,不靠猜,直接看包。tcpdump -i eth0 -nn port 3306 -w atlas.pcap抓完之后用 Wireshark 分析,可以看到每个连接的握手、查询语句、响应耗时。如果某个 SQL 应该走从库但抓包发现反向了,那路由配置肯定有问题。4.3 独家避坑心得最后分享几个我在项目中总结的独家经验,别的地方不一定能看到。第一,分片键的选择要有防御性。不要在项目初期图方便选一个业务主键做分片,业务主键往往带有自增特性和局部性,很容易出现热点。我见过一个支付业务按照门店 ID 分片,结果头部门店数据量是尾部门店的几百倍,查询严重倾斜。分片键要选访问频率均匀、区分度高的字段。第二,中间件节点不要跨机房部署。刚开始我图省事,把两台 Atlas 分别放在两个可用区,想着提升容灾能力。结果发现跨机房的网络延迟让每次 SQL 转发都增加了十几毫秒,应用层受不了,最后还是改回了同机房双节点。想跨机房容灾,应该从数据库层面做双向同步,而不是让中间件跨机房转发。第三,升级和变更尽量在低峰期进行。Atlas 的配置热更新虽然是动态的,但涉及表规则变更这类重大调整时,最好还是先灰度再全量。我在一次规则更新时,因为新旧规则并行产生了歧义,导致部分请求被路由到错误的表,虽然很快恢复了,但那次教训让我学会了:中间件的变更要走和业务发布一样的流程,不能因为它是一层代理就掉以轻心。5. 写在最后的一点体会做完 Atlas 这套中间层之后,我对“数据库治理”这件事的理解深了不少。以前总觉得数据库不够用就加缓存、加队列、加机器,实际上如果没有一个统一的流量入口,加再多资源也只是把问题往后拖延。我个人的建议是:如果你的系统已经出现连接数打满、读写混合导致锁竞争激烈、或者表数据量增长速度明显超过预期,Atlas 这类中间件是一个非常值得考虑的分层方案。它不能解决所有问题,但它能让数据库拓扑的变化不再牵动整个应用侧,这一点在业务扩张期尤其重要。最后再送一个小技巧:投入运行之后,定期写一个检查脚本去从库看真实流量分布。别只依赖 Atlas 自己的统计,真实 SQL 的执行情况会告诉你很多中间件日志里看不到的细节,比如哪些表才是真正的热点、哪些查询该被缓存兜住。掌握了这些一手数据,你才能把 Atlas 的路由规则调得越来越聪明。
最近在整理一套老系统的数据库访问链路,项目代号就叫 Atlas。这套东西本质是一个放在应用和 MySQL 之间的访问中间层,用来统一解决读写分离、分库分表、连接管理这些烦心事。你可能也遇到过类似情况:业务量上来之后,单库单表扛不住,应用层到处写死数据库地址,一到大促就要临时拆库,代码改到想骂人。Atlas 要做的就是让应用侧只认一个入口,真正落在哪台库、哪张表,由中间层去决策。这篇东西我打算从项目整体设计讲到核心原理,再到部署实操和排障经验,全程用我实际踩过的坑来铺垫。适合正在做数据库拆分、读写分离,或者被连接数打满折磨过的后端开发、DBA 和架构师参考。即便你是第一次接触中间件,只要跟着走一遍,也能理解它到底怎么工作、能解决什么问题。1. 项目定位与方案拆解1.1 Atlas 在整个数据链路中扮演什么角色先打个比方。以前应用直连数据库,就像每个员工都直接跑到仓库去拿货,仓库窗口就那几个,人一多就排队,偶尔有人拿错货还没人管。Atlas 就好比在仓库门口加了一个分拨台,所有取货请求先到这里登记,分拨台根据货单内容决定你去哪个窗口,顺带还能帮你把一些零散的小请求合并成大订单。放到技术链路上看,Atlas 的位置非常清晰:应用层通过标准的 MySQL 协议连上 Atlas 的端口,Atlas 解析 SQL 之后,再往后端真实的 MySQL 实例转发。应用侧根本感知不到后端有几台机器、哪些是主库、哪些是从库。这个方式的优势在于,数据库拓扑变更不用再通知所有业务方,只需要改中间层配置。我在做这套项目时,最直观的感受是:连接数和流量可控了。以前几百个服务实例每个都保持几十个长连接,数据库光握手就要耗尽 CPU。Atlas 收口之后,后端连接数变得非常平稳,应用扩容也不用担心把数据库连接池打爆。说白了,它让数据库从“被很多人直接操作”变成“被一个有脑子的人统一调度”。1.2 核心功能清单与方案选型对比Atlas 的核心功能我整理成了一张表和直连方案对比,大家看得更清楚:功能维度直连数据库Atlas 中间件后端地址管理每个应用配置自己的连接串统一入口,后端变更对应用透明读写分离应用自己实现,代码侵入大中间层按 SQL 类型自动路由分库分表应用层判断并拼接表名按分片键路由,应用无需关心连接池复用每个服务维护一堆连接中间层统一复用,降低连接数故障摘除依赖应用侧重试机制健康检查后自动剔除故障节点监控审计分散难汇总统一日志与统计,便于排查为什么不直接选应用层改造或者引入其他重量级方案,而是用 Atlas 这类中间件?我当时的考虑有几点。第一,应用层改造成本太高,几十个服务都要动一遍,光排期就要跨两个迭代。第二,很多轻量级方案对分库分表的支持很弱,尤其是一致性事务场景,自己写容易挖坑。第三,Atlas 这类基于 MySQL 协议的代理方案对业务代码基本透明,切换成本低,上线失败也容易回滚。当然它也有短板,比如跨库 JOIN 支持不好,分布式事务需要业务配合。但这不是 Atlas 的问题,而是所有分库分表都要付出的代价。你选的不是完美方案,而是在当前阶段收益最大、代价最小的方案。1.3 整体架构拓扑与数据流向Atlas 的部署形态不复杂,核心就三层。最上层是接入层,也就是应用服务器,它们只需要配置一个虚拟 IP 或者域名,指向 Atlas 所在的主机,端口可以是常见的 3306,也可以换成别的。中间是 Atlas 节点,建议至少部署两个,一台为主、一台为备,配合 Keepalived 之类的工具做高可用切换。下面是真实的 MySQL 实例集群,分为主库和若干从库,主库负责写入,从库负责读取。数据流向大概是这样的:应用发出 SQL 请求,Atlas 先解析这条 SQL 的类型和涉及的表,然后根据配置的路由规则,以及当前各节点的健康状态,决定把请求转发给哪个后端实例。如果是SELECT,默认会挑一个负载较低的从库;如果是INSERT、UPDATE、DELETE,或者会话开启了事务,就走主库。如果配置了分库分表,还需要根据分片键算出具体落到哪个库、哪张表。这里有个容易被忽略的点:Atlas 自身是无状态的,也就是说它不存业务数据,只是做转发和路由。这样设计的好处非常明显,一旦某台 Atlas 宕机,另一台可以直接顶上,数据不会丢。坏处是配置要同步好,不然两个节点的规则不一致,会引发路由错乱。我后续会专门讲怎么用脚本同步配置和管理节点。2. 核心机制与关键原理2.1 读写分离的实现细节与路由策略读写分离是 Atlas 最常用的能力,但很多人只知道“SELECT 走从库,其他走主库”,实际做起来没有那么简单。路由的判断不仅是看 SQL 的第一个关键词。比如SELECT ... FOR UPDATE,虽然以 SELECT 开头,但它带了行锁,必须走主库,否则你在从库加锁,主库的数据还在被别人改,业务就出大问题。再比如事务内的所有语句,只要你在一个显式事务里,哪怕全是 SELECT,也应该全走主库。原因是事务的隔离级别和快照读在从库上可能拿到过期数据,尤其主从延迟明显的时候,容易引发脏读。所以 Atlas 在做路由决策时,至少要参考这三个维度:语句是否为SELECT、是否在事务中、语句是否包含锁语义或函数调用。我见过不少业务事故都是因为没有处理事务内路由,比如先开启事务插入数据,再查询刚插入的记录,结果查到了旧值,因为查询走了从库而主从还没同步。这类问题非常隐蔽,线上复现难,用户又催得急,排查起来特别费劲。路由策略还需要考虑主从延迟。正常情况下,读流量全部打到从库无可厚非,但一旦从库延迟超过阈值,比如 relay log 应用积压超过几秒,Atlas 最好把部分读请求临时切回主库。我在配置里就预留了这样的开关,不是说一定要用,而是关键时刻能救命。2.2 分库分表的规则设计与扩容思路讲完读写分离,再聊分库分表。Atlas 支持按某个字段做分片,常见的就是哈希取模和时间范围分片。哈希取模是最简单也最常用的方案。假设订单表要拆成 16 张物理表,分片键是订单号,路由算法就是hash(order_no) % 16。关键是选择一个分布足够均匀的分片键,订单号、用户 ID 都算不错,但如果你按地区分片,就会出现头部地区数据量巨大、尾部地区几乎没数据的倾斜问题。这里我要提一个规划层面的教训:分片数一开始就要预留余量,最好设置成 2 的幂次方,比如 8、16、32。为什么?因为后面扩容时,如果分片数不能按倍数扩,数据迁移的范围会非常大。比如从 16 扩到 24,几乎所有数据都要重新分布;但从 16 扩到 32,则只有一半的数据需要迁移,迁移逻辑也更简单。我做 Atlas 项目规划时直接定成了 32 个分片,初期只用 8 个分片,剩下的暂时空着,后续加机器直接把新分片启用就行。分库分表的代价也要提前想清楚。跨分片 JOIN 基本别想了,IN 查询需要拆成多个查询再在内存里合并,分布式事务需要业务方配合做最终一致性方案。我一般建议团队把分片边界设计在业务层面,比如同一个用户的所有数据始终落在一个分片内,这样大部分查询都能限定在单分片内执行,复杂度会小很多。2.3 连接池复用与高可用保障再说连接管理。数据库连接很贵,从创建连接到认证,再到 MySQL 内部分配线程、初始化会话,开销都不小。应用侧如果每个请求都新建连接,数据库很快就会被拖垮。Atlas 在中间层做了一件很巧妙的事:它对外暴露的连接和后端实际建立的连接并不是一比一对应的。应用连上 Atlas 之后,Atlas 从后端的连接池里取一条空闲连接出来复用,请求处理完再把连接还回去。这样一来,后端只需要维持比应用连接数少一个数量级的连接,就能支撑大量并发请求。我实测过一个场景,一个有 50 个节点的应用集群,原来每个节点 30 个连接,直连时 MySQL 要维持 1500 个连接;接入 Atlas 后,后端只保留了 200 个连接,负载直接降下来一大截。高可用方面,Atlas 会周期性对后端每个实例做健康检查。检查方式可以是很简单的SELECT 1,也可以是自定义的探活语句。如果某个实例连续几次失败,它会被标记为不可用,读流量自动转移到其他可用节点。等到实例恢复健康,会重新放回池子里。这套机制在从库宕机、主从切换两种场景下都非常有用。主从切换的场景尤其要小心。Atlas 必须具备配置热更新的能力,不然你手动把主库切换成了新机器,中间件还傻傻地往旧地址发请求,服务就全挂了。我的做法是在 Atlas 管理接口上预留动态调整后端的命令,切换完成后立刻更新后端节点列表,不需要重启进程。3. 部署与实操全过程3.1 环境规划与安装前的准备先看机器规划,这是我实际部署时用的一个标准方案,大家可以参考:角色机器配置数量说明Atlas 节点4C8G,SSD2一主一备,Keepalived 做 VIP 漂移MySQL 主库8C16G,SSD1承载写入流量MySQL 从库8C16G,SSD2承载读流量,延迟监控监控/日志机2C4G1收集 Atlas 日志和 MySQL 慢查询安装前有几项系统设置必须处理,否则 Atlas 跑起来就会出现各种莫名其妙的连接问题。打开文件描述符上限:修改/etc/security/limits.conf,把nofile调高到 65535 以上,不然高并发下会报 “Too many open files”。关闭透明大页:数据库链路对延迟敏感,透明大页会导致内存分配不稳定,建议在/etc/rc.local里执行echo never > /sys/kernel/mm/transparent_hugepage/enabled。同步时间:主从和中间件节点都要配置 NTP,时间不一致会导致日志分析困难,甚至认证异常。Atlas 的运行依赖 MySQL 客户端库和 glibc,安装前确认系统里有libmysqlclient.so,没有的话用系统的包管理器装上即可。我用的是 CentOS 7.9,直接安装编译好的二进制包,解压到/usr/local/atlas,没有遇到依赖地狱的问题。3.2 配置文件逐项解读Atlas 的配置是我觉得整个项目里最值得花时间研究的部分。配置文件结构不复杂,但每个参数都暗藏玄机。我贴一份我实际使用的精简配置,并逐个讲解。[mysql] # Atlas 对外监听地址和端口 proxy-address = 0.0.0.0:3306 # 后端 MySQL 实例列表,主库在前,从库在后 backend-address = 10.0.0.11:3306 backend-address = 10.0.0.12:3306 backend-address = 10.0.0.13:3306 # 主库标识,用于区分读写角色 master-address = 10.0.0.11:3306 # 客户端连接 Atlas 所需的账号 pwds = admin:xxxxx # 管理接口地址和账号 admin-address = 0.0.0.0:2345 admin-username = admin admin-password = xxxxx # 分库分表规则文件 tables = /usr/local/atlas/conf/table.rule # 日志配置 log-path = /usr/local/atlas/logs log-level = INFO # 主从延迟阈值,超过则延迟读切主 max-delay = 5先看backend-address,注意主库要写在前面还是后面其实不重要,重要的是列表里必须包含主库。master-address是单独指定的,Atlas 用这个字段来区分哪个实例承担写流量。从库在列表里通常排在主库后面。pwds这个字段很多人会踩坑,它存的是加密后的密码,不是明文。我第一次配置时直接写了明文,结果客户端连接一直认证失败。后来查了文档才知道要先通过工具生成加密串再写进去。admin-address是中间件的管理口,和业务端口分开,防止外部误连。日常运维、查看状态、动态调整后端地址都靠这个端口。max-delay是主从延迟阈值,单位是秒。前面提到过,延迟超过阈值就要把读流量切回主库,这个参数就是那套机制的触发条件。设得太小会导致读流量频繁抖动,设得太大又会出现数据新鲜度问题,我一般从 5 秒开始调,根据业务容忍度再动态改。3.3 启动验证与压测观察配置完成之后,启动过程非常简单,执行启动脚本就行。但启动成功不代表路由已经符合预期,必须做一套完整的验证。第一步,用 MySQL 客户端连接 Atlas:mysql -h 127.0.0.1 -P 3306 -u admin -p能正常连上,说明基本链路通畅。第二步,执行SHOW DATABASES;,能列出后端 MySQL 的真实库,说明 Atlas 成功代理到后端实例。第三步,验证读写分离是否真的生效。我在从库上执行下面的 SQL 来查看当前连接来源:SHOW PROCESSLIST;然后在应用侧不断发起SELECT请求,观察从库的Info字段里是否持续出现来自 Atlas 转发的查询。如果从库一点动静都没有,大概率是路由配置有问题,所有流量都打到了主库。紧接着做一轮简单压测。我用 sysbench 造了大概 100 万条数据,分别测试直连 MySQL 和通过 Atlas 访问的吞吐差异。结果如下:压测场景连接数QPS 峰值平均延迟(ms)直连主库2001200012走 Atlas 读从库2001050015走 Atlas 读写混合200830018看这个结果,走 Atlas 后单次请求延迟确实有微幅上升,大概 2 到 3 毫秒,这是代理层的正常开销。但换来的是整体连接数大幅下降,后端单实例负载压力小了很多,系统的扩展性和稳定性才是更大的收益。3.4 业务接入与快速回滚策略验证通过后就是接业务。Atlas 最让我喜欢的一点是,业务接入成本几乎为零,只需要把应用里的数据库连接地址从原来的 MySQL 实例 IP 改成 Atlas 的地址即可。端口如果没有特殊要求,可以继续用 3306 端口,这样连驱动里的 URL 都不用改。不过接入顺序还是有讲究的。我建议先挑一个读多写少、不影响核心交易链路的服务做灰度,跑一两天,观察日志里有没有路由异常、连接中断、主从延迟导致的脏读报警。确认稳定之后再逐步扩大到所有服务。回滚方案我一般提前准备好。所有服务在变更配置时保留旧连接串,一旦发现 Atlas 这边有问题,直接用发布系统回滚配置,应用连接重新指向原 MySQL 实例。整个回滚过程不会超过五分钟,中间件的无状态特性在这里发挥了巨大作用,因为 Atlas 本身不落业务数据,回滚不会造成数据不一致。我还特意写了一个一键回滚脚本,核心就是把应用配置里的atlas.host改回mysql.host,然后重启服务。这种看似很笨的方案,在线上紧急故障时往往最管用。4. 常见问题与排查技巧实录4.1 高频故障速查表我把这段时间遇到的问题整理成了一张速查表,按“现象 -> 可能原因 -> 解决思路”的方式排列,方便大家直接对照。现象可能原因解决思路客户端连接 Atlas 报认证失败pwds里写的是明文而非加密串用工具生成加密密码后重写配置应用启动后查询偶发超时后端连接池耗尽调大 Atlas 后端连接上限,同时检查 MySQL max_connections从库 CPU 飙高但主库很低读流量全部打到主库检查master-address配置以及路由规则主从切换后新主库不接收写请求Atlas 后端列表未更新通过管理接口动态更新,不需要重启SELECT 查询看到旧数据主从延迟超过容忍阈值调低max-delay或部分读请求强制走主日志里大量连接被断开后端 MySQLwait_timeout过短调整 MySQL 会话超时参数Atlas 进程内存持续增长连接泄漏或慢查询堆积排查慢 SQL,定期释放空闲连接每一个问题我都在自己环境里踩过一两遍,尤其是主从切换后的写请求丢失,这个坑影响最大。当时我以为 Atlas 会把新主库自动识别出来,结果没有,因为它不知道你外部已经把主库切换了。后来我在切换脚本里加入了通知 Atlas 更新后端的步骤,才彻底解决。4.2 排查命令与日志分析实战排查问题不能瞎猜,我分享几个最常用的命令和方法,遇到异常时可以快速定位。第一招,连接 Atlas 管理端口看全局状态。管理端口不是给业务用的,是给运维和管理员用的。登录后直接输入show status,可以查看当前的前端连接数、后端连接池使用情况、路由次数统计等。如果没有这些指标,问题的定位会非常困难。第二招,看日志里的关键字。Atlas 的日志路径在配置文件里可以指定,正常运行时输出到/usr/local/atlas/logs。排查时重点看三类关键词:ERROR表示有请求执行失败;TIMEOUT表示连接或查询超时;BACKEND表示后端实例的状态变化。比如下面这行日志:[ERROR] cannot get connection from backend pool, timeout这行日志一出现,基本可以断定是后端连接池满了或者 MySQL 真的无法建立新连接。优先检查 MySQL 的max_connections和threads_connected,再看 Atlas 到 MySQL 之间的网络是否正常。第三招,用tcpdump抓包看 SQL 流向。在执行查询的同时抓包,能非常直观地看到 SQL 到底发给了哪个后端 IP。这招在处理路由怀疑的争论时最有效,不靠猜,直接看包。tcpdump -i eth0 -nn port 3306 -w atlas.pcap抓完之后用 Wireshark 分析,可以看到每个连接的握手、查询语句、响应耗时。如果某个 SQL 应该走从库但抓包发现反向了,那路由配置肯定有问题。4.3 独家避坑心得最后分享几个我在项目中总结的独家经验,别的地方不一定能看到。第一,分片键的选择要有防御性。不要在项目初期图方便选一个业务主键做分片,业务主键往往带有自增特性和局部性,很容易出现热点。我见过一个支付业务按照门店 ID 分片,结果头部门店数据量是尾部门店的几百倍,查询严重倾斜。分片键要选访问频率均匀、区分度高的字段。第二,中间件节点不要跨机房部署。刚开始我图省事,把两台 Atlas 分别放在两个可用区,想着提升容灾能力。结果发现跨机房的网络延迟让每次 SQL 转发都增加了十几毫秒,应用层受不了,最后还是改回了同机房双节点。想跨机房容灾,应该从数据库层面做双向同步,而不是让中间件跨机房转发。第三,升级和变更尽量在低峰期进行。Atlas 的配置热更新虽然是动态的,但涉及表规则变更这类重大调整时,最好还是先灰度再全量。我在一次规则更新时,因为新旧规则并行产生了歧义,导致部分请求被路由到错误的表,虽然很快恢复了,但那次教训让我学会了:中间件的变更要走和业务发布一样的流程,不能因为它是一层代理就掉以轻心。5. 写在最后的一点体会做完 Atlas 这套中间层之后,我对“数据库治理”这件事的理解深了不少。以前总觉得数据库不够用就加缓存、加队列、加机器,实际上如果没有一个统一的流量入口,加再多资源也只是把问题往后拖延。我个人的建议是:如果你的系统已经出现连接数打满、读写混合导致锁竞争激烈、或者表数据量增长速度明显超过预期,Atlas 这类中间件是一个非常值得考虑的分层方案。它不能解决所有问题,但它能让数据库拓扑的变化不再牵动整个应用侧,这一点在业务扩张期尤其重要。最后再送一个小技巧:投入运行之后,定期写一个检查脚本去从库看真实流量分布。别只依赖 Atlas 自己的统计,真实 SQL 的执行情况会告诉你很多中间件日志里看不到的细节,比如哪些表才是真正的热点、哪些查询该被缓存兜住。掌握了这些一手数据,你才能把 Atlas 的路由规则调得越来越聪明。