新闻详情

新闻详情

首页 / 资讯中心 / 详情

Nginx负载均衡算法与upstream配置实战:轮询、最少连接与一致性哈希

发布时间:2026/9/29 7:32:11来源:尧图网络
Nginx负载均衡算法与upstream配置实战:轮询、最少连接与一致性哈希
1. 负载均衡到底在解决什么问题1.1 从一台机器扛不住说起任何一台服务器都有物理上限。CPU 核数、内存带宽、网卡队列、磁盘 IOPS这几项里总有一项会先到顶。单机纵向扩容换更强的机器在成本上是不划算的性能翻倍往往意味着价格翻三倍而且扩容当天就得停机。横向扩容多台机器组集群才是主流路径代价是必须回答一个新问题请求该由谁接、怎么分。负载均衡就是回答这个问题的组件它站在客户端和真实服务之间把流量按照某种规则分发到后端多个实例上。它带来的收益不只是分摊压力。一组服务实例放在负载均衡后面你能做到不停机发布先摘掉两台、升级、再挂回来、灰度放量新版本只接 5% 流量、故障隔离一台挂了自动不再分配。所以负载均衡在架构里的真实定位是流量调度层性能只是它最容易被看见的那部分职责。有人觉得负载均衡就是把请求轮流发给后端配置里随便填几个 IP 就完事了。这种理解在只有两台机器、QPS 几百的场景下确实不会出问题一旦集群规模上去、请求耗时出现分化、上线了缓存和长连接分配不均的问题就会以某台机器 CPU 打满、其他机器闲着的形式暴露出来。这也是我写这篇东西的起因——大部分负载均衡的坑都不在有没有配而在算法和参数为什么不匹配。1.2 四层与七层的分界线聊负载均衡绕不开四层和七层的划分。判断标准很简单组件是否完整解析了应用层协议。只处理 TCP/UDP 包头、按 IP 和端口转发的是四层能看懂 HTTP 请求行、Header、URL 的是七层。对比项四层负载均衡七层负载均衡工作层次传输层TCP/UDP应用层HTTP/HTTPS/gRPC转发依据源目 IP、端口、协议号Host、URL、Header、Cookie拆包开销不拆包只改包头完整解析请求开销更大典型吞吐十万到百万级 QPS万级到十万级 QPS常见形态内核态转发、硬件设备Nginx、各类网关服务会话保持依赖源 IP 哈希依赖 Cookie 或 Header 哈希适合的业务数据库代理、长连接入口微服务 API、灰度路由选择逻辑很清楚越靠外层越用四层越靠内层越用七层。公网入口通常先过四层扛量再由七层做业务路由。反过来把七层放在最外侧扛全量流量CPU 会大量消耗在 TLS 握手和 HTTP 解析上得不偿失。我在一次活动大促里见过把 HTTPS 终止全放在业务网关上的做法流量上来后网关 CPU 直接跑满后来把证书卸载前移到四层入口网关负载立刻降了六成。这个改动几乎没动业务代码只是把职责放回了正确的位置。1.3 负载均衡不等于高可用这是最容易被混淆的一点。负载均衡解决的是分配问题高可用解决的是存活问题两者只是常常一起出现。一个只做了轮询转发、没有任何健康检查的负载均衡器后端挂了一台它照样会把请求发给那台死机器用户看到的就是七分之一的请求失败。完整的可用性保障至少需要三层配合负载均衡层的健康检查与自动摘除连续失败 N 次后把节点踢出转发列表、调用方的超时与重试单次请求卡住不能无限等、以及服务自身的限流与熔断防止被上游的重试洪峰打垮。健康检查还分主动和被动主动检查是负载均衡器定期发探测请求被动检查是根据真实请求的失败结果来判断。开源版 Nginx 的max_fails属于后者节点只有在真实请求失败了才会被计数这意味着一个新挂掉的节点在被判定失败之前仍然会吃掉一部分流量。对延迟极度敏感的接口被动检查的这段窗口期是真实存在的风险值得通过多探针或者应用层主动上报来弥补。2. 常见负载均衡算法逐个拆解2.1 轮询与加权轮询最朴素的公平轮询Round Robin简称 RR就是按顺序一个个发。假设后端是 A、B、C 三台请求序列就是 A→B→C→A→B→C。它假设了所有后端完全等价配置一样、当前负载一样、每个请求消耗的资源一样。只要这三个条件成立轮询就是最简单且绝对均匀的方案实现上只需要一个自增计数器取模。加权轮询Weighted Round RobinWRR是给这个假设打了个补丁。机器规格不一致时比如 8 核机器和 2 核机器并存给 8 核机器配 weight4、2 核机器配 weight1让强机器多干活。权重怎么定我一般按 CPU 核数或内存做基准用最小规格的机器作为 1其他机器按比例取整。比如集群里有 2C4G、4C8G、8C16G 三种规格权重就设 1:2:4。这个算法比拍脑袋设高配设 10、低配设 3靠谱得多因为它是可推导的后续加机器也能延续同一套换算逻辑。但最朴素的加权轮询有个明显缺陷它会把同一个节点的请求堆在一起。按权重 5:1:1 的配置序列会变成 A A A A A B C前五个请求全部砸向 AA 会在很短时间内出现一个尖峰然后再空闲。这种突发式分配对连接池、线程池都不友好很容易触发瞬时限流。2.2 平滑加权轮询的数学过程Nginx 默认的加权轮询用的是平滑算法能保证权重大小关系不变的前提下把请求序列打散。它的实现只需要两个变量每个节点的固定权重 weight和运行时变化的当前权重 current_weight。每一轮的过程是固定的三步每个节点的current_weight weight选出current_weight最大的节点把选中节点的current_weight - 所有节点的 weight 之和。用权重 5:1:1 的三个节点 a、b、c 实际走一遍总权重是 7轮次累加后的 current_weight (a,b,c)命中扣减后的 current_weight (a,b,c)15, 1, 1a-2, 1, 123, 2, 2a-4, 2, 231, 3, 3b1, -4, 346, -3, 4a-1, -3, 454, -2, 5c4, -2, -269, -1, -1a2, -1, -177, 0, 0a0, 0, 0七轮跑完序列是 a a b a c a aa 出现 5 次、b 和 c 各 1 次权重比例完全正确而且 b、c 被均匀地插在中间没有出现连续扎堆。第七轮结束后所有 current_weight 回到 0说明这是一个长度恰好等于总权重的循环不会出现长期漂移。这个归零性质是平滑算法的关键如果实现里忘了扣减总权重误差会累积几十万次请求后就会出现明显的分配倾斜。用代码实现只有十来行这里给一段可以直接跑的最小版本方便你在自己的调度逻辑里复刻class SmoothWRR: def __init__(self, nodes): # nodes: [{name: a, weight: 5}, {name: b, weight: 1}] self.nodes nodes self.total sum(n[weight] for n in nodes) for n in self.nodes: n[current] 0 def pick(self): best None for n in self.nodes: n[current] n[weight] if best is None or n[current] best[current]: best n best[current] - self.total return best[name]注意比较current_weight时要用严格大于而不是大于等于。平局时固定选列表里靠前的那个才能保证序列可复现。如果是大于等于选中项会随遍历顺序漂移压测时你可能会看到两份完全不同的分布结果白白浪费排查时间。2.3 最少连接与加权最少连接权重是静态的它描述的是机器的能力上限而不是此刻有多忙。同样权重的两台机器一台正在处理三个耗时 2 秒的报表接口另一台在跑一堆 10 毫秒的查询轮询会把它们当成一样闲结果就是慢的那台越堆越多。最少连接Least Connections用当前活跃连接数作为分配依据谁手上的连接少就给谁天然具备自适应能力。加权最少连接least_conn配合 weight在 Nginx 里的实际判据是连接数 / 权重的比值比值最小的胜出。所以权重大小关系和动态负载是同时生效的。这个算法有个必须知道的盲点连接数不等于工作量。如果后端用的是 HTTP 长连接一个连接上可能已经跑了上千个请求也可能刚建好还没用连接数完全反映不出真实压力。另外在使用连接池的场景下比如后端是数据库代理连接数是稳定的最少连接反而退化成近似轮询。判断要不要用least_conn我的经验是看接口耗时的方差耗时方差大P99 是 P50 的十倍以上就用它方差小就用轮询没必要为了看起来先进而引入额外的状态统计。2.4 一致性哈希缓存场景的救命稻草前面几种算法都假设后端是无状态的请求发给谁都一样。一旦涉及本地缓存、本地文件、有状态的会话这个假设就崩了。轮询加上本地缓存会得到这样的结果同一个 key 第一次落到 A 被缓存第二次落到 B 就得回源再查一次缓存命中率被硬生生砍到 1/N后端压力反而更大。一致性哈希把节点和一个哈希空间映射成一个环。常见实现是把 0 到 2 的 32 次方减一这个区间首尾相接成一个环节点按哈希值落在环上请求也按 key 的哈希值落环然后顺时针找到的第一个节点就是目标。这样同一个 key 永远落在同一个节点上缓存命中率保住了。它真正聪明的地方在扩容时的表现。假设环上有 A、B、C 三个节点新增一个 D用取模哈希的话几乎所有 key 都要重新映射缓存集体失效回源洪峰能把数据库打穿。一致性哈希里只有落在 D 和它逆时针前一个节点之间的那段 key 需要迁移其余保持不变代价从全量降到约 1/N。但只有三个物理节点的环哈希分布往往很不均匀。解决办法是虚拟节点每个物理节点在环上放 100 到 200 个虚拟点key 落到虚拟点再映射回真实节点。虚拟点越多分布越接近均匀。这个数量不是越多越好200 个虚拟点的路由表已经需要二分查找了继续增加只是徒增内存和查找开销。常见的 Ketama 实现默认每节点 160 个虚拟点是个久经考验的经验值。upstream cache_backend { hash $request_uri consistent; server 10.0.0.21:6379; server 10.0.0.22:6379; server 10.0.0.23:6379; }上面这段里hash $request_uri consistent表示按 URI 做一致性哈希consistent关键字就是开启环式哈希并启用虚拟点。如果要按用户维度保持会话把变量换成$cookie_sessionid或者基于用户 ID 拼出来的变量即可。2.5 最短响应时间与 P2C最短响应时间Least Time直接用历史响应时间作为选点依据把请求发给最近表现最好的节点。它比最少连接更贴近用户感受因为连接数少不代表响应快——一个卡在 GC 上的节点可能连接数很少但每个请求都要等几百毫秒。它的代价是需要持续采集指标。开源版 Nginx 不提供这个算法商业版本里才有least_time指令可以选择以响应头时间或完整响应时间为依据。自己实现的话通常会维护一个滑动窗口的平均 RT窗口大小是个权衡窗口太大反应迟钝慢节点要很久才被降权窗口太小容易抖动一个偶发的慢请求就会让节点被冷落之后它的样本更少、判断更不准。P2CPower of Two Choices二选一是另一个思路随机挑两个节点从这两个里选指标更好的那个。它不需要维护全局状态是个纯本地决策天然适合分布式的大规模调用。数学上它只比纯随机好一点但实际效果惊人——它能把最忙节点和平均节点的负载差距压到很小的范围同时避免了所有调用方都选同一个最优节点的羊群效应。gRPC 的负载均衡策略和不少 RPC 框架都提供这个模式。我自己在服务间调用上做过对比P2C 相比轮询P99 延迟降低了差不多三成改动量只有几行配置。2.6 随机与加权随机随机算法就是从后端列表里随机取一个。听起来很粗糙但在节点数量足够多几十个以上时大量请求的统计结果会收敛到均匀分布而且它不需要任何中心状态每个调用方可以独立决策不存在单点。缺点是短时间窗口内会有波动节点少的时候尤其明显三台机器一百个请求很可能出现 45:35:20 这种分布。加权随机在随机的基础上按权重设置概率区间实现上就是累加权重后取一个随机数落在哪个区间。它在小规模集群下不如平滑加权轮询均匀但胜在实现简单、无状态、天然去中心化。我在一些边缘节点的小集群里会用它因为那套环境里不方便维护中心的调度状态。3. Nginx 负载均衡配置实战3.1 upstream 基础结构与权重换算Nginx 的负载均衡配置集中在upstream块里写法不复杂但每个参数的选择都有讲究upstream api_backend { least_conn; server 10.0.0.11:8080 weight5 max_fails3 fail_timeout10s; server 10.0.0.12:8080 weight3 max_fails3 fail_timeout10s; server 10.0.0.13:8080 weight1 backup; keepalive 64; }逐个参数说。least_conn声明使用最少连接算法不写就是默认的平滑加权轮询。三行server定义后端weight是权重按前面说的核数比例换算11 号机器 8 核、12 号 4 核多一点、13 号是备用的低配机器所以是 5:3:1。backup标记表示这台机器平时不接流量只有当 11 和 12 全部不可用时才会启用适合放一台降级用的机器或者专门跑兜底逻辑的服务。max_fails3 fail_timeout10s这两个参数要连起来读在 10 秒的统计窗口内失败 3 次节点就被摘除被摘除后同样要等 10 秒才会重新放一个请求进去探测。如果探测成功节点恢复失败则继续摘除。所以一个节点从挂掉到完全退出转发列表最坏情况下会损失 3 个请求从恢复到稳定接流量又要经历一次探测。fail_timeout设得太短会导致频繁摘除恢复把本来只是偶发超时的节点反复踢出设得太长会让已经恢复的节点长时间闲置。我一般把这两个值设成接近业务超时时间的两到三倍比如接口超时是 3 秒这里就设max_fails3 fail_timeout10s。keepalive 64是 upstream 到后端的长连接池大小。这里很容易踩坑只写这一行是不生效的还需要在location里配合两行配置location /api/ { proxy_pass http://api_backend; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }proxy_http_version 1.1是必须的因为 HTTP/1.0 默认不支持长连接。proxy_set_header Connection 用来清掉客户端发来的Connection头否则客户端说closeNginx 转发到后端也会跟着关连接连接池就白建了。这两行是长连接生效的必要条件少了任意一行你会在后端看到TIME_WAIT数量居高不下每秒钟都在建新连接。我见过一个团队排查了半个月为什么 QPS 上不去最后发现就是漏了这两行加上之后同一个压测脚本的 QPS 翻了将近一倍。3.2 会话保持ip_hash、hash 与无状态化的取舍会话保持sticky session有几种实现方式各有各的代价。ip_hash按客户端 IP 做哈希同一个 IP 固定落到同一台机器。它的问题是大量用户来自同一个出口 IP 时公司出口、学校出口、移动网络网关这些用户会全部压到同一台后端上。我遇到过一次线上告警某台机器 QQ 相关的流量占比超过 70%排查下来是某个大客户的内网通过单一出口访问几万个用户共用一个源 IPip_hash把它们全指向了同一个节点。而且ip_hash要求集群大小固定加了机器之后只能靠权重来慢慢引流扩容操作非常别扭。hash $cookie_xxx consistent用业务侧的 Cookie 做键粒度比 IP 细也不受 NAT 影响是更推荐的做法。但要注意 Cookie 可能为空——未登录用户第一次访问时还没有这个 Cookie所有空值会落到同一个节点上形成热点。解决方式是给空值做一层兜底比如在变量拼接时掺入$remote_addr或者$request_id让空 Cookie 的用户也能分散开。最彻底的方案还是无状态化把会话数据挪到 Redis 之类的共享存储里后端任何一台机器都能处理任意请求负载均衡层就可以放心用轮询。这个改动一次投入、长期受益代价是要重构会话的读写逻辑。我的建议是新项目一开始就按无状态设计别等到集群扩容时才回头改。3.3 健康检查的被动与主动开源版 Nginx 只支持被动健康检查也就是靠max_fails这套机制。它的局限在于必须等真实请求失败才会计数而且失败的定义是连接建立失败、超时或者响应错误如果你的后端接口返回 200 但对内报错Nginx 是不知道的。要做到主动探测通常有三条路一是换用带主动检查能力的商业版本或者支持该能力的网关组件二是引入第三方的健康检查模块三是在运维层用一个独立的探针服务探测失败后调用接口动态地把节点从 upstream 里摘掉或者直接改配置 reload。第三条路最土但最可控我在几个项目里都用过一个简单的定时任务每 5 秒 curl 一次每个节点的健康接口连续两次失败就调运维接口摘节点恢复后再挂回来。逻辑加起来不超过一百行可控性和可观测性比任何黑盒方案都好。注意不管是哪种健康检查探测接口一定要设计得足够轻。我见过把健康检查接口写成查一次数据库、调一次下游的节点本身只是轻微卡顿健康检查也一起超时结果所有节点被同时判定为不健康整个集群瞬间空转。健康检查接口的职责只有一个——证明进程还活着、能接受连接。3.4 超时与重试最容易被误用的两个参数proxy_connect_timeout 2s; proxy_send_timeout 5s; proxy_read_timeout 5s; proxy_next_upstream error timeout http_502 http_504; proxy_next_upstream_tries 2; proxy_next_upstream_timeout 10s;四个超时参数的作用点不同理解清楚才能设对。proxy_connect_timeout是跟后端建立 TCP 连接的超时一般设 1 到 3 秒这个时间反映的是网络和 backlog 队列的情况设太长没意义。proxy_send_timeout是把请求发给后端的超时proxy_read_timeout是等后端响应的超时这两个要按业务的实际耗时来设一般取 P99 的两倍左右。proxy_next_upstream是重试策略。这里有个非常危险的细节默认情况下如果请求已经发出去、后端也已经开始处理但响应超时了Nginx 会向下一台后端重发这个请求。对于那些非幂等的接口——比如下单、扣款、发消息——重试意味着用户可能被扣两次钱、收到两条消息。我的处理原则是写接口POST/PUT/DELETE原则上不开重试或者只对连接失败这种确定没到达后端的错误重试proxy_next_upstream error绝不对timeout重试。读接口GET可以放心重试。如果业务上确实需要对写接口做重试就必须在服务端做幂等控制比如用请求 ID 去重、数据库唯一索引、乐观锁版本号让重复请求变成无害操作。另一个容易忽略的点是重试会放大流量。上游失败 20%、重试 2 次意味着后端要承受 1.2 倍的请求量。如果是因为后端整体过载导致的超时重试洪峰会把已经奄奄一息的集群彻底压死。这种时候更该做的是熔断和快速失败而不是重试。4. 不同业务场景下的算法选型4.1 无状态 API 服务的选型普通的 REST API、微服务间的内部调用属于这一类的典型。特征是所有实例等价、请求耗时接近、无本地状态。这种情况下直接用平滑加权轮询就足够了配置简单、行为可预测、排查方便。想再进一步可以上P2C代价是需要部署侧的指标采集能力。我不建议在这类场景里过早引入最少连接或者最短响应时间。它们的收益在请求耗时方差大的时候才明显而在方差小的场景里它们会因为指标波动做出一些看起来莫名其妙的决策反倒增加排查难度。压测时发现分布不匀如果用的是自适应算法你得先怀疑是指标采集出了问题还是算法本身在起作用多了一层不确定性。4.2 缓存与有状态服务的选型带本地缓存的场景用一致性哈希键的选择决定成败。用 URL 做键适合静态资源用用户 ID 做键适合会话和个性化数据用业务主键做键适合领域对象缓存。键的基数要足够大否则热 key 依然会集中在某一个节点上哈希再均匀也救不了。曾经有个项目的排行榜接口哈希键用了榜单类型总共只有三种取值三个节点各接一个榜单结果大榜单的节点被打爆小榜单的节点在发呆。后来把键改成榜单类型分页区间才真正分散开。有状态服务还有一个容易忽视的问题节点摘除时的状态迁移。一致性哈希解决了哪个 key 归谁但没解决节点挂了之后它的 key 由谁接管。缓存场景下这只是回源成本上升有状态场景下可能就是数据丢失。所以真正需要持久化状态的服务状态应该存在外部存储里负载均衡层的哈希只是为了减少跨网络的访问次数而不是状态的唯一载体。4.3 异构机器与灰度发布机型不一致的集群权重换算前面已经说过按 CPU 核数做基准。但有一种情况权重解决不了同一台机器上跑了多个实例其中一个实例做了内存泄漏。这时候应该做的是把异常实例摘掉而不是调权重。权重的定位是静态能力的描述不要把它当成动态调速的旋钮每天调权重说明你的容量规划出了问题。灰度发布是另一个高频场景。常用做法是在 upstream 里建两个组一个稳定组放 95% 的流量一个灰度组放 5%用split_clients按请求特征分流split_clients ${remote_addr}${http_user_agent} $group { 5% gray; * stable; } upstream app_stable { server 10.0.0.31:8080; server 10.0.0.32:8080; } upstream app_gray { server 10.0.0.41:8080; } location /api/ { proxy_pass http://app_$group; }这里的分流依据去掉了纯 IP掺入了 User-Agent是为了避免同一个用户在不同设备上被分到不同版本。split_clients的百分比是按请求数算的流量小的接口样本量不足可能前几百个请求一个都没进灰度组放量前最好先用大盘确认一下实际比例。灰度期间务必盯紧新版本的错误率和延迟别只看流量进来了多少。4.4 等开销负载均衡ECMP在四层的应用等开销负载均衡这个词更多出现在网络设备的语境里指的是等价多路径Equal-Cost Multi-Path在路由层面存在多条代价相同的转发路径时把流量按某种哈希规则分摊到这些路径上。它工作在四层以下和前面聊的应用层算法是两个层面的东西但在现代数据中心里两者是配合使用的。ECMP 的分流依据通常是五元组源 IP、源端口、目的 IP、目的端口、协议号的哈希。同一个 TCP 连接的流量会被散列到同一条路径上这就是流粘性它保证了同一连接的报文不会乱序到达。没有流粘性的话一个连接的数据包走了不同路径接收端会因为乱序大量重传吞吐反而下降。ECMP 有两个典型问题值得记住。一是哈希极化如果所有流量都来自少数几个源 IP哈希结果会集中导致部分链路拥塞而其他链路空闲。二是大象流一个长期存在的大流量连接比如备份任务、大文件传输会死死占住一条路径其他流无法挤进去即便整体带宽还有富裕。所以现代网络里会引入动态调整机制或者用更细粒度的流标识来打散。做应用层的容量规划时如果发现每条链路带宽利用率都很高但应用侧 QPS 不高可以往这个方向排查。5. 常见故障与排查实录5.1 流量倾斜的四种典型原因排在第一位的永远是权重配错。前面提到的那次事件扩容的新机器权重默认是 1而老机器还是 5新机器几乎没接到流量。排查方法很直接让所有后端节点输出自己的请求计数对比一下差异。如果差异比例跟权重比例一致那就是配置问题如果差异跟权重完全对不上往下看第二条。第二条是长连接复用导致的实际分配不均。这是最隐蔽的一类。即使算法是完美的轮询如果调用方和后端之间是长连接连接一旦建立就会被反复使用新连接只有在旧连接关闭后才会建立。结果是早期建立的连接把大量请求压到了少数节点上后面新加的节点拿不到连接只能干等。表现得就像算法失效了。验证方法是统计每个节点上的请求数而不是连接数如果连接数很均匀但请求数差异很大基本就是这个问题。解决办法是给长连接设置最大请求数比如每个连接处理 1000 个请求后主动关闭或者最大存活时间强制重新分配。第三条是哈希键分布不均。用一致性哈希时如果键的取值集中某些节点天然会拿大头。用hash $remote_addr尤其明显移动网络下大量用户共享出口 IP。验证方法很简单把哈希键的分布统计出来看看 Top 10 的键占了多少请求量。第四条是慢节点导致的连接堆积。节点本身没挂但响应慢连接被长时间占用连接数越来越高反倒因为最少连接算法被不断降权看起来是好事——但它的连接池已经被占满再接到新请求就是排队这时候应该直接摘掉它。慢节点比挂节点更难处理因为挂节点会被健康检查快速剔除慢节点却可能一直活着、能响应只是拖累整体。监控上一定要给每个节点的 P99 单独建图不能只看集群整体平均。5.2 慢节点引发的雪崩链条一个节点变慢可能引发一连串反应这是负载均衡层面最需要警惕的故障模式。链条通常是这样的第一环节点响应变慢活跃连接数上升。第二环上游的请求在负载均衡器上排队等待时间变长。第三环超过了上游的超时阈值上游开始重试请求量放大。第四环重试的请求打到其他健康节点上把其他节点的负载推高它们也开始变慢。第五环健康检查因为超时开始误判把本来健康的节点也摘掉可用节点进一步减少。整个过程可能只需要几十秒。要打断这条链关键动作有两个一是在负载均衡层做快速失败而不是无限等待超时时间要设得比后端正常耗时长一点但绝不设成等到底二是在调用方做熔断当某个节点的失败率超过阈值时直接停止向它发请求一段时间不要让它继续消耗资源。还有一个经验是给慢节点设置冷静期——被判定为慢后强制摘除一段时间而不是立刻允许它重新接流量。这样做虽然浪费了一点容量但能避免它在半死不活的状态下反复抖动。5.3 连接数不均衡与端口耗尽后端看到的现象是某些节点ESTABLISHED数量特别高另一些很低。除了上面的长连接问题还有一个常见原因是负载均衡器自身的连接池配置。如果每个工作进程维护独立的连接池池子大小和进程数、后端节点数的组合会影响实际分布。比如 4 个 worker 进程、3 个后端节点某些 worker 的连接池可能偏向了某一个后端。还有一类问题是端口耗尽。负载均衡器主动向后端建立大量短连接时会消耗本地的源端口默认范围通常是 32768 到 60999大约 28000 个。如果连接建立速度很快、回收又慢TIME_WAIT状态默认持续 60 秒端口会被耗尽报错通常是 Cannot assign requested address。我遇到过的一次压测脚本以每秒 3000 个请求的频率打过来没建长连接不到 20 秒就开始大量报错。解决办法就是启用 upstream 长连接把连接建立频率降下来这是最根本的解法。排查命令我常备这几条排查目的常用命令关键观察点查看端口与连接总量ss -sTIME-WAIT 数量是否异常增长查看监听队列ss -lntSend-Q 是否长期非零说明 backlog 堆积按后端统计连接ss -tan | awk {print $5} | sort | uniq -c各节点连接数是否均衡单节点耗时curl -o /dev/null -s -w %{time_total}\n http://节点/健康接口与集群平均值对比查看生效配置nginx -T确认 upstream 与实际预期一致注意nginx -T输出的是合并后的完整配置包括 include 进来的所有文件。排查配置改了没生效的问题时一定要用它而不是看单个配置文件被 include 的旧配置覆盖新配置是极常见的原因。5.4 一份可直接对照的问题速查表现象最可能的原因优先动作某节点流量明显偏高权重配置错误核对 upstream 权重与机型比例连接数均匀但请求数倾斜长连接复用设置连接最大请求数与存活时间新扩容节点接不到流量长连接未重建或权重过低重启客户端连接池或调整权重请求集中在少数 key 所在节点哈希键分布不均更换哈希键或加虚拟节点集群整体 P99 上涨慢节点拖累 重试放大摘除慢节点、关闭超时重试后端出现大量 TIME_WAIT未启用 upstream 长连接配置 keepalive 与 HTTP/1.1报 Cannot assign requested address源端口耗尽启用长连接或扩大端口范围某个接口偶发重复数据超时重试作用于非幂等请求关闭 timeout 重试、加幂等控制6. 把负载均衡用稳的几条个人经验6.1 参数不要拍脑袋先算出基准值再说权重、超时、连接池大小、fail_timeout这几个参数我见过太多是参考了网上一篇博客就填上去的。它们的合理值其实都能推导权重按核数比例算超时按 P99 的两倍算连接池大小按峰值 QPS × 平均耗时估算并发数再留 20% 余量fail_timeout按业务超时的两到三倍设。推导出来的值不一定最优但至少有依据出问题时你知道该往哪个方向调。拍脑袋填的参数出事时连怀疑的对象都没有。举个具体的估算例子。假设某接口峰值 QPS 是 2000平均耗时 50 毫秒那么并发数大约是 2000 × 0.05 100。如果后端有 4 台机器每台的并发约 25那 upstream 的keepalive设成 32 就够了——它描述的是每个 worker 进程到后端的空闲连接保留数不需要设得很大设太大反而会占用后端不必要的连接槽位。这些数字算一遍只要两分钟比上线后半夜被叫起来改配置划算得多。6.2 上线前的验证动作每次调整负载均衡配置我都会做三件事。第一是用真实流量镜像压一遍确认各个节点的请求数比例符合预期误差在 5% 以内算正常。第二是摘节点演练手动把一台机器的进程停掉看流量多久完成切换、有没有报错、上游超时和重试配置是否按预期工作。第三是看一组关键指标每个节点的请求数、平均耗时、错误率、活跃连接数四个指标放在同一张图上一眼就能看出倾斜。第三件事的价值比想象中大。我曾经因为观察到请求数很均匀但某节点的活跃连接数一直是其他节点的三倍而提前发现了一个连接泄漏问题——那个节点上的服务有个接口忘了关数据库连接。如果只看请求数这个问题要等到半个月后连接池耗尽才会暴露。6.3 后续可以继续深挖的方向负载均衡这一层往下走有几个方向值得投入时间。一是自适应限流让每个节点根据自身的实时负载比如 CPU、队列长度动态上报我还能接多少负载均衡器按上报值加权分配这就是所谓的最少未完成请求 服务端反馈的组合方案比纯静态权重精确得多。二是延迟感知的路由把网络 RTT 也纳入决策跨机房场景下能避免把请求发到物理距离远的节点。三是全链路的重试预算控制在调用链的入口处统一分配重试额度避免每一层各自重试导致的放大。不过我也想说一句这些方案的前提是你的基础配置已经没问题了。我见过太多团队在权重还配错、长连接还没开的情况下就去研究自适应调度最后发现问题出在最初的那一页配置上。把upstream块里的每一个参数都搞清楚它到底在做什么比引入任何新框架都更有价值。我个人在排查过的所有负载均衡相关问题里真正需要改算法的不到一成剩下的九成都出在参数配置和长连接复用上。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

