凌晨两点半,手机在床头柜上疯狂震动。值班同事的声音有点发虚:“主库挂了,从库没顶上,现在只读页面全在报错。”我一边套外套一边问:“半同步复制配了没?”“配了。”“自动切换脚本呢?”“切了,但是新主库的数据少了一截。”那一瞬间我就知道,这个晚上不是修一个故障,而是给过去几年欠下的架构债还一次利息。
后来我把这套踩坑经历、方案选型逻辑和现网验证手段整理过很多次,发现真正决定高可用架构成败的,从来不是某个开源组件选得有多时髦,而是你有没有把“可用性目标、单点消除、切换一致性、故障恢复”这条链路想透。这篇文章就围绕高可用架构设计这个核心,把我这些年做 MySQL 高可用、Kubernetes 集群、SQL Server 同步、以及最近在问数智能体这类 AI 应用里做稳定性设计的经验,一次讲清楚。适合正在做业务稳定性保障、架构设计、或者刚接手高可用改造的同学参考,内容尽量说人话,重点放在“为什么这么做”和“实操时怎么避坑”。
1. 高可用架构设计先想清楚:可用性目标怎么定,架构师的第一份作业
很多人拿到“做高可用”这个需求,第一反应是翻开源项目文档,看看 MHA 怎么配、etcd 要几台机器。我的建议是,先停一下,把笔拿起来算一笔账。高可用架构设计的第一步不是画拓扑图,而是定目标,这个目标直接决定了你要花多少钱、买多少机器、引入多少复杂度。
1.1 不要一上来就画拓扑图,先算账:可用性指标怎么定
可用性最常见的度量是“几个九”,公式是:可用性 = 正常运行时间 / 总时间。99% 对应一年约 87.6 小时的停机窗口,99.9% 是 8.76 小时,99.99% 是 52.6 分钟,99.999% 只有 5.26 分钟。你可以简单记一个数:每增加一个九,全年可容忍的故障时间就缩小大约 10 倍。
定目标不能拍脑袋。我做过一个电商后台系统,业务方张口就要 99.99%,我问他:你一年的营收受这个系统的影响是多少?如果凌晨三点数据库不可用十分钟,对业务的实际损失是什么?系统研发团队只有 4 个人,有没有人会半夜起来处理故障?问完之后,他把目标调到了 99.9%。这不是降低标准,而是让投入产出比变得理性。高可用从来不是越贵越好,而是匹配业务容忍度和团队运维能力。
还有一个容易忽略的指标——恢复时间目标(RTO)和恢复点目标(RPO)。RTO 是故障后多久恢复业务,RPO 是允许丢多少数据。这两个指标直接决定了你的技术选型:RPO 接近零,就必须做同步复制或者强一致方案;RTO 要求分钟级,就必须有自动切换能力,而不是等人去手动执行命令。以 MySQL 高可用为例,如果 RTO 要求 30 秒内,但你用的是手工切换脚本,几乎不可能达标;如果 RPO 要求零丢失,异步复制和半同步复制就要谨慎评估,因为半同步在最坏情况下也有丢失窗口。
1.2 高可用不是所有层都做双活,优先解决“最关键路径”
很多刚做架构的同学容易陷入一个误区:每个组件都想搞双活,每个节点都想做成对等的。结果就是架构图异常漂亮,实际运维时发现组件之间的数据一致性、冲突处理、拆分逻辑复杂得让人崩溃。正确做法是找关键路径。
怎么找关键路径?画一张从用户请求到最终数据落盘的依赖图,标出每条链路上哪些是单点,哪些是无状态可水平扩展的。无状态服务(比如 Web 前端、API 网关)相对好办,加节点、加负载均衡就可以;有状态服务(数据库、缓存、消息队列)才是真正的高可用难点,因为它们的“状态”没法简单地复制一份就完事。
我看到华为企业数据架构设计方法里有一个思路很值得参考:先理清楚核心数据资产和它们之间的关系,再决定哪些数据需要做高可靠保障,哪些数据允许暂时不可用。这套方法虽然是企业数据治理视角,但它的“分层分级”思想完全可以迁移到高可用设计:不是所有数据都值得做同等级别的保护,优先保障核心业务数据链路,次要数据甚至可以接受短暂降级。比如日志数据、埋点数据丢了可以补,但订单数据、支付状态绝对不能丢,这就是分层分级的意义。
2. 基础设施层高可用:从Rocky Linux 9到Kubernetes集群
目标定完了,关键路径也画出来了,下一步就是底层基础设施的高可用。这一层是承载一切的底座,但它往往最容易被忽视。我最近在基于 Rocky Linux 9 和 Docker 环境搭 Kubernetes 高可用集群时,就发现很多看起来和“业务无直接关系”的底层细节,才是故障时能不能快速恢复的关键。
2.1 操作系统的“隐形成本”:为什么选Rocky Linux 9
很多人以为高可用架构只跟软件架构有关,其实操作系统这一层非常关键。我之所以在新的集群环境里选 Rocky Linux 9,一个重要原因是它和 RHEL 完全二进制兼容,稳定性、安全更新、生态支持都有保障。对于需要长期运行的集群节点来说,操作系统本身能不能稳定滚动更新,比某个应用层的特性重要得多。
但真正踩坑的是系统层配置。高可用集群对操作系统有几项隐藏要求,不提前处理,等故障发生就晚了。第一是时间同步,集群节点之间时钟偏移超过一定阈值,会导致心跳误判、证书校验失败、数据库复制异常。我通常用 chrony 配置内网时间源,并在部署脚本里加一个开机自检,确保所有节点时钟偏差在 50ms 以内。第二是内核参数,比如 Kubernetes 节点需要调整 fs.file-max、net.ipv4.ip_forward、net.bridge.bridge-nf-call-iptables 等参数,否则节点网络和容器通信会出现诡异问题。第三是防火墙和 SELinux,这两个经常被安装文档忽略,但恰恰是它们让 keepalived 的 VIP 漂移不生效,或者让 K8s 的 kube-proxy 规则失效。
2.2 基于Docker的Kubernetes高可用集群搭建:控制面是关键
Kubernetes 的高可用,核心在控制面。很多人以为把所有组件都多副本部署一遍就是高可用,实际上一半以上的线上故障都出在 etcd 和 API Server 的协调上。etcd 是高可用集群的“大脑”,它内部用 Raft 协议保证一致性,要求奇数个节点,比如 3 个或 5 个。为什么是奇数?因为 Raft 的容错能力是“最多容忍一半以下的节点故障”,3 个节点容忍 1 个故障,5 个节点容忍 2 个故障。偶数节点看似多一台,实际上容错能力没有提升,反而增加了同步开销。这一点在设计 K8s 高可用集群时特别重要。
如果是基于容器方式部署集群组件——也就是你说的“基于 docker 的 kubernetes 高可用集群安装”——还需要注意一个点:不要把 etcd 和 kube-apiserver 放在同一台机器上就算高可用,你必须保证它们分散在不同物理节点,并且通过 VIP 或者负载均衡器对外提供服务。常用做法是前面挂一层 keepalived + nginx(或者 haproxy),把 6443 端口代理到多个 API Server 上。keepalived 负责 VIP 的漂移,nginx 负责请求分发和健康检查。这个方案很成熟,但一定要在部署时把健康检查的探测路径写对,nginx 要探测 kube-apiserver 的 /healthz 接口,而不是简单地探测 TCP 端口。探活路径错了,就会出现“节点活着但服务不可用,VIP 却切不过去”的问题。
还有证书问题。用 kubeadm 或纯手工方式部署时,API Server 的证书 SAN 要提前把所有可能的访问地址都加进去,包括 VIP、各节点 IP、域名。证书 SAN 少一个,等 VIP 漂移之后客户端就可能因证书校验失败而连不上,这个坑非常隐蔽。以我的经验,最好在初始化集群之前,就把规划好的 VIP、每个节点的 IP、未来的负载均衡域名全部列出来,一次性写进证书配置里。后期再改证书,虽然可行,但涉及分发、滚动重启,操作风险明显增加。
3. 数据层高可用:MySQL和SQL Server的高可用实战
数据层是高可用架构里最硬核的部分。无状态服务挂了可以随时拉起新副本,但数据库如果丢了数据或者主从切换出了岔子,那就是商业事故。这一块我会重点讲一下 MySQL 高可用的几种路径和 SQL Server 高可用同步的实践经验,因为这两个是我在真实环境里用得最多、也最容易被问到的。
3.1 MySQL高可用:主从复制之外,还要会选方案
MySQL 高可用的基础是主从复制,原理并不复杂:主库把变更写入 binlog,从库通过 IO 线程拉取 binlog 写入本地 relay log,再由 SQL 线程回放。但基础复制有两个痛点,一是延迟,二是数据丢失。异步复制下,主库提交成功但 binlog 还没来得及传给从库,此时主库宕机,这部分数据就永久丢失了。
解决数据丢失的常用手段是半同步复制(semisync replication)。它要求主库在提交事务时,至少要等一个从库确认收到了 binlog,才向客户端返回成功。这样主库宕机时,从库最多只丢失极少量的、处于最后确认状态的事务。但很多人在配半同步时忽略了一个细节:半同步是“退化式”的,如果所有从库都确认超时,主库会自动退化成异步复制,避免主库不可写。这个机制是为了可用性,但也意味着在网络抖动时,你以为数据是安全的,实际上已经退化成了异步。所以我会在监控里专门加一条:实时监测半同步复制是否处于正常状态,一旦退化为异步,立刻告警。
说到方案选型,我把常见 MySQL 高可用方案拉了一个对比表,方便你根据自己的场景做判断:
| 方案 | 切换方式 | 数据一致性 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 手工主从切换 | 人工执行 | 取决于复制配置 | 低 | 可接受较长 RTO 的测试或边缘业务 |
| MHA | 自动选主、自动切换 | 基本保证,依赖半同步 | 中 | 传统主从复制架构的经典选择 |
| MGR / InnoDB Cluster | 组复制,多主或单主 | 强一致性(需配置) | 中高 | 对数据一致性要求高的新场景 |
| Orchestrator | 自动检测、自动切换 | 依赖复制配置 | 中 | 大规模 MySQL 实例的拓扑管理 |
MHA 曾经是很多公司的标配,但它有个问题:切换时需要一个管理节点来做协调,管理节点本身又是一个单点。而且 MHA 依赖 SSH 免密登录和 binary log 补偿,切换过程中如果主库已经不可达,需要从从库找差异日志来补,流程比较复杂。相比之下,MySQL 官方推出的 InnoDB Cluster(基于 MGR)更像一个完整的解决方案,它自带了 MySQL Router,对应用层屏蔽了主从角色变化,但要求你的表必须是 InnoDB,而且对网络延迟比较敏感。如果跨机房部署 MGR,建议先做小流量验证,别一上来就全量切过去。
实操上有几个经验值得写下来。一是无论选哪个方案,都要开启 GTID(全局事务标识符),它能大幅简化主从切换时的位点匹配,不然切换时去找 binlog 文件名和 position 会非常痛苦。二是路由层要实现“读写分离 + 自动感知主库”,例如用 MySQL Router 或者应用层框架里的动态数据源,主库故障切换后,只读流量自动打到新主库,避免应用重启才能恢复。三是切换后一定要检查从库的 SQL 线程是否正常,特别是当从库有过复制中断,积压了大量 relay log 时,新主库可能带着延迟对外服务,这种“切换成功但数据落后”的状态比直接故障还危险。
3.2 SQL Server高可用同步:Always On 可用性组的配置要点
SQL Server 的高可用方案里,生产环境用得最多的就是 Always On 可用性组。很多人把它和故障转移集群(Failover Cluster Instance, FCI)搞混。FCI 是实例级的,多台机器共享一份存储,某个节点挂了,另一台接管同一个数据库文件;而 Always On 可用性组是数据库级的,每台机器有自己的副本,通过日志实时同步。这个区别决定了它们适合不同的场景:FCI 不解决存储单点问题,但应用感知最简单;Always On 可以在副本上做只读路由,充分利用硬件资源。
Always On 里最核心的是同步模式选择。同步提交模式下,主副本提交事务时要等待至少一个辅助副本确认日志落盘,因此不会丢数据,但对网络延迟敏感,主库性能受制于最慢的那个副本。异步提交模式性能好,但可能丢数据,适合容灾和报表场景。生产环境我一般建议:同机房的两个副本用同步提交,用于自动故障转移;异地的灾备副本用异步提交,不参与自动故障转移。这样既保证了关键场景的 RPO 接近零,又不会因为跨地域的高延迟拖垮主库性能。
配置同步模式时还有一个容易踩的坑:自动故障转移的前提是“主副本和辅助副本都处于同步提交模式,并且健康状态同步正常”。如果你的辅助副本因为某种原因变成了“未同步”状态,那自动故障转移实际上就失效了,但你在界面上不一定能第一时间发现。所以监控里除了看可用性组是否“Healthy”,还要看每个副本的同步状态是否是“Synchronized”。另外,可用性组监听器(Listener)的配置一定不要漏,应用应该连接监听器的虚拟网络名称,而不是直接连某个实例的 IP。否则主副本切换后,你的应用就要改配置重启。
还有一个被很多人忽略的点:见证服务器(witness)和仲裁。数据库级别的自动故障转移虽然不像 FCI 那样以 Windows 集群仲裁为基础,但如果你在 Windows Server 故障转移集群上搭建 Always On,那么集群仲裁仍然存在。如果仲裁配置不当(比如没有见证磁盘或见证共享),集群节点之间发生网络分区时,可能会因为“脑裂”导致两边都想接管服务。我的建议是:生产环境一定要在第三方位置(如独立的云服务器或另一机房的机器)配置文件共享见证或云见证,并仔细测试两台主节点之间的网络断开的场景,观察故障转移行为和业务的影响时间。
4. 应用层与业务场景:问数智能体架构中的高可用设计
最近一年,“问数智能体”这类 AI 应用越来越多,很多企业会把自然语言查询能力接到自己的数据平台上,让业务人员直接问“上个月华东区的退货率是多少”就能拿到报表。这类系统有一个有趣的地方:它的高可用设计和传统 CRUD 系统很不一样,因为它在原有数据链路上又引入了大模型调用、NL2SQL 生成、语义理解这些新环节,每一环都是新的故障点。
4.1 新型应用场景:从传统后端到AI智能体
传统的高可用设计,核心是处理“流量突增、节点故障、数据一致性”这些问题。但问数智能体这类系统,高可用要额外考虑“模型服务不可用”和“生成结果本身不稳定”这两个变量。模型服务(比如大模型 API)不像你自己的 MySQL 集群,你没法完全控制它的可用性。如果上游模型接口超时或者限流,你的智能体如果只是简单等待,那用户感知到的就是“服务卡死”;如果设置了很短的超时,那用户又可能会出现“问一半就报错”的糟糕体验。
更麻烦的是,模型生成的 SQL 是不可预测的。同一个问题,用户今天问和明天问,生成的 SQL 可能不一样;换一个说法,得到的查询条件、聚合逻辑也可能不一样。这会导致一种新的“故障”:用户手上的报表数据前后对不上,信任感崩塌。所以在设计问数智能体的高可用架构时,不光要保证系统不挂,还要保证“在模型不稳定时,系统的输出仍然可控”。
4.2 智能体高可用的核心设计:降级、缓存、限流、重试
我在实际设计这类系统时,核心思路是把大模型作为“增强组件”而不是“单点依赖”。一句话:如果模型挂了,系统要能退化成规则模式,而不是完全不可用。
具体做法有四个要点。
第一是降级链设计。当模型服务异常或者超时时,系统自动切换到预设的模板 SQL 或者规则引擎来回答常见问题。例如把所有高频查询(销售报表、库存汇总等)提前用人工规则映射成一个配置化的查询模板,用户提问先经过一个轻量的关键词意图识别,能匹配上就直接执行模板 SQL,匹配不上再调用大模型。这样模型完全不可用时,高频问题依然能回答,保证了大部分业务场景的可用性。
第二是结果缓存。大模型有很强的“输入相似性”,同一个公司内部,用户问来问去其实就那么几十个问题模式。可以用语义向量做相似度检索,命中缓存的直接返回历史结果。这既降低了模型调用成本,又让系统响应变快,还保证同样的问题答案完全一致,避免了“今日答案和昨日不同”的信任问题。缓存失效策略要仔细设计,数据有更新时要主动清理相关缓存。
第三是限流和熔断。大模型 API 的限流往往比数据库严格得多,而且按 token 计费,一个排查错误的死循环可能烧掉一大笔钱。我会在智能体网关层做两层保护:一层是面向用户的全局限流(比如每个用户每分钟最多 10 次模型调用),另一层是针对上游模型 API 的熔断(连续失败超过阈值就快速失败,走降级逻辑)。同时设置超时时间,一般大模型调用的超时控制在一个合理的秒级范围,宁可让用户拿到降级结果,也不能让用户无限等待。
第四是状态和会话管理。问数智能体通常是有状态的,用户会在这个问题上追问“那华南区呢”“那上个月呢”,这要求系统保存上下文。如果仅靠内存保存会话状态,服务重启就会丢上下文,用户感知到的就是“突然失忆”。生产环境要把会话状态存到 Redis 这类外部存储里,并且把会话状态同步纳入高可用保障范围,Redis 本身也要做主从和自动故障转移。同时,智能体后面的任务执行(比如生成 SQL 后要跑一个复杂的离线查询)往往是异步的,我会把它投递到消息队列,由 worker 去执行,这样即使用户请求服务重启,异步任务也不会丢,最多重新消费一次。
5. 故障排查与实战经验:高可用不是设计出来的,是演练出来的
不管你的架构图画得多漂亮,高可用设计最终要回答一个问题:故障真的发生时,你的团队有没有能力快速恢复?这一章我整理了这些年积累的隐患清单和验证手段,希望能帮你少走弯路。
5.1 常被忽视的隐患清单
有几个隐患,平时不声不响,一到关键时刻就跳出来坑你。把这些点整理成一个速查表,供你对照检查:
| 风险点 | 典型表现 | 预防手段 |
|---|---|---|
| 心跳网络抖动 | 节点偶发“假死”,触发不必要的切换 | 心跳网络与业务网络隔离,合理设置超时和重试次数 |
| 时钟偏移 | 证书校验失败、复制日志时间戳错乱 | chrony 内网时间同步,监控时钟偏差 |
| 磁盘空间不足 | binlog 无法写入、从库回放卡住 | 磁盘使用率告警,日志自动清理 |
| 复制延迟积压 | 主从切换后新主数据落后 | 监控 Seconds_Behind_Source / 同步延迟指标 |
| 证书过期 | 组件之间 TLS 握手失败 | 证书到期自动告警,提前续期 |
| 备份仅备份不验证 | 恢复演练时发现备份不可用 | 定期做恢复演练,像真实故障一样恢复数据 |
| 变更不做预演 | 上线配置错误直接导致集群异常 | 变更前在预发环境完整执行一遍 |
这里有两点值得重点展开。第一个是“心跳网络抖动引发脑裂”的问题。无论你是用 keepalived 做 VIP 漂移,还是用 etcd 做选主,都要防止网络分区时出现“双主”或“双节点同时认为自己是主”的脑裂情况。应对思路是仲裁机制:K8s 里 etcd 靠多数派仲裁,Keepalived 靠优先级和组播域,MySQL 高可用方案里一般靠管理节点的判定和 fencing。但仲裁机制本身也可能成为单点或盲区,所以一定要把仲裁节点放在一个独立于普通节点的地方,并且定期模拟网络分区场景来验证不会脑裂。
第二个是“备份可用性和恢复速度”的问题。很多团队都有备份,但从未真正测过恢复流程。等你需要恢复时才发现:备份文件损坏、备份时间点落后太多、恢复步骤没人会操作。我建议每个季度做一次“备份恢复演练”,就好比消防演习,真到着火的时候才能有条不紊。恢复演练不只是 DBA 的事,要把应用的启动顺序、流量切换都一起测一遍,测完把结果记录成一份文档,下次再快速参考。
5.2 高可用架构的验证与演练:混沌工程与恢复SOP
高可用架构设计完成之后,下一步就是验证。我强烈建议引入混沌工程(Chaos Engineering)的思路,用主动制造故障的方式,验证你的系统是否真的能扛住。不是说你一定要上全套 Chaos Mesh 或者 Litmus 这类平台,哪怕手动做故障注入也有效果。你可以选择一个业务低峰期,定期做以下操作:直接 kill -9 主数据库的进程,观察 MHA 或 MGR 是否按预期切换;把 Kubernetes 某个节点直接关机,观察 Pod 是否重新调度到其他节点;断开两个机房之间的网络链接,观察跨机房同步是否中断、能否恢复。
做这些演练时,最重要的不是“故障被解决了”,而是发现预案里没有覆盖到的场景。比如我遇到过一次:演练时把主库 kill 掉,切换脚本成功把流量切到了新主库,但发现消息队列里的一个消费者连的还是旧主库的地址,导致下游业务写数据失败。这个问题在架构图上完全看不出来,只有真实演练才能暴露。所以我会建议,每做一次演练,都要把发现的问题追加到待办清单里,作为下次架构改进的输入。
另外,一定要有恢复 SOP(标准操作流程)。故障发生时,人往往处于慌乱状态,如果没有一份明确的 Runbook,操作人员可能在“要不要切换”“从哪里找文档”上浪费大量时间。我发现最好用的结构是:第一步,先做什么(比如先摘流量、先启动告警升级);第二步,看哪些监控指标;第三步,如果 A 方案不行,B 方案是什么;第四步,恢复后要检查哪些指标才能确认“真正恢复”,而不是只是表面恢复了。把这些流程写成 Markdown 放在团队 Wiki 里,并让每个值班成员都实际走一遍,确保紧急情况下知道到哪里找、怎么做。
最后再分享一个我自己的习惯:每次做高可用切换之后,无论切换成功还是失败,我都会要求团队写一份事后复盘,重点不是追责,而是理清“系统在哪一个瞬间发生了什么”“我们当时看到了什么信息”“我们做了什么决定”。把这个时间线整理出来,你会发现一个有趣的现象:很多时候故障持续时间极短,但因为没有人能快速定位信息,导致恢复动作迟迟没有执行。所以我会优先保证监控指标和日志信息在故障时是“能快速被找到的”,而不是把精力全花在优化某个组件的切换耗时上。
这套思路,无论是传统的 MySQL 高可用、Kubernetes 集群、SQL Server 同步,还是新兴的问数智能体架构,都一样适用:先想清楚目标和关键路径,再逐层消除单点、做好切换一致性,最后用演练验证并完善恢复流程。架构设计没有终点,每一次故障都是一次重新理解系统的机会。