单点是如何变成事故的

绝大多数线上事故的起点都很朴素:某个环节只有一台机器。这台机器半夜磁盘写满、进程被 OOM 杀掉、或者宿主机物理重启,整个服务就跟着挂了。它不是「会不会出事」的问题,只是「什么时候出事」的问题。

高可用要做的事,就是把这些单点一个个找出来、用「不止一份」的方式兜底。它不是上一套很贵的系统,而是一连串朴素决策的叠加:

  • 每一层都至少两份:应用多实例、数据库主从、负载均衡器双机。
  • 坏了要自动发现:靠健康检查把坏节点摘出去,别等用户来报。
  • 挂了要自动切换:流量自动绕开故障节点,不依赖人工半夜登机器。
高可用的目标不是「永不出错」,而是「出错时用户几乎感觉不到」。
本文核心知识点
  • 负载均衡把流量摊到多实例,是高可用最外层的那道闸门。
  • 健康检查自动探测坏节点并摘除,是「自动切换」的眼睛。
  • 无状态实例之间不存会话,才能随便扩缩容、随便摘挂。
  • 故障演练主动杀实例、断网络,验证你的高可用是真的还是纸面上的。
高可用与故障演练配图:分发器把流量分流到三台服务器、其中一台带健康盾牌标记的蓝色扁平科技插画
负载均衡分发流量,健康检查挑出坏节点,多实例彼此兜底——三者合起来才叫高可用。

负载均衡:流量怎么分发

负载均衡站在所有应用实例前面,替你把进来的请求分出去。它既做「分摊压力」,也做「摘坏兜底」。常见分发策略各有适用场景:

策略做法适用
轮询按顺序一个接一个分发实例配置相近、请求耗时均匀
加权轮询按机器性能分配不同权重实例规格不一时
最少连接发给当前连接数最少的实例请求耗时长短差异大时
一致性哈希同一用户固定落到同一实例有本地会话、需会话粘滞时

以 Nginx 为例,一个最基础的反向代理加负载均衡配置长这样:

upstream backend {
    least_conn;
    server 10.0.0.11:8080 max_fails=2 fail_timeout=10s;
    server 10.0.0.12:8080 max_fails=2 fail_timeout=10s;
    server 10.0.0.13:8080 max_fails=2 fail_timeout=10s;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
    }
}

注意 max_fails 与 fail_timeout:它的意思是 10 秒内失败 2 次就把这台实例暂时标记为不可用,一段时间后再试探恢复。这就是下一节要讲的健康检查的雏形。

健康检查:自动摘掉坏节点

一台实例进程还在但已经不服务了——连接池打满、线程死锁、后端依赖全挂——这种「半死不活」最坑。靠请求失败计数去摘节点太慢,正确做法是主动健康检查:负载均衡器定期主动请求一个轻量探测接口,连续失败就摘,恢复了就加回来。

  • 探测什么:一个独立的 /health 接口,只检查本进程是否活着,不要顺带查数据库——否则数据库一抖,所有实例全被误摘。
  • 探活频率:间隔几秒一次,失败连续两三次才摘,别因一次抖动就误伤。
  • 恢复探测:摘掉后还要定期试探,一旦恢复自动加回,别摘了就再也回不来。

这里有个经典反模式值得单独说:健康检查接口去依赖下游服务。你的 /health 里如果调了数据库和缓存,那么下游一抖动,负载均衡会把你整池实例都摘掉——本可以局部降级的故障,被你放大成了全站不可用。健康检查只回答「我自己起没起得来」,不回答「世界好不好」。