Atlas 中间件自动故障转移
解读
在国内互联网公司的 PHP 面试里,数据库高可用是“必答题”。Atlas 是 360 开源的 MySQL 代理中间件,很多中小型公司用它做读写分离 + 故障转移,面试官问“自动故障转移”并不是让你背文档,而是想确认三件事:
- 你是否真的在生产环境踩过主库宕机的坑;
- 是否理解 Atlas 的“无脑切换”与业务幂等、事务补偿之间的冲突;
- 能否用 PHP 代码把“感知切换 → 重试 → 降级”这三步写利索。 答得太浅(“改配置就行”)会被认为没实战;答得太深(“改 Atlas 源码”)又容易超时。把“机制 + 配置 + PHP 兜底”三段讲清,最能体现后端工程师的“靠谱度”。
知识点
- Atlas 故障转移触发条件
- 后端 MySQL 连续心跳失败次数 ≥ backend-max-failure(默认 3)
- 心跳间隔 backend-idle-timeout(默认 30 s)
- 满足条件后 Atlas 把 master 标记为 OFFLINE,把第一台 slave 提升为 new master,并改写内部 ip_pool 映射;对客户端连接池完全透明。
- 切换粒度与一致性
- Atlas 只做“实例级”切换,不做数据补偿,可能出现 1~2 秒写丢失(RPO>0)。
- 原主库如果“假死”又恢复,会形成双主,必须借助 kill 旧连接 + 设置 read_only=1 手工隔离。
- PHP 侧必须做的三件事
- 连接层:PDO::ATTR_ERRMODE 设 EXCEPTION,捕获 2003/2013/2006 三类错误码。
- 重试层:指数退避 3 次,间隔 100 ms、200 ms、400 ms,防止 Atlas 正在重启旧连接。
- 业务层:事务型写操作加唯一索引 + 幂等令牌(如订单号),切换后重试不重复落库。
- 国内云环境差异
- 阿里云 RDS 自带高可用,Atlas 只放在本地 IDC 做“多云混合”场景;腾讯云 CDB 的 GTID 与 Atlas 心跳端口冲突,需要把 heartbeat-user 设成 ‘repl’ 账号并开 3307 端口。
- 监控与告警
- 在 Atlas 的 stats 接口(默认 1234)拉取 backend_status=OFFLINE 次数,通过 Falcon 或夜莺推送钉钉告警。
- PHP 端把重试次数写入 Redis 队列,方便 SRE 做故障复盘。
答案
“Atlas 的自动故障转移分三层:中间件层、驱动层、业务层,我分别做过配置和代码兜底。
-
中间件层 在 instance.cnf 把 backend-max-failure 调成 2 次、backend-idle-timeout 调成 15 秒,缩短探测周期;同时把 slave 节点按优先级 1、2、3 排列,确保第一从库延迟最低。为了防止‘脑裂’,我把旧主库通过 cron 每 10 秒执行
mysql -e "set global read_only=1",一旦 Atlas 把它踢掉就自动只读。 -
驱动层 PHP 用 PDO 长连接,捕获异常码:
try { $pdo->exec($sql); } catch (PDOException $e) { $code = $e->getCode(); if (in_array($code, ['2003', '2013', '2006'])) { usleep(100000); // 第一次 100 ms $pdo->exec($sql); // 重试 } else { throw $e; } }生产环境我封装成
RetryablePDO,对业务透明。 -
业务层 订单写库带唯一索引 order_sn,重试时如果主键冲突直接返回上次写入的 id,保证幂等;同时把重试事件打到 Kafka,供 BI 校正金额。
上线半年,Atlas 做过 3 次主库宕机演练,RPO 最大 1.8 秒,PHP 端无 500 错误,符合公司 SLA。”
拓展思考
如果公司从 Atlas 迁到 ShardingSphere-Proxy,故障转移模型从“代理层选主”变成“ZK 协调选主”,PHP 侧代码几乎不用改,但要把重试异常码扩展到 8001(ShardingSphere 的“Circuit Break”)。此外,Atlas 不支持 GTID,无法做“防回环”校验,未来想做到 RPO=0,需要下沉到 MySQL MGR + 半同步复制,PHP 端再引入 Saga 事务模式补偿,高可用才算“毕业”。