多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

集群负载均衡实战:从算法选型、健康检查到故障转移全链路解析

集群负载均衡实战:从算法选型、健康检查到故障转移全链路解析 我们在生产环境里跑过几十台节点的集群也折腾过从几百 QPS 到几万 QPS 的流量变化负载均衡这块我踩过的坑比看过的文档多得多。很多人一开始以为负载均衡就是把请求轮询发到几台机器上等真正上了集群才发现连接不均衡、数据倾斜、故障误判、会话丢失每一个问题都能让你从凌晨三点排查到天亮。这篇文章以我在多个集群项目里的实操经验为基底把集群负载均衡的几个关键环节拆开讲均衡策略选型、健康检查设计、故障转移链路、分层架构搭配以及数据集群和 AI 算力集群里的特殊处理。文章不会只讲理论每个部分都会给出可落地的参数、配置思路和排查路径对正在搭 Hadoop、Redis、Kafka、Doris 或微服务网关集群的工程师都有参考价值。1. 从单机到集群负载均衡要解决的三个本质问题先想清楚一个问题我们做负载均衡到底在均衡什么流量连接数据还是计算资源如果只理解为“把请求分到不同机器”那写一个轮询脚本就够了根本不需要研究集群负载均衡。实际上集群环境下的负载均衡比单机时代复杂得多核心要解决三个本质问题。1.1 请求分散只是表面状态一致性才是深层约束集群最大的优势是水平扩展但一旦引入多节点状态一致性问题就来了。举个最简单的例子一个 Web 应用用 Session 保存用户登录态单机时代 Session 就在本机随手可取。上了集群之后用户的请求轮流打到 A、B、C 三台机器Session 在 A 机器上下次请求打到 B 机器如果 B 机器没有 Session用户就被踢下线了。这就是为什么负载均衡策略里会有“会话保持”这个选项。常见的做法有几种源地址哈希根据客户端 IP 做哈希同一个 IP 固定打到同一台后端节点。实现简单但只解决了“会话保持”没解决“数据均衡”——如果一个公司 NAT 出口就一个 IP那这个 IP 下所有用户全挤到一台机器上。Cookie 会话保持七层负载比如 Nginx、网关在用户请求里写入 Cookie后续请求根据 Cookie 路由到对应节点。比源地址哈希更精准但要求负载均衡器能解析七层协议。共享会话存储把 Session 放到 Redis 这类外部存储里节点本身无状态负载均衡器随便轮询都没问题。这是最干净的方案代价是引入 Redis 集群的额外复杂度Redis 集群本身又要做负载均衡和数据分片。我在实际项目中强烈推荐“共享会话存储 无状态节点”的方案。表面上看多做了一层 Redis但收益很大节点可以随时扩缩容负载均衡策略可以从“会话保持”的束缚里解放出来自由使用最均衡的算法。会话保持本质上是把单机故障的恢复时间等于最终用户感知的时间太痛了。1.2 吞吐、延迟与可用性三者如何取舍一个集群做负载均衡不可能同时做到三个极致吞吐量最大、延迟最低、可用性最高。这像极了数据结构里的 CAP 定理——你必须在三者之间做取舍。以 Kafka 消费端为例。Kafka 的消费组本身就是一种负载均衡机制一个分区的数据只会被组内一个消费者拉取所以消费者数量和分区数量的匹配非常重要。如果消费者多于分区多余的消费者完全空闲吞吐没提升反而增加了 Rebalance 开销如果消费者少于分区一个消费者要处理多个分区单点压力就上来了。这种场景下“负载均衡”就不是简单把请求轮询发出去而是根据后端处理能力做适配。Kafka、Redis Cluster、Doris 这类数据集群的负载均衡核心是数据分片和副本分布请求层面的负载均衡只是入口层的事情。在生产环境里我的经验是先把可用性底线画出来再谈吞吐和延迟。可用性靠的是健康检查和故障转移吞吐靠的是合理的分片和并行度延迟靠的是最短路径和缓存。三者的优先级不能乱。2. 负载均衡算法选型轮询、一致性哈希与“等开销”策略的适用边界算法是负载均衡的“大脑”。大部分集群项目里真正能用好的算法不超过四个轮询、最少连接、一致性哈希、以及结合后端负载状况的自适应策略。但这四个不是互相替代的关系而是适用于完全不同的场景。2.1 轮询与加权轮询什么时候“最公平”反而是“最不公平”轮询Round Robin是最简单的算法把请求按顺序轮流发到后端。加权轮询Weighted Round Robin给每个节点配置一个权重让性能好的机器多分流量。这个算法在“所有节点能力一致、所有请求耗时一致”的假设下是最公平的。但生产环境里这个假设几乎不成立集群里有旧机器和新机器CPU 核数和内存不一样某些请求本身很重比如报表查询某些请求很轻比如健康检查节点上有其他任务在跑瞬时负载不可控。轮询的致命问题是它不感知后端状态。有一次我们在生产环境用纯轮询后端有一台机器因为磁盘 IO 异常变慢了轮询照样给它发相等比例的请求结果这台慢机器上的请求堆积反过来加剧了磁盘负担形成恶性循环。集群的整体延迟从 20ms 飙到了 2s而负载均衡器一点感知都没有。所以我的结论是如果后端节点能力不一致、请求处理时间波动大不要用纯轮询。Kubernetes 的 Service 默认是轮询在普通场景够用但如果你在 K8s 里跑 Redis 集群或者计算密集型的 Spark 任务建议把负载均衡策略在 Ingress 或 ServiceMesh 层改成别的方式。2.2 一致性哈希数据集群和数据分片的黄金搭档一致性哈希Consistent Hashing解决的核心问题是当节点增减时尽量少地迁移已有的映射关系。最典型的使用场景是缓存集群。假设你用 5 台 Redis 做缓存用 hash(key)%5 决定数据落在哪台机器。当集群扩容到 6 台hash(key)%6 的结果和以前完全不同几乎所有 key 都“跑”到了新机器上缓存全部穿透数据库被击穿这就是生产事故。一致性哈希的做法是把哈希值空间组织成一个环每个节点根据哈希值分布在环上每个 key 顺时针找到最近的节点。当节点增减时只有该节点附近的 key 需要迁移其他 key 的映射保持不变。但一致性哈希有个新问题如果节点少分布可能严重不均。3 台机器在哈希环上可能挤在一起导致一台机器承担 80% 的流量。解决办法是引入“虚拟节点”——每个物理节点映射出 100~200 个虚拟节点散落在环上让分布趋向均匀。“等开销负载均衡”这个词在搜索里经常被关联到集群它实际上是一种更精细的目标让每个节点的负载考虑 CPU、内存、IO、带宽等多维资源尽量相等。一致性哈希只保证了 key 的分布均匀不能保证每个 key 的访问频率和访问成本相等。所以生产里做 Redis 集群除了用一致性哈希做分片还要关注热点 key 的问题某个 key 访问量巨大哈希落到一台机器上那台机器就是热点。2.3 最少连接、动态加权与自适应后端感知型策略最少连接Least Connections算法会把请求发给当前并发连接数最少的节点。它比轮询聪明一点因为它感知到了后端的“忙闲”程度——只要后端能承受的请求数是有限的并发连接数就能近似反映压力。但我在实际测试里发现最少连接有一个隐蔽的坑它假设“每个连接的耗时相近”。如果有一台机器处理请求特别慢它上面的并发连接数会一直很高新请求自然会避开它。这看起来很好慢节点被边缘化了。可是如果问题出在负载均衡器而不是后端节点呢我遇到过 Nginx 和后端之间 keepalive 连接池分配不均的情况导致某些后端的连接数异常偏高最少连接反而把流量都导到了其他节点那几个节点也被拖垮。更现代的做法是动态加权。负载均衡器定期采集后端节点的实时负载指标CPU、内存、请求队列深度、最近响应时间计算一个综合负载分数调度时把流量尽量分给分数最低的节点。Sentinel 在做 Spring Cloud 网关流量控制时就用到了类似的思路——它可以基于 QPS、线程数、响应时间做熔断和流控本质上就是一种自适应负载均衡。我的建议是请求类型单一、后端能力一致用加权轮询 健康检查有状态服务或缓存用一致性哈希 虚拟节点后端处理能力波动大或请求类型复杂用最少连接或动态加权追求极致的系统在负载均衡器之外再加一层自适应流控比如 Sentinel 的熔断降级不要指望单一算法解决所有问题。3. 健康检查与故障转移集群稳定性的守门员负载均衡算法决定流量怎么分健康检查决定哪些节点“有权分到流量”。这部分在项目里最容易被低估。很多刚搭集群的团队负载均衡配上就完了健康检查参数用默认值结果故障转移要么迟迟不触发要么误杀健康节点比不做还糟糕。3.1 主动探测与被动探测的搭配逻辑主动探测是负载均衡器每隔几秒主动向后端发送探测请求比如 TCP 连接、HTTP GET 一个健康检查接口探测成功就标记为健康失败就摘除。被动探测是在真实请求过程中统计超时、连接失败、5xx 错误次数超过阈值就把节点标记为不健康。两者必须搭配用。只做主动探测的典型问题是探测接口写得太简单只返回一个 200没检查依赖的数据库、缓存、磁盘空间。结果后端业务逻辑已经因为依赖故障而龟速运行了健康检查还显示“非常健康”流量照常打过去用户界面一片报错。我踩过类似的坑。当时一个集群里某个节点的磁盘满了业务接口写不进去数据但健康检查只探测了一个静态页面的 HTTP 状态永远返回 200。从负载均衡器看这个节点健康得很实际上已经半瘫痪了。正确的健康检查应该做到探测接口返回当前节点关键依赖的状态比如能否连上本机 Redis、能否读取数据库连接池、磁盘剩余是否低于阈值主动探测的间隔不能太长我一般设 3~5 秒超时 1~2 秒连续失败 3 次才摘除被动探测作为兜底只要真实请求连续超时比例超过某个阈值立即强制摘除。3.2 熔断、慢启动与摘除恢复避免雪崩的细节故障转移不只是“把坏节点摘掉”更关键的是“怎么摘”和“恢复时怎么加回来”。先说摘除。如果一个节点因为瞬时 CPU 飙高导致响应慢被动探测连续秒失败负载均衡器就直接摘除它。这一步要小心“拆东墙补西墙”A 节点故障流量切到 B 和 CB 和 C 本来就接近满载新的流量一压B 和 C 也超时了然后 B 和 C 也被摘除雪崩就是这么来的。自我保护的办法是给每个节点设一个最大并发上限比如 MaxConns流量超过上限后新请求直接排队或者返回 503而不是无限地塞给后端。Sentinel 的“线程数隔离”就是干这件事的。再说恢复。后端节点从故障里恢复后不要立刻把它加回来满负荷转发。原因很简单它的缓存是冷的、连接池是空的、依赖的本地资源刚刚重建一下子接收大量流量非常容易再次挂掉。正确做法是慢启动Slow Start新恢复的节点在 1~2 分钟内权重从 10% 慢慢增加到 100%这期间它一边处理流量一边把缓存和连接池暖起来。3.3 跨集群迁移中的负载均衡以 CDH 到 CDH 为例搜索热词里有“把数据从一个 CDH 集群迁移到另一个 CDH 集群”我顺便说说这个场景里的负载均衡问题。CDH 之间的数据迁移常见工具是 DistCp分布式拷贝或者 Replication Manager。迁移过程会产生大量的 MapReduce 任务这些任务的调度本身就是集群负载均衡的一部分。我遇到的典型问题是迁移任务的 vCore 和内存资源没有设置上限把目标集群的计算资源全部占满业务查询完全跑不动。解决办法给迁移任务单独设置资源池YARN Capacity Scheduler 的队列限制最高资源使用率迁移时错峰避开业务高峰在迁移前先测试两个集群之间的带宽后面第 5 节细说确认不会把网络打满。迁移完成后的数据校验也要注意负载均衡。MapReduce 校验任务如果并发太高同样会压垮集群建议限制校验任务的 Map 并行度在合理范围。4. 分层负载均衡从四层入口到数据层调度的完整链路一个复杂的集群系统负载均衡不会只发生在某一层而是从用户入口到数据存储层层递进。每一层的目标和选型都不同。4.1 四层 LVS 与七层 Nginx链路中的分工在典型的 Web 集群架构里负载均衡一般是两层LVS四层在前面扛高并发Nginx七层在后面做反向代理和业务路由。四层负载均衡工作在网络层和传输层只处理 IP 和 TCP/UDP 头不解析 HTTP 报文所以转发速度极快性能上限高。LVS 的 DR 模式性能尤其好数据包进来后直接改写 MAC 地址转发到后端响应包由后端直接返回客户端不经过 LVS避免了“单点流量回程”的瓶颈。七层负载均衡能感知 HTTP 协议可以做路径路由、Cookie 会话保持、HTTPS 卸载、请求头改写。但代价是每次请求都要解析完整的 HTTP 报文性能比四层低不少。在生产环境里这两层必须结合用LVS 负责把流量均匀分发到一组 NginxNginx 负责按 URL 路径、域名路由到具体的业务集群业务集群内部再做一次负载均衡比如 Spring Cloud 的 LoadBalancer 或者 RPC 框架的服务发现最终数据访问由数据集群自己的分片机制决定落到哪个节点。千万别想着只用一层搞定所有事。只用 Nginx高并发下 Nginx 会成为瓶颈只用 LVS你没法做 HTTP 路由和灰度发布。4.2 网关层的流量治理Spring Cloud Gateway 与 Redis 集群的联动微服务架构里Spring Cloud Gateway 是七层负载均衡和流量治理的关键节点。它不是简单地把请求转发给下游服务而是要做认证、限流、熔断、动态路由。有一个非常典型的场景网关做限流和熔断状态数据放到 Redis 集群里。这个方案的负载均衡问题非常隐蔽。Redis 集群本身有分片从网关视角看你配置一个 Redis 集群地址客户端连接会通过集群的代理或者直接和多个分片通信。如果 Redis 集群节点负载不均比如某个分片的 key 特别集中那这个分片的网络和 CPU 都会成为瓶颈网关的限流数据读写也会变慢。因为网关是同步等待 Redis 响应的Redis 慢了网关就慢了整个请求链路都拖慢了。我当时的做法是给网关的 Redis 限流数据单独使用一个小的 Redis 集群和业务数据完全隔离限流数据的 key 设计上要避免热点比如以“网关节点 ID 用户 ID 秒级窗口”为维度做分布式限流这样每个 key 的访问频率都相对均匀网关本身多节点部署前端再挂一层负载均衡避免网关成为单点。从架构演进来看Spring Cloud Gateway 里的路由规则还可以从配置中心动态加载这样发布新服务或者改动负载均衡权重时不需要重启网关对集群的运维体验提升很大。三个网关节点做滚动更新始终保持至少两个节点在服务流量无损切换这就是负载均衡在发布场景中最朴素但也最关键的作用。4.3 数据集群的负载均衡差异Redis、Kafka、Doris、Hadoop 各自怎么做不同数据集群有各自的 Sharding 和 Replication 机制负载均衡的切入点完全不同。我按这些年实际用过的集群逐个说一下Redis Cluster的数据分片基于哈希槽Slot总共有 16384 个槽位每个节点负责一部分槽位。集群的负载均衡核心是槽位的分配均匀以及当节点增减时槽位的自动迁移。Redis 集群的请求分发可以走客户端直连聪明客户端根据 key 计算节点也可以走集群代理Predixy、Codis 等。客户端直连少一跳延迟低但客户端要维护集群拓扑变化代理模式对客户端透明适合客户端种类繁杂的团队。Kafka的负载均衡重点在分区分配和消费者组 Rebalance。生产端要把消息均匀分发到多个分区不能都用默认的粘性分区——有些业务场景下部分 key 特别多只按 key 哈希会积压到个别分区。消费端要注意最多消费者数量不超过分区数量否则消费者白白空转还可能导致频繁 Rebalance 把集群搞抖。新的 Kafka 客户端在 Rebalance 时支持静态成员组Static Membership可以避免因为 GC 停顿导致的成员频繁进出。Doris部署成集群时FEFrontend节点负责查询解析和规划BEBackend节点负责数据存储和计算。Doris 的负载均衡可以放在 FE 前面用四层负载或者 JDBC 连接串里配置多个 FE 地址让连接分摊到不同 FE。但在查询重、数据倾斜明显的场景下单靠连接层均衡是不够的还需要在表设计的时候制定分桶策略让数据均匀分布到 BE 上。Hadoop/YARN集群的负载均衡主要体现在任务调度上。YARN 的 Capacity Scheduler 和 Fair Scheduler 都在做“计算资源的负载均衡”。容量调度器按队列划分资源公平调度器按任务动态调剂资源。分布式集群里如果多个部门共用一套 Hadoop必须把资源队列划清楚不然一个大任务就能把整个集群的计算能力吃光其他任务全部排队。Spark 集群的负载均衡在 Driver 把 Task 分发给 Executor 这一层。Executor 的数量决定了并行度Task 分配到 Executor 的策略在 Spark 内部实现用户能控制的是分区数partitionBy、repartition、并行度参数spark.sql.shuffle.partitions。我在 Spark 调优时发现shuffle 分区数如果设置过小小于 Executor 总数肯定有部分 Executor 空闲设置过大又会增加 shuffle 写入的寻址开销。合理值公式没有但可以从“分区数 ≈ Executor 核数的 2~3 倍”起步观察任务执行时间再调整。5. 集群间流量调度、带宽测试与 AI 算力集群的负载均衡5.1 如何测试两个集群之间的真实带宽“如何测试集群之间的带宽”是很多集群建设初期都会遇到的问题。迁移数据、跨集群备份、联邦查询都依赖集群之间的网络带宽而网络设备标称的带宽和实际能跑出来的带宽往往差距很大。最精准的办法是用 iperf3 做 TCP 吞吐测试在集群 A 的一个节点上启动服务端iperf3 -s在集群 B 的节点上启动客户端iperf3 -c 集群A节点IP -P 8 -t 60-P 8表示用 8 个并行连接能够测出接近全速的吞吐-t 60测 60 秒避免短时间测试受慢启动影响。但要注意几个实际问题集群之间如果有防火墙或安全策略iperf3 的默认端口会被拦截需要先放行如果两个机房跨地域尤其是跨运营商TCP 窗口大小会影响吞吐需要调大内核 socket 缓冲区参数net.core.rmem_max和net.wmem_max测出的带宽是一对节点的带宽不是整集群的带宽。要估算全集群带宽需要做多节点并行测试。测试时要避开业务高峰避免把生产带宽打满拖垮线上业务。更贴近大数据场景的测试方法是直接用 Hadoop 自带的TestDFSIO工具。它可以模拟大量文件写入和读取测试 HDFS 集群的吞吐能力结果比 iperf3 更符合真实数据迁移场景。5.2 AI 算力集群的构成、架构与调度负载均衡AI 算力集群和传统大数据集群有本质区别。传统集群主要处理数据瓶颈在磁盘 IO 和网络吞吐AI 集群主要跑 GPU 训练任务瓶颈在 GPU 算力、显存容量和 GPU 之间的互联带宽。这里的负载均衡场景主要体现在两个层面训练任务的分配和GPU 资源的调度。一个典型的 AI 推理集群硬件层是 GPU 服务器 高速互联如 InfiniBand 或 RoCE软件层是 Kubernetes GPU 调度插件如 Volcano、Kueue。这里说的负载均衡指的是如何把不同 Size 的推理任务和训练作业分配到不同的 GPU 节点上尽量让每张 GPU 卡的利用率接近。我建议从三个维度设计 AI 集群的调度显存维度任务声明需要的显存大小K8s 调度器根据节点可用显存做过滤和打分。这个如果没做好一张 32G 的卡上可能堆了 20 个只用到几百 MB 的小任务而其他节点完全空闲。拓扑亲和多卡训练任务最好把同一台机器上的卡分配在一起避免跨节点通信。因为 NVLink 带宽远高于以太网跨节点通信会成倍增加训练时间。时间片和抢占策略推理任务通常在白天多训练任务在夜间多。AI 集群如果支持时间片复用和优先级抢占就可以在一天的维度上让 GPU 资源利用率更平滑。5.3 集群故障转移的演练如何验证负载均衡真的“扛得住”故障转移的代码一个小时就能写完但验证它有效要花一两周。我见过太多集群配置了故障转移但从来没真正触发过生产一挂就出问题。建议每季度做一次故障演练至少覆盖几个场景停掉一台应用节点观察负载均衡器是否在健康检查超时窗口内摘除它存量请求是否被平滑切走停掉一台 Kafka Broker观察生产者是否感知到元数据变化分区 Leader 是否快速切换停掉一个 Redis 主节点观察哨兵或 Cluster 协议是否完成主从切换客户端是否能够自动重连给某个节点制造 CPU 过载观察负载均衡算法能否自动把流量导向其他节点。演练之前要通知到所有相关团队最好在凌晨低峰期做准备好回滚方案。记录每次演练的“摘除耗时”和“恢复耗时”连续两次演练如果耗时差异超过 50%说明你的健康检查参数可能需要调整。6. 生产环境落地负载均衡的细节与排查链路最后一节讲几个我反复踩的、文档里很少写清楚的细节。这些问题每一个都能让集群在关键时刻“掉链子”。6.1 负载均衡器自身的瓶颈与高可用很多人关注后端节点的高可用却忘了负载均衡器自己也是单点。Nginx 只有一台它挂了整个集群就完了LVS 只有一台Keepalived 没配好VIP 漂移失败流量就断了。生产环境的配置建议LVS 必须用 Keepalived 做主备两个节点之间用 VRRP 协议共享一个 VIP主节点挂了备节点在 2~3 秒内接管Nginx 至少部署两台前面的 LVS 或云负载均衡做流量分发负载均衡器本机的资源连接数、句柄数、内存要纳入监控。Nginx 的连接数打满之前通常是worker_connections或者系统ulimit先到上限提前调好这两个参数。6.2 超时、重试与幂等负载均衡中最容易被忽略的三角关系请求超时了怎么办负载均衡器通常会重试。但重试是有风险的如果请求不是幂等的比如下单、转账重试会导致重复扣款或重复下单。在负载均衡层配置重试时不能只写“失败就重试”必须考虑重试的对象是哪个节点如果原节点超时重试打到另一个节点如果是因为原节点处理慢但没挂打到另一个节点反而可能增加另一个节点的压力重试的次数上限我建议最多重试 1 次重试太多会在后端故障时把流量放大几倍直接把其余节点拖垮请求幂等性写操作最好在业务层做幂等或者在网关层先识别出不能重试的请求。超时时间的设计也有讲究。假设后端接口 P99 延迟是 800ms负载均衡器的响应超时就不能设成 1s否则大量正常请求会被误判为超时。建议先通过监控拿到真实延迟分布把超时设置为 P99 的两倍左右再结合被动探测的阈值做动态调整。6.3 一套完整的异常排查链路最后给一套排查负载均衡异常的处理思路。集群出问题时不要瞎猜按顺序查先看指标再翻日志优先确认是接入层、服务层还是存储层的异常检查负载均衡器的后端列表确认哪些节点被摘除、哪些异常恢复这部分直接把流量分散情况和节点健康状态对得上检查后端节点的连接数、CPU、内存、磁盘 IO判断是后端被流量打满还是后端本身有故障对比负载均衡器上的请求量和实际到后端的请求量如果差距很大说明负载均衡器层丢请求了要么是连接池打满了要么是超时被丢弃了看响应时间曲线如果 P99 和 P50 差距突然拉大通常是少量慢请求占满了线程池导致快速请求也在排队。我按照这套路径排查过至少十几次集群事故多数情况下原因都出在“健康检查太乐观 重试策略太激进 后端没有自我保护”这三件事上。把这三个点稳住集群的稳定性能提升一大截。负载均衡的最终目的不是“把流量均匀分发出去”而是“让集群里的资源各司其职系统在故障面前还能稳稳地向外提供服务”。这需要你把算法、健康检查、故障转移、架构分层和运维演练全部考虑到位而当这些都到位之后你会发现集群真正的价值才体现出来——扩容可以很小步故障可以很优雅流量可以很平滑。
返回列表