Skip to content

高可用架构

机器挂了服务就废了——这就是高可用要解决的问题。说白了,高可用就是"东西坏了之后服务还能不能继续跑"。具体坏的方式五花八门:机器宕机、进程崩掉、磁盘写满、网络抽风、数据库不可写、缓存挂掉,每一种都能让服务趴下,但表现和处理方式都不一样。

单机系统里最要命的就是单点。一开始你可能觉得"我多备份数据不就行了",但真出问题的时候会发现:入口只有一个,入口坏了后端再健康也白搭——根本没人能进来;数据库主库只有一个,主库不可写的时候所有写接口全报错,业务直接停。所以高可用要做的事其实就两件:把单点拆掉,把切换做对

一、单点

任何"只剩一个"的关键对象都是单点,坏了之后影响各不相同:

单点坏了之后的现象
单台 Web 服务器页面和接口全部不可访问
单个负载均衡后端正常,但外部入口不可用
单个数据库主库写入失败,所有依赖写入的接口报错
单个 Redis缓存、会话、限流、分布式锁全部异常
单条公网线路外部访问中断,或者部分地区访问异常

减少单点不是简单复制一份机器摆那儿就行——多出来的实例要能被入口发现并接入流量,坏掉的实例要能被自动摘除,角色切换之后客户端要能连到新的主节点。这些动作缺一项,备用机器就是摆设,真出事的时候根本不会自动接管。

二、主备

主备是最容易理解的高可用方案:一台主节点干活,另一台备节点在旁边候着,主节点出问题的时候备节点顶上。打个比方,就像演戏的 A 角和 B 角,A 角正常演,B 角在后台候场,A 角出状况了 B 角立刻顶上去,观众基本无感。

数据库、负载均衡、网关、存储系统里都能见到主备形态。备节点平时不一定闲着——有的只做数据同步、有的承担读请求分担压力、有的完全冷备只在故障时启动,具体看产品实现。

主备真正麻烦的是切换那一瞬间。切得慢,业务中断时间长,用户感知明显;切得早或者切错了,可能出现两个节点都以为自己是主的情况,这就是脑裂。脑裂场景里两边同时接写请求,数据分别往两边写,后面要合并非常痛苦,甚至可能丢数据。

Keepalived 的 VIP 漂移是典型的主备入口方案——平时 VIP 挂在主节点上,主节点不再响应心跳之后 VIP 自动漂到备节点,客户端一直访问同一个 VIP,完全感知不到背后切了机器。数据库主备切换就更重一些,除了入口变化,还要考虑复制进度(数据有没有同步过去)、只读状态(旧主有没有正确切到只读)、应用连接刷新(连接池里可能还连着旧主)这些问题。

三、故障发现

高可用系统得先能发现故障,才能切流量或者切角色。发现故障的方式不止一种:

机制关注什么
心跳节点之间能不能互相看到
健康检查入口层定期探测后端,判断是否能处理请求
Leader 选举多个节点里投票选出一个主节点
仲裁网络分区时,避免多个主节点同时出现
监控告警从错误率、延迟、资源、状态变化发现异常

这里有个特别坑的点:心跳失败不代表对方真的死了,也可能是中间网络断了。两个节点互相看不到的时候,如果都直接抢主,本来只是网络抖动的小问题,就被升级成了数据冲突的大问题。所以分布式系统里会引入各种机制避免这种"孤立节点自己做决定":仲裁节点(第三方投票)、租约(必须定期续约才算主)、Quorum(多数派同意才能切换)、外部锁(用 etcd/Zookeeper 这种做仲裁)。

切换完成之后,用户侧看到的现象会很具体:连接被重置、短时间 502、写入接口返回只读错误、登录态突然丢失。判断切换是否成功,不能只看入口能不能访问,还要试一次写入,确认新主确实可写、旧主确实不再接写请求,再看错误率和延迟有没有回落到正常水位。

四、集群形态

"集群"这个词在不同组件里含义差很多,不要一概而论:

类型怎么分工
Web 集群多个无状态实例,入口负载均衡分发请求
Redis Cluster数据按 slot 分散到不同节点,每个节点负责一部分 key
Kafka 集群Topic 分区分布到多个 broker,消费者按消费组分担
数据库集群主从、组复制(MGR)、分片、多主——模型有好几种
Kubernetes 集群控制面调度 Pod,工作节点承载实际负载

无状态服务做集群最轻——请求打到任意实例,都能从数据库、缓存、对象存储拿到需要的状态,实例之间完全等价,挂一个少一个,加一个多一个。

有状态服务做集群难得多,要先说清楚几件事:数据放在哪个节点、谁能写、副本同步到什么程度、节点坏掉之后由谁接管。Redis、Kafka、MySQL、etcd 都有自己的一套分布式模型,不能只用"多节点"这一个词概括。这也是为什么有状态服务的运维比无状态服务难得多——MySQL 集群和 Redis Cluster 和 Kafka 集群,虽然都叫集群,但运维逻辑、故障切换、扩缩容方式完全不一样。

五、数据一致性

应用做高可用相对简单,因为应用是无状态的,实例之间等价,挂一个不影响别的。但数据做高可用就麻烦多了,要同时解决复制延迟、主从切换、写入冲突、数据恢复这一堆问题。

场景用户侧会看到什么
主从复制延迟刚提交的数据,马上去查列表,查不到
主库故障切换少量最近写入的数据没有出现在新主上
多主写入冲突两个地方同时改同一条记录,结果互相覆盖
网络分区部分节点认为主库不可用,部分节点还能连接旧主

很多业务系统选择"一个主库负责写、多个副本负责读和容灾"这种架构,就是为了减少写冲突——写入口越多,一致性问题越复杂。多主架构虽然可用性更高,但写入冲突的处理非常麻烦,工程复杂度也高,所以大多数业务系统还是单主+多从的模型。

读写分离场景里有个特别经典的体验问题:写完立刻读。比如用户刚改完头像,下一秒刷新个人资料页,如果这个读请求被路由到了延迟从库,从库还没同步过来,用户看到的还是旧头像——他会以为"修改没生效"。所以这种"写后立刻读"的请求一般要特殊处理,直接打到主库而不是走读写分离。这个坑很多人都踩过。

六、恢复确认

高可用不是把所有组件都做成双份就完事。不同重要性的系统,高可用投入应该不一样——内部低频工具可能只需要备份、监控和能快速重建就行;核心交易系统才需要主备、跨机房、灰度发布、限流熔断和定期演练。把所有系统都按最高标准做高可用,成本根本承受不住。

故障切换之后要留下能检查的东西,以便复盘和确认:

检查项看什么
入口域名、VIP、负载均衡后端是否切到新节点
应用错误率、延迟、实例重启情况、日志异常
数据库新主可写、旧主只读或被隔离、复制链路状态
用户路径登录、查询、提交、支付、后台任务是否都恢复
监控告警是否恢复,有没有残留错误

切换记录里写清楚关键时间点非常重要:什么时候发现故障、什么时候切入口、什么时候写入恢复、什么时候旧主被隔离。后面复盘 RTO(恢复时间目标)和 RPO(恢复点目标,即允许多少数据丢失)的时候,这些精确到分钟甚至秒的时间点,比一句"已恢复"有价值得多。