MFC迷宫游戏开发实战:从GDI双缓冲到DFS算法,吃透C++桌面编程 2026/9/29 8:25:18

MFC迷宫游戏开发实战:从GDI双缓冲到DFS算法,吃透C++桌面编程

简介:一套基于MFC框架的简单迷宫游戏源码包,面向正在学习C与Windows程序设计的初学者,演示如何利用微软基础类库搭建图形界面并实现核心游戏逻辑。包内完整工程基于Visual C 6.0构建,包含7个头文件与6个C源文件,将窗口…

阅读更多 →
前端报错排查指南:彻底解决TypeError Cannot read property of undefined 2026/9/29 8:25:12

前端报错排查指南:彻底解决TypeError Cannot read property of undefined

[已解决] TypeError: Cannot read property xxx of undefined 的完整排查思路与根治方案周五晚上十一点,运维群里突然弹出一条消息:管理后台的订单详情页白屏了。我爬上服务器看了一眼日志,满屏都是那行让所有前端人血压飙升的红字——TypeEr…

阅读更多 →
用 Trae SOLO 10 分钟做出你的专属简历网站,全程不写一行代码(TaoToken 配置避坑指南) 2026/9/29 8:25:05

用 Trae SOLO 10 分钟做出你的专属简历网站,全程不写一行代码(TaoToken 配置避坑指南)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
从训练到推理:Model-Optimizer 模型优化组件的实践指南 2026/9/29 8:24:46

从训练到推理:Model-Optimizer 模型优化组件的实践指南

Model-Optimizer 是我在做模型性能优化专项时沉淀下来的一个公共组件。名字看着直白,但第一次接触的人很容易理解偏:多数人的第一反应是 PyTorch 里的 Adam、SGD 这类 Optimizer,可我真正做的这套东西覆盖训练和推理两个阶段,解决…

阅读更多 →
用 OpenClaw + Halo CLI 配 TaoToken:AI 自动生成博客的配置文件骨架 2026/9/29 8:24:45

用 OpenClaw + Halo CLI 配 TaoToken:AI 自动生成博客的配置文件骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
含热电联供的智能楼宇群协同能量管理:主从博弈建模与需求响应设计 2026/9/29 8:24:39

含热电联供的智能楼宇群协同能量管理:主从博弈建模与需求响应设计

这几年做综合能源系统优化,被问得最多的一个问题就是:楼宇里都已经装了自己的热电联供、屋顶光伏、储能,为什么还要大费周章搞“智能楼宇群协同能量管理”?单独把每一栋楼自己的那一亩三分地优化好,不行吗?…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