在电商订单系统重构中,我们用MySQL承载核心交易数据,面临着大促期间主节点意外重启、网络分区造成写入中断等风险。团队没有停留在基础主从复制,而是从开发视角重新设计整套高可用与故障恢复机制,保证即使底层硬件出问题,上层业务也能持续对外服务。

半同步复制在开发中的取舍与落地
默认异步复制下,主库提交事务后立刻返回客户端,不等待从库接收binlog,这在网络闪断时会造成主从数据不一致,故障切换后老主库未同步的数据彻底丢失。我们在项目中启用了半同步复制,要求主库在提交前至少收到一个从库确认收到relay日志。这样即使主库崩溃,已确认的事务必然存在于某个从库,故障恢复时不会丢已提交订单。
但半同步不是银弹。开启后写入延迟明显上升,尤其跨机房部署时RTT可能到十毫秒以上。我们通过rpl_semi_sync_master_timeout设为五百毫秒,超时则自动降级为异步,避免从库卡死拖垮主库。同时业务层对支付类请求开启半同步,对点赞类非核心写关闭,用代码中的注解路由不同数据源。下面这段Java配置展示了如何按业务标记选择是否强制半同步:
// 通过Spring AbstractRoutingDataSource动态选库
public class BizRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 从上下文取标记,order表示核心交易走半同步组
return BizContext.get().isStrongConsist() ? "semiSync" : "async";
}
}
// 事务开启前设置
BizContext.get().setStrongConsist(true);
我们还监控了Rpl_semi_sync_master_status变量,一旦频繁off就告警,开发介入查网络。这种在代码层做读写分离与一致性分级,比单纯堆硬件更有效。项目上线后,核心交易零丢失,非核心接口毛刺可控。
故障检测与自动转移的工具对比实践
高可用关键在“自动”二字。人工切换耗时久,我们对比了MHA与Orchestrator。MHA依赖SSH拉取各节点binlog补全差异,配置简单但脑裂防护弱,且只支持一主多从。Orchestrator基于拓扑发现,用Raft保证自身高可用,能自动拒绝非法切换。我们在多机房场景选了Orchestrator,通过HTTP钩子通知应用配置中心更新主库地址。
开发侧要配合做健康检查。我们写了轻量探活接口,不仅查MySQL连通,还执行SELECT 1与复制延迟阈值判断。Orchestrator触发切换后,配置中心推送新主库DNS,应用用带超时连接池重连。以下伪代码说明应用如何监听变更并重建连接:
def on_master_change(new_host):
pool.dispose_all() # 丢弃旧连接
pool.config(host=new_host, timeout=2)
retry_connect(max=3)
# 配置中心回调
config_watch.register('/mysql/master', on_master_change)
我们也踩过坑:某次从库延迟两分钟,Orchestrator误判主库死,切到旧从库导致部分脏读。后来加规则,切换前必须对比Exec_Master_Log_Pos与候选从库落后秒数小于十。工具再智能,也需要开发在恢复流程里写防御逻辑,不能全信自动化。
应用层重试、降级与binlog保留策略
数据库高可用不等于应用无感知。我们在DAO层封装了重试模板,捕获死连接异常后按指数退避重试,并区分幂等写与非幂等写。对创建订单这类用唯一订单号做去重,对余额扣减用乐观锁版本号防超扣。降级方面,主库不可用时把非重要查询切本地缓存,写入进RabbitMQ异步落库,恢复后补偿。
故障恢复另一关键是binlog保留时长。我们设expire_logs_days为七天,并每日冷备到对象存储。某次误删表,用上周全备加binlog回放到删前位点,半小时找回。以下命令展示如何基于位点恢复:
# 从全备恢复基础数据 mysql -uroot -p < backup_20240101.sql # 应用binlog到指定停止点 mysqlbinlog --stop-position=1542 mysql-bin.000123 | mysql -uroot -p
我们还把慢查询与复制延迟指标接Prometheus,开发可看面板定位恢复瓶颈。整体看,高可用是存储、工具、代码三方合力,单靠DBA配主从远远不够。把故障当成常态去写代码,系统才真正可靠。