一台云虚拟机在凌晨两点被流量打挂了。云虚不是拟机被攻击,是载均它后端接的那个推荐服务突然开始抽风,响应时间从80毫秒飙到4秒。云虚用户请求全堆在连接池里,拟机CPU看着不高,载均但线程全卡死了。云虚运维爬起来扩容,拟机加了四台机器,载均结果新机器一上线就被压垮——因为负载均衡器还在往那台已经半死的云虚旧机器上狂发请求。
这就是拟机云虚拟机负载均衡最要命的地方:它不光是“把请求分一分”那么简单。你分得不对,载均扩容等于白扩,云虚加机器反而可能加速雪崩。拟机
云厂商控制台上通常给你两个选项:四层(TCP/UDP)和七层(HTTP/HTTPS)。很多人随手选七层,觉得“更高级”。但四层转发只认IP和端口,它不解析HTTP头,所以速度快、延迟低,适合数据库代理、游戏服务器这类长连接场景。七层要拆包看URL、Cookie、Header,能做基于路径的路由——比如 /api/* 转给一组机器,/static/* 转给另一组。代价是每个请求多一次解析开销。我见过一个团队把gRPC服务挂在七层LB后面,结果延迟凭空多了15毫秒,换成四层立刻恢复。
选错了层,后面怎么调都是白搭。
默认的健康检查通常是每30秒发一个TCP握手或者HTTP GET /。问题在于,你的应用可能端口还活着,但内部线程池已经满了。TCP检查照样通过,LB继续往这台机器上灌流量。更隐蔽的是,有些框架在优雅关闭时会先停掉业务线程,但HTTP端口还监听一段时间——健康检查一直返回200,LB以为它很健康,实际上它已经处理不了任何请求。
我自己的做法是自定义一个 /health/deep 端点,里面检查数据库连接池、Redis连接、关键线程池的活跃度。任何一个指标异常就返回503。健康检查间隔缩到5秒,连续失败2次就摘除。代价是LB和后端多了一点心跳流量,但比半夜被叫醒强。
电商购物车、登录态这些场景,传统做法是开会话保持(Session Affinity),让同一个用户的请求始终落到同一台后端。这确实省事,但扩容时新机器分不到已有用户的流量,缩容时那台机器上的用户全掉线。云厂商的LB通常支持基于Cookie的会话保持,但Cookie过期时间设长了,用户粘在一台有问题的机器上死活不换。
更合理的路子是把会话外置到Redis,LB用轮询或者最少连接算法。代价是每次请求多一次Redis调用,但换来的是后端完全无状态,扩缩容瞬间生效。我经手的一个项目从会话保持切到Redis集中存储后,扩容时间从“等用户自然流失”变成“30秒内新机器接满流量”。
轮询最简单,适合后端机器配置完全一致的情况。最少连接数在长连接场景下更合理,但它统计的是“当前连接数”,不是“当前负载”。如果某台机器连接数少但每个连接都在跑重查询,最少连接算法照样把它打爆。加权轮询需要你手动调权重,调不准就是灾难。云厂商现在有些支持“自适应”算法,根据后端响应时间动态调整权重——听着美好,实际用下来,响应时间采样窗口设大了反应迟钝,设小了来回震荡。
我的经验是:无状态短请求用轮询,长连接用最少连接,有状态服务先做无状态化再谈算法。别指望一个算法解决所有问题。
负载均衡不是配完就完事的。它更像一个持续调参的过程——流量模式变了、后端版本更新了、网络拓扑调整了,LB的策略都得跟着动。那些把LB当“设好就忘”的团队,迟早会在某个凌晨收到告警短信。