LVS调度算法选型与线上失衡排查实战
做网络和运维的朋友应该都跟 LVS 打过交道。这套基于 Linux 内核的负载均衡方案网上讨论最多的是 NAT、DR、TUN 三种模式怎么选、转发性能能跑到多少但我自己这些年折腾下来真正让线上集群翻车的往往不是转发速度而是调度算法。有几次大半夜爬起来看 ipvsadm 输出都是因为后端服务器负载明显不均最后定位来定位去问题都落在调度算法选型和对连接统计的理解上。这篇文章打算把 LVS 调度算法这块掰开揉碎不讲安装教程重点讲清楚几件事每类算法到底靠什么做决策、适用于什么业务、有哪些反直觉的坑以及线上出现调度失衡之后怎么一步步定位和处理。适合正在用 LVS 做负载均衡、或者准备从其他四层代理工具迁移到 LVS 的读者。看完之后你至少能根据业务特征直接列出候选算法并且知道用什么命令观察调度效果。1. 从一次失衡排查谈起调度器在连接建立那一刻做了什么1.1 我遇到的那次“流量均匀却负载不均”先说一个印象非常深的案例。某次压测后端两台机器配置完全相同ipvsadm -L -n看连接数基本五五开轮询算法也没有配置错误。但一台机器 CPU 已经跑到 90%另一台只有 10%。当时第一反应是 LVS 出了问题后来在客户端抓包才发现压测工具维护着连接池大量请求复用了少数几条长连接而这几天连接恰好都被调度到了一台 RS 上。也就是说LVS 层面的“调度”是公平的但业务层面的请求分布完全由连接建立模型决定。这个案例给我的印象特别深如果对 LVS 的调度粒度没有清晰认知很多失衡问题都会排查错方向。你可能会说这种事太巧了但生产环境里客户端连接池、服务间 RPC 长连接、数据库中间件复用连接都是同样的原理一点也不罕见。1.2 LVS 调度到底发生在哪个环节LVS 的转发路径有好几种但不管 NAT、DR 还是 TUN调度这件事都发生在同一个位置当一个新的 TCP 连接对 TCP 而言就是第一个 SYN 包到达 Director 时Director 会调用注册在内核里的调度器从当前存活的 RealServer 列表里选一台然后把这条连接的转发关系写进连接跟踪表。之后这条连接的所有报文都是直接查表转发不再经过任何调度算法的计算。换句话说调度算法只决定“新连接去哪儿”不参与“已建立连接怎么转发”。这里有一个非常关键的推论调度算法的输入是“连接建立事件”而不是“请求报文”。所以判断一个算法适不适合你的业务核心要看业务的连接特征——连接是长是短、请求是否复用连接、连接数能不能代表后端压力而不是看单连接的吞吐量或报文大小。很多人在社区里问“为什么 LVS 不按 CPU 负载调度”根源就是没理解这个环节。LVS 的内核调度器根本不看后端负载它只能看自己维护的连接表里这个有限的信息维度。NAT、DR、TUN 三种模式的区别在于数据报文的转发路径和封装方式NAT 模式下请求和响应都经过 Director适合中小规模集群DR 模式通过改写 MAC 让响应直接回客户端性能上限最高但要求后端和 Director 在同一个二层网络TUN 模式用 IP 封装隧道解决跨网段问题但会带来额外封装开销。有一点需要特别注意不管用哪种模式调度算法的选择范围都是一样的。工作模式解决的是“怎么把包送过去”的问题调度算法解决的是“把新连接送给谁”的问题排查时如果把这两件事混在一起很容易绕进死胡同。2. 静态调度算法轮询、加权轮询与哈希的适用边界2.1 rr 和 wrr 的“平均”到底指什么rr 是所有算法里最好理解的新连接挨个轮流分配给各台 RS第 1 个去 RS1第 2 个去 RS2循环往复。它不读任何状态内核开销最小。但“轮询”保证的是连接数量的平均分布不是流量的平均分布。在短连接场景下连接数量、请求数量、流量三者强相关rr 表现非常好一旦业务出现大量长连接问题就来了——先建立的连接会长时间占用某几台 RS而新连接依然被均匀分给所有 RS于是那些被长连接“占座”的服务器实际可处理的并发请求反而更少。压测工具连接池引起的分布不均就是这个原理。wrr 在 rr 的基础上引入了权重。假设 RS1 的权重是 4RS2 是 1那么调度器每轮会尽量按 4:1 的比例分配连接。权重怎么设我的经验是从硬件能力出发按 CPU 核数、内存容量、网卡吞吐综合评估。比如一台 8 核的机器对一台 2 核的机器起步权重可以设为 4:1然后再根据线上观测微调。这里特别想提醒一点很多人在负载不均时第一反应是“换算法”但在后端能力有明显差异的集群里先把 wrr 或 wlc 的权重调准往往比换算法更有效。权重是调度器唯一能感知“后端能力差异”的入口不会用权重就谈不上精确调度。2.2 dh 和 sh哈希调度换来的“亲和”与埋下的“热点”dh 是根据目标 IP 做哈希同一目标 IP 的新连接始终落在同一台 RS 上。这个特性的典型应用场景是后端缓存集群如果不同业务请求的目标 IP 被映射到不同缓存节点同一个目标的访问会稳定落在同一台缓存实例上缓存命中率会明显提升。但 dh 有一个大坑——它不是一致性哈希。RS 列表只要发生变化扩容、缩容、宕机哈希取模结果的重新分布会让大量映射关系跟着变原本固定在一台 RS 上的连接会大面积迁移。对缓存类业务来说这可能瞬间击穿缓存。所以使用 dh 的集群扩缩容动作必须非常谨慎最好在低峰期操作。sh 是根据源 IP 做哈希作用是让同一个客户端总是调度到同一台 RS常被用来做会话保持。但 sh 能不能用取决于源 IP 的分布是否足够分散。我见过一个案例某个办公网出口做了 NAT几千个用户对外只显示同一个源 IPsh 算法下这些用户的所有连接全被分到了一台 RS 上其他 RS 闲得没事干。这个场景下应该改用其他会话保持方式或者让出口 NAT 改成多 IP。另一方面sh 也不关心后端负载如果某个源 IP 是超大规模流量入口比如被高频调用的开放接口哈希会将这个热点完整地送给对应那台 RS形成单点压力。别指望哈希算法做负载均衡它的核心价值是“亲和”。3. 动态调度算法连接数统计公式里的门道3.1 lc 与 wlc默认算法的短板动态调度算法的共同特点是每次调度时都去看看当前各台 RS 的连接状态再从中挑一台。lc 的逻辑最简单谁当前连接数最少新连接就给谁。wlc 则是 lc 的加权版它选的是“连接数与权重比值最小”的那台。LVS 内核里 wlc 的计算公式是(active*256 inactive) / weight各台 RS 取最小值。公式里 active 是活跃连接数inactive 是不活跃连接数把它们都算进来是为了避免分配完连接就立刻被释放导致判断波动256 这个系数则让活跃连接在决策里比重更大。正因为这个公式既能覆盖大多数业务又计算简单ipvsadm在创建虚拟服务时不指定调度算法默认就是 wlc。但 wlc 有一个非常反直觉的点它假设“连接数越少后端越空闲”这个假设在长连接和请求复用场景下经常失效。举一个实际见过的例子一组 API 服务后端机器处理能力完全一致用 wlc 跑高并发短请求。很快发现其中一台机器 CPU 被打满其他机器还有余量。原因是那台机器处理请求特别快每次它的活跃连接数都最先降到低位调度器于是不停地给它派新连接。它越处理越快被派得就越多最终把自己压垮了。连接数不是负载这个教训我是用一次线上问题换来的。在后端处理速度差异大的短连接场景里wlc 反而可能放大失衡。3.2 sed 和 nq为“不要排队”设计的改进sed 全称是 Shortest Expected Delay它把目标改成“期望延迟最小”。公式是(active 1) * 256 / weight取最小值的 RS。相比 wlc它多算了“新连接即将带来的一次处理任务”可以理解成“当前这台 RS 的期望响应时间”。如果某台 RS 的活跃连接数已经很高即使它的权重也很大sed 也不会轻易把新连接塞给它因为再塞过去用户体验已经劣化了。所以 sed 更适合那些连接处理时间相对固定、连接数能比较好反映后端压力的业务。nq 是在 sed 基础上加了一条优先规则如果存在完全空闲的 RSactive 和 inactive 都为 0那就直接发给它不用比较公式只有所有 RS 都有连接时才退回到 sed 逻辑。这个“先把空闲机器塞满再排队”的思路在突发流量到来时特别有用。比如业务高峰刚起步后端几十台机器大部分是空的nq 会迅速把请求摊到所有空机器上避免流量先堆在前几台被轮询到的机器。但要注意nq 只关心“有没有空闲 RS”如果业务全是长连接RS 几乎不会进入完全空闲状态nq 就基本等价于 sed。3.3 lblc 和 lblcr为目标地址服务的动态亲和lblcLocality-Based Least-Connection稍微特殊一点它不是为源 IP 亲和而是为目标 IP 亲和。算法会为每个目标 IP 记录最近一次选中的 RS后续该目标 IP 的新连接优先仍然去这台 RS只有在这台 RS 的连接数明显高于整体水平时才改用 wlc 逻辑另选一台。你可以把它理解成“带逃逸机制的 dh”。它的价值在于既保留同一目标尽量落在同一台 RS 的亲和性又避免某个热点目标把一台 RS 彻底打挂。所以缓存类业务如果觉得 dh 太死板lblc 会平滑很多。lblcrLocality-Based Least-Connection with Replication是 lblc 的进阶版允许同一个目标 IP 对应多个候选 RS类似“一台缓存不够就把它复制到备选列表”。新连接来了优先使用最近使用的 RS如果它过载就尝试备选列表里负载最小的一台实在不行才走全局 wlc。代价是调度逻辑更复杂每台 RS 的复制状态需要额外维护后端扩缩容时复制列表的更新节奏如果跟不上会出现短暂的调度偏差。我的建议是如果后端缓存规模不大先用 lblc 就够了lblcr 留给那些真需要多级缓存副本的集群不要为了功能全选更复杂的算法。4. 生产环境选型按业务特征反向匹配调度算法4.1 选型前先回答三个问题很多读者上来就问“LVS 用哪个调度算法最好”这个问题本身就问错了。调度算法没有银弹只有匹配不匹配。我一般建议团队先回答三个问题。第一业务连接是短还是长一个 HTTP 请求几十毫秒就结束这算短连接WebSocket、推送通道、数据库连接池一次挂几个小时甚至几天就是长连接。第二后端各台 RS 的能力是否一致如果用了两代硬件混部权重设置就直接决定了最终的调度上限。第三业务是否有会话保持要求也就是同一个客户端是否能接受被调度到不同 RS。这三个问题问完候选算法基本就收敛了。4.2 一张选型表与实测体会根据这些年维护过的集群我整理了一张选型表供参考。注意这里写的是适用场景不是铁律真正上线前还是要压测验证。业务特征推荐算法关键点短连接、无状态、RS 规格一致rr / wrrwrr 权重设为 1 时等价于 rr保留后续调权能力短连接、RS 规格差异大wrr权重依据 CPU 核数、内存先定基线再按实测微调长连接、连接池复用nq / wrrnq 优先填空闲机器wrr 按预设比例控制处理速度差异大的短请求高并发wrr / sed避免 wlc 的快机器被连续派单打爆缓存集群要求目标 IP 亲和dh / lblcdh 简单但扩缩容代价大lblc 有逃逸机制多级缓存允许副本复制lblcr适合后端数量较多、热点分散的场景必须会话保持sh / persistentsh 无超时persistent 有保持窗口拿不准、先用默认wlc常规业务能扛住但要注意长连接场景我自己实际用下来最常翻车的是两列长连接场景用了 wlc以及会话保持用了 sh 却没注意源 IP 数量。前者会让连接数统计失真后者会产生单点热点。如果业务特征刚好落在表格里这两类的交界处宁可多花半天压测也不要拍脑袋直接上线。举个例子之前做一个带实时推送功能的社区系统后端有大量长连接一开始用的 wlc结果其中一台机器活跃连接数总是偏低新连接不断涌过来CPU 飙得很高。后来改成 wrr按后端规格设成 1:1 权重每台机器的连接分布马上稳定了。事后复盘长连接模式下连接数本来就反映不了瞬时压力再让调度器按连接数决策天然就是错配。4.3 会话保持sh 和 persistent 别混为一谈sh 算法的亲和是纯哈希的只要后端 RS 列表不变化它就永远不变化没有超时概念。persistent 则是 LVS 提供的一个独立机制创建虚拟服务时用-p参数指定保持时间比如-p 3600表示在 3600 秒内来自同一个源 IP 的新连接都会进入同一个持久性模板强制发给第一次命中的那台 RS。两者的主要区别有三个sh 靠源 IP 哈希persistent 靠模板记录sh 没有过期时间persistent 有sh 在 RS 宕机时通过重新哈希来分配新连接persistent 在保持窗口内会继续把新连接尽量发往原 RS直到窗口过期。实际业务里如果应用层已经用 Cookie、Token 或者共享 Session 存储解决了会话保持LVS 这层就别再加 sh 或 persistent加了只会让负载分布变差。我见过有人为了让推送系统不掉线同时开了 sh 和 persistent结果某个源 IP 段因为 NAT 出口集中导致一台 RS 压力巨大。后来去掉 persistent 只保留 sh并且让后端把会话信息放到共享存储问题才缓解。会话保持的选择本质是“你愿意为亲和付出多少均衡代价”的取舍。5. 调度失衡的排查思路与调优实操5.1 从 ipvsadm 统计看连接分布线上怀疑调度有问题第一件事是看 Director 上的 ipvsadm 统计。常用三种输出方式ipvsadm -L -n查看每台 RS 的 ActiveConn 和 InActConnipvsadm -L -n --stats查看累计连接数、进出字节数ipvsadm -L -n --rate查看每秒连接数、每秒流量怎么看呢先看各台 RS 的 ActiveConn 分布。如果明显偏斜直接怀疑权重配置或连接模型如果 ActiveConn 分布正常但后端负载差异大就要去 RS 上确认 CPU、内存、磁盘 IO问题往往不在调度而在单机资源争抢。这里有一个容易误判的点某台 RS 的 InActConn 很高时不代表它空闲可能是 TCP 连接处于 ESTABLISHED 状态但长时间没有活跃比如客户端连接池挂着一堆长连接。这种情况连接数统计是虚的直接按连接数判断负载会踩坑。5.2 常见“调度不均”的根因与处理链路根据经验调度不均基本逃不出下面几类原因对号入座反而最快。权重与机器能力不匹配。RS 硬件是两代产品混部权重还都是 1。处理办法是先把权重改成按核数、内存比例设置wrr 和 wlc 都支持。长连接占座。rr 看似平均但先建立的长连接长期占用某些 RS新连接不断被轮询到其他机器。处理办法是改用 wrr 并配合连接数上限必要时在后端加连接数告警。连接池复用。客户端根本不新建连接LVS 调度的粒度是连接不是请求。这种情况只能从客户端或业务层入手LVS 层无法感知单条连接内的请求频率。健康检查失效。RS 已经宕机但权重没有被置 0新连接继续被调度过去。处理办法是检查健康检查组件的探测间隔和判定逻辑故障 RS 要尽快从 ipvs 规则里摘掉。InActConn 堆积。TCP 超时参数设置不合理连接表里积攒了大量已结束的连接。处理办法是按业务调整ipvsadm --set的超时时间。任何一次失衡排查都建议按这个顺序走先看 Director 连接统计再看后端 RS 负载先确认 RS 存活和权重再讨论算法选择。不要一上来就动算法先把变量控制住。5.3 权重、超时参数和压测验证的经验权重调整有一个容易忽略的细节LVS 的权重只影响新连接分配已经建立的老连接不会因为权重调低就被断开或迁移。所以想通过降低某台 RS 权重来给它“减负”只能等现有连接自然结束效果有明显的滞后性。如果某台 RS 已经明显过载更快的办法是直接把它的权重设成 0让它停止接收新连接等存量连接消化完再恢复。超时参数用ipvsadm --set tcp tcpfin udp调整。默认值我记得 TCP 是 900 秒TCP-FIN 是 120 秒UDP 是 300 秒。如果业务全是短连接TCP 900 秒意味着很多早已结束的连接还会在 LVS 连接表里保留 15 分钟这些条目占用内存不说还会让 InActConn 虚高干扰你对连接分布的判断。短连接场景建议把 tcp 和 tcpfin 调小长连接场景反而要把 tcp 调大避免正常长连接被提前回收。最后说一个我自己的习惯每次调整算法或权重之后不要只看一两分钟就说结论。连接建立模型受业务流量周期影响很大白天和夜里的分布可能完全不同至少跑一个完整的压测周期我通常压 30 分钟以上再对比 ipvsadm 统计和 RS 监控图。另外我会把 Director 的 ipvsadm 输出和后端每台 RS 的连接数监控放在同一个看板里一旦出现失衡立刻能对上号。做运维久了你会发现调度算法本身并不难难的是把“连接分配”和“真实负载”这两套数据在同一个时间轴上对照着看。