自建负载均衡层这件事,最容易犯的错误是把它当成一个"能跑起来就行"的小角色。很多团队把 HAProxy 装在一台 2 核 4G 的云主机上,配好 backend 就上线,直到某天流量涨上来,连接开始排队、握手延迟飙升、后端明明还活着却被判定为宕机,才发现问题不在 HAProxy 的配置写法,而在当初选的那台服务器根本撑不住这个连接模型。负载均衡器和 Web 服务器的资源消耗曲线完全不同,前者吃的是连接数、包速率和握手算力,后者吃的是业务计算和磁盘 IO。用选 Web 服务器的思路去选负载均衡服务器,几乎一定会选错。
这篇文章不讨论 HAProxy 的入门配置怎么写,那个官方文档已经足够详细。这里要拆的是更前端的一层问题:当你决定自建 HAProxy 负载均衡层,这台物理机或者裸金属服务器,CPU 该怎么选、内存该配多大、网卡该上什么规格、磁盘要不要单独规划、内核参数要在装机当天就改掉哪几个。这些东西选错了,后期几乎只能靠换机器解决,而换机器意味着 VIP 切换、会话中断和一次不小的迁移成本。
要给负载均衡服务器定配置,第一步不是看别人配了多少,而是把 HAProxy 的资源消耗结构拆开。HAProxy 是一个事件驱动、单进程多线程的代理程序,它不为每个连接创建线程或进程,而是把所有连接挂在一个事件循环上,由内核的 epoll 通知哪些连接有数据可读可写。这个模型决定了它的资源消耗有非常鲜明的特征:CPU 消耗集中在"每个连接都要做的那几次事"上,内存消耗则与"同时活着的连接数量"成正比。
CPU 侧的主要消耗点有四块。第一块是连接的建立与拆除,包括 accept、三次握手完成后的状态迁移、以及关闭时的 TIME_WAIT 处理逻辑。第二块是 TLS,如果你把 HTTPS 终止在 HAProxy 上,握手和对称加解密几乎会吃掉绝大部分 CPU,这是所有消耗里最重的一块。第三块是 ACL 规则匹配与 HTTP 头部改写,规则越多、正则越复杂、每个请求要走的判断链越长,单次请求的 CPU 开销就越高。第四块是日志格式化,尤其是开启详细 HTTP 日志之后,拼接一长串字段并写入的成本在极高请求速率下会变得可观。
内存侧的结构相对清晰。每一个并发连接在 HAProxy 内部都需要缓冲区来暂存请求和响应数据,这个缓冲区的大小由 tune.bufsize 控制,在常见默认配置中是 16KB 量级。因为请求方向和响应方向各需要一份缓冲,所以单个连接的缓冲区开销是"缓冲区大小乘以 2"。除此之外,连接本身还有会话结构体、流结构体、过滤器状态等固定开销,开启 TLS 之后每个连接还要额外持有 TLS 会话相关的内存。理解这一点是后面所有内存估算的基础。
还有一项容易被忽略的资源是网络栈。HAProxy 是流量的汇聚点,所有进出的包都要经过这台机器的内核协议栈。这意味着内核的conntrack 表、socket 缓冲区、软中断队列都在这台机器上承受压力。很多时候机器看起来 CPU 和内存都没满,但请求已经开始超时,原因就在网络栈的某个队列被打满了。
事件驱动模型带来一个很实际的后果:HAProxy 不会因为连接数增加而产生线程调度开销或上下文切换爆炸,所以在空闲连接很多、但实际吞吐不高的场景下,它的 CPU 占用可以非常低。一个挂着五万个长连接、每秒只有几百个请求的网关,CPU 可能长期在个位数百分比。这在很多人眼里是"配置买大了"的证据,但实际上内存早就悄悄吃掉了几个 GB。
反过来说,如果这些连接全都是短连接、每秒新建连接数很高,或者全部走 TLS 且没有会话复用,那么 CPU 会在很短的时间内被打满,而内存反而没什么压力。同样是十万并发这个数字,长连接推送型和短连接 API 型对服务器的要求是两种完全不同的配置。这也是为什么在看任何"某某配置能撑多少并发"的说法时,必须先问清楚:这个并发是活跃连接还是总连接,是长连接还是短连接,是明文还是 TLS。
单进程多线程这个结构还决定了纵向扩展的边界。现代版本的 HAProxy 支持通过 nbthread 参数启动多个工作线程,把连接分摊到多个线程上并行处理,从而利用多核。但线程数不是越多越好,它通常应该与可用的物理核心数对齐,超过核心数只会引入调度竞争。而且线程数一旦超过单个 NUMA 节点的核数,跨节点内存访问带来的延迟会抵消并行带来的收益。这些细节都会直接影响 CPU 选型时的核数与主频取舍。
内存估算是整篇文章里最容易被低估的一环。一个通用的估算方法是:最大并发连接数乘以单个缓冲区大小,再乘以 2,得到的就是连接缓冲区的总量。用常见默认配置推演,也就是 maxconn 乘以 16KB 再乘以 2。这里的乘以 2 代表一个连接在请求方向和响应方向各需要一份缓冲。
举个具体的推演过程。假设你把 maxconn 设成 50000,缓冲区保持常见默认的 16KB 量级,那么缓冲区总量就是 50000 × 16KB × 2,约等于 1.6GB(预估,按通用默认值推演)。这还只是缓冲区本身。每个连接的会话结构体、流结构体、以及可能的过滤器与重写规则产生的中间状态,还要在此基础上叠加一个不小的系数。行业内比较常见的经验做法是,在缓冲区总量之外再预留百分之三十到百分之五十的空间给这些结构性开销,同时给操作系统页缓存、内核 socket 缓冲区和进程本身留出余量。
如果开启了 TLS,还要把 TLS 会话的内存算进去。每一条 TLS 连接都要维护加密上下文、读写缓冲区和可能的会话票证,这部分开销在开启会话复用之后依然会随并发数增长。粘性会话表、stick-table 这类用于会话保持和限流的结构,也会按条目数占用内存,条目数通常与并发连接数或者独立客户端数量正相关。这些加起来,实际内存占用高于"缓冲区乘以二"这个朴素结果,是很正常的。
需要明确的是,上面这套换算是按通用默认配置推演的量级估算,不是某个版本的精确数值。不同版本的 HAProxy 在缓冲区默认值、结构体大小上会有差异,实际部署前应当以本机实测为准。但这个方法论本身是可靠的:它给了你一个在采购阶段就能用的、数量级正确的估算工具,而不是靠拍脑袋。
把上面的换算方法套到三个典型档位上,可以得到一组采购时直接可用的量级参考。以下是按常见默认配置推演的估算结果,全部标注为预估,实际值以实测为准。
一万并发这一档,缓冲区总量约为 10000 × 16KB × 2,约 0.3GB(预估,按通用默认值推演)。看上去很小,但加上连接结构体、内核 socket 缓冲、系统自身占用和一定的余量,把内存配到 8GB 是比较稳妥的做法。留出余量的原因不是怕一万连接本身,而是怕突发。业务侧一旦出现连接堆积或者后端响应变慢,连接数可能在几分钟内翻几倍,而这时正是最不能 OOM 的时候。
五万并发这一档,缓冲区总量约为 1.6GB(预估,按通用默认值推演)。叠加结构开销之后的实际占用通常在数 GB 量级,建议配到 16GB 到 32GB。这个档位是最容易出问题的区间,因为很多团队会觉得"不就是几万连接吗,16G 足够了",然后真的把内存用到接近上限,结果一次流量脉冲就把进程拖进内存回收,延迟出现长尾抖动。
十万并发这一档,缓冲区总量约为 3.2GB(预估,按通用默认值推演)。如果开启了 TLS,加上加密上下文和会话缓存,实际占用会更高。这个档位建议 32GB 起步,条件允许配到 64GB。到了十万这个量级,通常已经不建议单机硬扛,而应该考虑横向拆分,把流量按域名、业务或者哈希分散到多台负载均衡器上。单机撑住和维护起来轻松,是两回事。
还有一个判断内存是否够用的实用信号:观察系统在高峰期的可用内存和内存回收行为。如果可用内存长期低于总量的百分之二十,或者内核开始频繁做直接回收,说明这台机器的内存余量已经不足。HAProxy 对延迟极其敏感,一旦内存紧张触发回收,P99 延迟会立刻恶化,而且这种恶化往往先表现为"偶发慢",很难第一时间定位到内存。
除了连接缓冲之外,还有三类结构会实打实地吃内存,而且它们的容量往往是被配置显式指定的,配大了就真的会占掉那么多。
第一类是 stick-table。它常被用来做会话保持、客户端限流、以及基于源 IP 的访问控制。它的条目数是配置里写死的,每个条目还要存若干字段。如果你配了一张百万级条目的表,并且每个条目存了多个计数器,那么这张表本身可能就要占掉几百 MB 到上 GB 的内存(预估,取决于字段数量)。这张表的内存与并发连接数无关,即使当前连接很少,它也会一直占着。
第二类是 TLS 会话缓存与会话票证。开启会话复用能显著降低握手 CPU,这是好事,但缓存本身要占用内存。会话缓存的大小需要在"复用率带来的 CPU 节省"和"缓存占用的内存"之间做权衡。对于移动端占比高、连接复用周期长的业务,把缓存调大收益明显;对于连接极短、客户端分散的业务,大缓存的边际收益会迅速下降。
第三类不直接体现在 HAProxy 的配置里,但一样吃内存:内核为每个 socket 维护的读写缓冲区。这些缓冲区大小由内核参数控制,会随并发连接数线性增长。在十万连接量级下,内核侧 socket 缓冲的占用完全可能达到与 HAProxy 用户态缓冲相当的量级。这也是为什么在做内存估算时,不能只算 HAProxy 自己报的数字,而要把内核侧一并考虑进去。
如果你的 HAProxy 承担 HTTPS 终止,那么 CPU 选型的第一优先级就是为 TLS 服务,其余所有考虑都排在其后。原因很简单:一次完整的 TLS 握手涉及非对称运算,而一次连接只做一次握手,却可能只传输几 KB 数据。对于短连接、高频新建的业务,握手成本会直接决定这台机器能撑多少新建连接速率。
这里有一个非常常见的判断失误:用带宽或者吞吐去估 TLS 场景的 CPU 需求。带宽和握手次数之间没有固定比例。同样是跑满 1Gbps,一边是少量大文件下载,一边是海量小请求,后者的新建连接速率可能是前者的几十倍,CPU 需求完全不在一个量级。评估 TLS 场景的正确指标是"每秒新建握手数",而不是带宽。
降低握手 CPU 的手段有几个层次。最有效的是提高复用率:开启会话复用、会话票证,把 TLS 1.3 的 0-RTT 特性用起来,让客户端尽量复用已有会话,从源头上减少完整握手的次数。其次是选择更省的密钥交换算法和证书类型。再次是把握手卸载到别的层,比如在前面加一层专门做四层转发的设备,或者在后端终止 TLS 让 HAProxy 只做纯转发。这些手段的选择要结合业务形态,不能一概而论。
在密钥交换算法的选择上,ECDHE 与 RSA 的差异是公开的技术常识,但在实际选型里经常被忽略。RSA 的握手运算开销大,且随密钥长度增长显著;同等安全强度下,ECDHE 使用的椭圆曲线运算量要小得多,单次握手的 CPU 开销通常明显低于 RSA,量级差异可达数倍(行业经验值,以实测为准)。这个差异在每秒几千次握手的场景下,直接决定了你是用 4 核跑还是用 16 核跑。
另一个维度是证书签名算法。同样一张证书,用 ECDSA 签名和使用 RSA 签名,在握手时的验证成本不同。如果客户端兼容性允许,选择 ECDSA 证书配合 ECDHE 密钥交换,能显著降低握手侧的单核开销。兼容性是需要评估的前提:部分老旧客户端对 ECDSA 证书链的支持并不完整,在面向广泛终端的业务上,通常需要配置双证书来兼顾。
还需要理解的是,握手的成本结构在 TLS 1.2 和 TLS 1.3 之间也有变化。TLS 1.3 简化了握手往返,减少了握手的轮次,在高延迟链路上对体验的改善尤其明显。启用 TLS 1.3 不只是安全上的升级,在移动网络、跨境链路这类 RTT 较高的场景里,它也是实打实的性能优化。具体到你的客户端分布能拿到多少收益,要以实测为准。
在 CPU 选型上,负载均衡场景的基本结论是优先堆核数,而不是追求最高主频。理由是:HAProxy 的负载是可并行的。连接之间彼此独立,天然可以被分摊到多个线程上处理,而握手、加解密这类操作本身就是可以并行执行的。相比之下,单核主频的提升幅度有限,且高频型号往往核数少、价格陡增,性价比反而不如一颗核心数更多、主频适中的型号。
nbthread 参数的配置原则是让它与可用的物理核心数对齐或者略少。配得比核数多,会引入线程调度竞争;配得比核数少,则有核心闲置。需要注意超线程带来的逻辑核并不等同于物理核,在延迟敏感的场景下,把线程数按物理核数配置通常比按逻辑核数配置更稳。如果需要进一步压榨性能,可以把线程绑定到固定核心上,减少线程在核心间迁移带来的缓存失效。
还有一个现实约束:NUMA。当核心数超过单个 NUMA 节点能提供的数量时,线程访问跨节点内存的延迟会明显上升。在高 PPS 场景下,这种延迟会被放大。可行的做法包括把网卡与处理该网卡中断的核心放在同一个 NUMA 节点上,或者调整内存分配策略。这类调优属于比较靠后的优化层次,但在十万 PPS 以上的场景里,收益是实打实的。
握手之后的数据传输走的是对称加密,常用的就是 AES-GCM 这类套件。对称加密虽然比非对称运算轻得多,但在高吞吐场景下依然是 CPU 消耗的大户。这里的关键变量是 CPU 是否支持 AES-NI 指令集。
AES-NI 是 Intel 和 AMD 在较早期的处理器上就引入的硬件加速指令集,它把 AES 的若干核心运算用硬件电路实现。支持 AES-NI 的处理器在 AES 加解密上的吞吐,与不支持、纯软件实现的处理器相比差距明显(行业经验值,以实测为准)。在选负载均衡服务器时,确认所选 CPU 支持 AES-NI 是一条硬性要求,这个要求在今天绝大多数在售服务器级 CPU 上都满足,但在一些老旧的库存机型上不一定。
验证方法很简单,装机后在系统上检查 CPU 特性标志里是否包含对应字段即可。如果确认支持,还应当确认 HAProxy 链接的加密库能够实际调用到这些指令,而不是走了软件回退路径。这类问题不常发生,但一旦发生,表现就是"CPU 明明很新,吞吐量却上不去",排查起来很费时间。
把视角从 CPU 和内存移到网卡,这里有一个几乎是铁律的判断:在负载均衡场景里,网卡先被打满的往往不是带宽,而是 PPS,也就是每秒包数。原因是每个数据包不论大小,都要经过网卡收发、触发中断、进入内核协议栈、再交给用户态程序,这些处理成本与包的大小关系很小,与包的数量几乎成正比。
以太网上有一个可以直接用的换算关系。在标准以太网帧长下,1Gbps 线速对应的包速率约为 1.48 Mpps(百万包每秒)量级,10Gbps 线速对应的包速率约为 14.88 Mpps 量级(理论值,以实测为准)。这意味着如果你在 1Gbps 链路上跑的全是 64 字节的小包,那么在带宽远未跑满之前,包速率就已经触到了天花板。反过来,如果平均包长较大,比如几百字节,那么同样的带宽对应的包速率会低一个数量级,网卡的压力就小很多。
这个换算对选型的直接指导是:先估算你的业务平均包长,再算出目标带宽下对应的 PPS 需求,然后确认网卡和所在机器的包处理能力能否覆盖,并预留足够余量。很多业务的平均包长偏小,尤其是 API 类、心跳类、短连接类业务,这类业务最容易被 PPS 卡住。判断负载类型的简单方法是看平均包长,如果它在一两百字节量级,就要把 PPS 当作第一指标来看待。
"千兆网卡跑不满千兆"是一个高频抱怨。除去上游限速和链路质量这些因素,机器侧最常见的原因有三个。
第一个是包太小。前面已经说明,小包场景下瓶颈在 PPS 而不在带宽,千兆线速对应的包速率要求在小包场景下很容易超过单核软件处理的能力上限。这时即使带宽监控显示只用了百分之六十,实际已经出现了丢包和重传,表现为吞吐上不去、延迟抖动。
第二个是中断集中在一个核上。传统上网卡的所有收发中断默认由一个 CPU 核心处理,如果网卡不支持多队列或者多队列没有正确开启,那么无论你有多少个核,包处理的软中断都压在那一颗核上。这颗核被打满之后,其余核心再闲也没用。表现特征是系统整体 CPU 使用率不高,但某一颗核长期百分之百,同时伴随接收队列丢包。
第三个是队列和缓冲区设置偏小。网卡的环形缓冲区、内核的 netdev backlog 队列都有默认大小,在高突发场景下默认值可能不够,导致瞬时丢包。这类丢包在监控上表现为间歇性的、与流量脉冲同步出现的错误计数增长,调整队列长度通常能缓解。
解决单核瓶颈的标准手段是多队列加中断亲和性。RSS 是网卡硬件层面的多队列能力,它根据数据包的哈希把不同流分到不同的硬件队列,每个队列绑定一个中断,从而让多个 CPU 核心分担包处理。开启 RSS 之后,还要确保中断被正确地分散到不同核心上,这可以通过配置中断亲和性来实现,也可以依赖系统的中断平衡服务自动完成。
如果网卡硬件不支持多队列,或者虚拟环境下无法使用硬件多队列,那么可以用 RPS 在软件层面做类似的事情:由内核根据包的哈希,把包处理的工作分发到多个 CPU 上。RPS 是软件实现,效率不如 RSS,但在无法使用硬件多队列的环境下,它依然能带来明显的改善。
配置时有一个容易踩的细节:队列数量与线程数量的匹配。如果网卡开了 8 个队列,而 HAProxy 只跑了 4 个线程,或者反过来,都可能出现负载不均。更理想的状态是让中断亲和、RPS 分发的目标核心集合,与 HAProxy 工作线程绑定的核心集合保持一致或高度重叠,并且在同一个 NUMA 节点内。这样包从网卡到用户态的整条路径都在局部完成,缓存命中率最高。
另外两项值得关注的网卡特性是 TSO/GSO 和 GRO/LRO。它们通过让协议栈处理更大的数据单元来降低每包的固定开销,对吞吐有明显帮助。但在某些对延迟极其敏感、或者需要精确按包处理的场景下,需要评估它们带来的副作用,例如可能掩盖真实的包速率统计。是否启用、如何取舍,要结合业务特点实测决定。
从程序逻辑上说,HAProxy 是一个几乎不写盘的进程。它不缓存响应体,不持久化连接状态,正常运行时的磁盘写入几乎为零。正因为如此,很多人在给负载均衡服务器选磁盘时非常随意,直接沿用系统盘,装完系统就上线。这个做法在开启详细日志之后会变成隐患。
HAProxy 的日志能力非常细致,可以记录每个请求的时间戳、客户端地址、后端选择、各类计时字段、状态码、字节数、以及自定义的头部内容。这套日志在排障时价值极高,但它的写入量同样可观。在每秒数万请求的量级下,开启详细 HTTP 日志意味着每秒数万行的写入,按每行几百字节计算,一天下来就是几十 GB 甚至更多(预估,取决于字段数量与请求速率)。如果这些写入和系统盘、和其他服务共用一块盘,磁盘 IO 压力会反过来影响整个系统的响应。
更麻烦的是同步写入。如果日志是以同步方式落盘的,那么每次写日志都要等待磁盘 IO 完成,在高请求速率下这会直接拖慢请求处理,把磁盘的延迟注入到请求的延迟里。表现为平均延迟和 P99 延迟同时恶化,而且机器看起来 CPU 和内存都很闲,非常具有迷惑性。
应对日志写放大的思路有三条,实际部署中通常组合使用。
第一条是让日志走独立的盘或者独立的写入路径。把日志盘与系统盘分离,可以避免日志写入影响系统本身的运行,也可以避免日志占满根分区导致系统异常。从容量规划的角度,应当按峰值请求速率乘以单行大小乘以保留天数来估算日志盘的容量需求,并留出数倍余量。
第二条是使用异步写入。通过 rsyslog 之类的日志服务接收 HAProxy 的日志,让 HAProxy 只负责把日志送出去,由日志服务负责缓冲、刷盘和转发。这样磁盘 IO 的抖动不会直接传导到请求路径上。同时,日志服务还可以配置队列、限速和丢弃策略,在极端突发时优先保住业务而不是保住日志。这是非常关键的一点:日志是可以丢的,请求是不可以慢的。
第三条是采样与分级。不是所有流量都需要记录详细日志。可以在正常情况下只记录错误请求和慢请求,或者按一定比例采样,在排障窗口内再临时打开全量日志。这样既能把日常写入量压下来,又能在需要的时候拿到足够的信息。配合日志转轮与定期归档,磁盘容量就完全可控了。
默认的内核参数是为通用场景设计的,不是为高并发网关设计的。装机之后至少要检查并调整下面这几项,每一项背后都有明确的踩坑场景。
第一项是文件描述符上限,包括系统级的 fs.file-max 和进程级的 ulimit nofile。每个连接至少占用一个文件描述符,HAProxy 与后端之间的连接还要再占用一个。把 maxconn 设成十万,文件描述符却还是默认的几千,结果就是连接建立到一定数量后开始报"too many open files",而且这个错误在日志里出现时往往已经影响线上了。进程级的限制还要在服务的启动配置里显式设置,只在 shell 里改 ulimit 对通过服务管理器启动的进程通常不生效。
第二项是 net.core.somaxconn 和 net.ipv4.tcp_max_syn_backlog。前者限制了监听套接字接受队列的长度,后者限制了半连接队列的长度。在新建连接速率很高的场景下,默认值偏小会导致队列溢出,表现为连接被丢弃或者握手超时。这类问题在压测时很容易复现,但在日常流量下不显形,属于典型的"平时没事,大促时崩"的隐患。
第三项是 net.netfilter.nf_conntrack_max。如果系统上启用了连接跟踪,无论是因为防火墙规则还是因为其他模块依赖,每一条连接都会在 conntrack 表里占一个条目。表满之后新连接会被直接丢弃,而且日志里可能只留下一行不起眼的提示。在负载均衡器上,如果确实不需要连接跟踪,可以通过规则让业务流量不走跟踪,或者把表调到足够大,同时相应调整哈希桶的数量以避免单桶链表过长。
第四项是 net.core.netdev_max_backlog,它决定了内核在软中断处理之前能缓存多少包。在高 PPS 突发下这个值偏小会直接导致丢包。与之相关的还有网卡环形缓冲区的大小,可以通过网卡工具调整。这两项通常要一起看。
HAProxy 不只是接连接,它还要向后端发起连接。每一次向后端建连,都要占用一个本地端口。这就带来了一个常被忽略的上限:本地端口范围。
在常见默认配置中,本地端口范围大约只有三万个左右(以实际系统值为准)。如果你的业务是短连接、且并发很高,那么在后端连接频繁建立和关闭的过程中,本地端口可能被耗尽,表现为 HAProxy 到后端的连接建立失败。这个问题在后端数量少、单台后端承载连接多的时候尤为突出。解决方向包括扩大本地端口范围、启用端口复用相关的内核选项、以及尽量使用长连接回源。
TIME_WAIT 是与之相伴的另一个话题。主动关闭连接的一方会进入 TIME_WAIT 状态,持续一段时间。在高并发短连接场景下,TIME_WAIT 状态的连接数量会非常庞大,它们占用内存、占用端口、也占用连接跟踪表的条目。可以通过调整相关内核参数来加快回收或限制数量,但必须谨慎:过度激进的回收可能带来序号回绕之类的问题,在 NAT 环境下尤其危险。
这里有一个值得单独提醒的历史坑:早期常用的某个 TIME_WAIT 快速回收参数,在较新的内核版本中已经被移除,因为它会在 NAT 场景下导致连接异常。如果你还在照搬几年前的调优笔记去设置它,那么要么设置无效,要么在某个内核版本上直接报错。正确做法是理解 TIME_WAIT 的成因,从连接模型上解决,比如尽量使用长连接回源、让合适的角色来主动关闭连接,而不是靠内核参数硬压。
三者都能做负载均衡,但它们的定位差异很大,选择错误会在后期付出很高代价。这里只讲分界,不讲优劣。
LVS 工作在四层,尤其是 DR 模式下,它只做转发而不改写数据包内容,响应可以由后端直接返回给客户端,不经过负载均衡器。这带来的结果是性能极高,能够承载非常大的入口流量,而且它几乎不消耗连接级别的内存。代价是它的能力边界很窄:七层信息它看不到,健康检查与后端摘除的能力相对弱,复杂的流量调度逻辑难以实现。它适合放在最外层,作为一个纯粹的大流量入口。
Nginx 的七层能力很强,生态成熟,配置习惯被广泛接受,而且它同时还能承担 Web 服务、反向代理和缓存的角色。对于已经以 Nginx 为主的技术栈,用 Nginx 做负载均衡是很自然的选择。但在纯转发场景下,Nginx 每个连接的内存开销通常高于 HAProxy(行业经验值,以实测为准),在极长连接的场景下这个差异会被放大。它的优势在于一专多能,劣势在于专注度。
HAProxy 的定位是四层和七层都强,同时又把连接管理这件事做到了很细的粒度:精细的健康检查、成熟的统计页面、丰富的调度算法、灵活的 ACL 与流量控制。它非常适合作为业务层的统一入口,尤其是当你的流量调度逻辑比较复杂、对后端摘除的及时性要求比较高的时候。它的统计与可观测能力也是运维日常依赖最多的部分之一。
一个在实践中被广泛采用的组合是分层的:外层用 LVS 承担超大流量的入口转发,内层用 HAProxy 做七层调度与精细管理。这样既拿到了 LVS 的转发性能,又拿到了 HAProxy 的管理能力。是否需要分层,取决于你的量级和复杂度,量级不到的时候,单层 HAProxy 反而更容易维护。
负载均衡器是单点故障风险最高的组件,因为它一旦挂掉,后面所有健康的后端都不可达。所以高可用不是可选项。最常见的方案是两台机器跑 Keepalived,通过 VRRP 协议协商出一个虚拟 IP,主节点持有这个 VIP,备节点监听主节点的心跳,主节点失联时接管。
脑裂是这套机制最主要的失效模式。当两台机器之间的心跳链路中断,但两台机器本身都还活着、都还能对外提供服务时,双方都认为对方已死,于是都去抢占 VIP。结果是同一个 IP 出现在两台机器上,ARP 缓存混乱,流量时通时断,而且这种故障的表现非常诡异,排障时很容易误判成网络问题。应对脑裂需要多重手段:心跳链路做冗余,不要只依赖单一网络路径;引入第三方仲裁,让节点在失去心跳时能判断是"对方死了"还是"我和外界断了";以及在检测到冲突时能被及时告警。
部署位置同样关键。两台机器如果放在同一个机架、接同一台交换机、共用同一路电源,那么这个机架的任何一个故障都会同时打掉主备两台,高可用形同虚设。最低要求是跨机架部署,更稳妥的是跨可用区。跨可用区会带来额外的延迟和带宽成本,需要在可用性目标和成本之间权衡,但对于核心入口,这个成本通常是值得的。
VIP 漂移还有一些细节要注意。云环境或者某些网络环境下,组播可能不可用,这时需要把 VRRP 配置成单播模式并显式指定对端地址。另外,是否启用抢占、抢占前的等待时间如何设置,会影响故障恢复的节奏。不恰当的抢占策略可能导致 VIP 在两台机器之间反复横跳,每次横跳都会带来一段连接中断。
健康检查配置看起来只有几个数字,但它直接决定了故障时被影响的请求量,以及正常时被误摘除的概率。这两个目标是相互冲突的:检查越频繁、失败阈值越低,故障发现越快,但也越容易被瞬时抖动误判。
检查方式的选择比频率更重要。只做 TCP 端口检查,只能确认后端进程还在监听,无法确认它是否还能正常处理业务。一个典型的故障场景是:后端应用线程池耗尽、数据库连接打满,端口依然能连上,但请求进去必然超时。这时 TCP 检查会认为它健康,流量继续打进去,用户持续报错。改用 HTTP 检查,并且验证返回的状态码甚至响应内容,才能真正反映后端的可用状态。
在取值上,常见的思路是把检查间隔设在秒级,连续失败若干次才判定为不可用,连续成功若干次才恢复。具体数字要跟着业务的容忍度走:对错误率极其敏感的业务,可以把间隔调短、失败次数调少,代价是更容易被抖动误伤;对稳定性要求高于极致切换速度的业务,则把阈值放宽,宁可多错几秒也不要频繁摘除导致剩余节点雪崩。
还有两个常被忽略的机制。一个是慢启动:刚恢复的节点立刻承接全量流量,很可能被瞬间压垮再次下线,让它在一个时间窗内逐步增加权重会更安全。另一个是健康检查本身的隔离:检查请求要走独立的路径或者独立的端口,避免和业务流量争抢已经紧张的后端资源,否则会出现"因为忙而被判定为死,因为死了而更忙"的恶性循环。
下面是三档参考配置,全部标注为预估配置,用于采购阶段的量级参考,不构成任何报价或承诺,具体机型与可用配置以实际咨询确认为准。表格之后补充几点选择逻辑。
| 业务档位 | 并发连接量级(预估) | CPU 建议 | 内存建议(预估) | 网卡与带宽 | 典型部署形态 |
|---|---|---|---|---|---|
| 小中型业务 | 1 万级 | 4 至 8 核,需支持 AES-NI | 8 至 16 GB | 1G 或 2G 专属带宽,建议双网口 | 单机运行加冷备,或双机主备 |
| 中大型业务 | 5 万级 | 8 至 16 核,主频适中优先核数 | 16 至 32 GB | 10G 网卡,按 PPS 预留余量 | 双机 Keepalived 主备,跨机架 |
| 超大入口 | 10 万级及以上 | 16 至 32 核或更多,注意 NUMA | 32 至 64 GB 及以上 | 10G 或 25G 网卡,开启多队列 | 四层打底加七层分层,或集群横向扩展 |
| 纯四层超量转发 | 以 PPS 为指标估算 | 核数为主,单核要求相对低 | 8 至 16 GB 即可 | 10G 或 25G,重点看 PPS 能力 | LVS DR 模式外层,HAProxy 内层 |
表格里的数字是量级而不是精确解。有几个调整方向值得说明。如果业务以 TLS 为主并且新建连接速率高,那么 CPU 应当在对应档位上向上加一档,因为握手是最吃 CPU 的部分。如果业务以长连接推送为主,那么内存应当向上加一档,CPU 反而可以保持。如果平均包长很小,那么网卡和中断处理的优先级要高于一切。
另一个容易被低估的是冗余系数。表格里的量级建议通常已经包含了一定余量,但如果你的业务有明显的脉冲特征,比如整点开抢、定时任务触发、或者依赖外部推送,那么应当在峰值而非均值的基础上做规划。负载均衡器没有"降级"这个选项,它要么扛住,要么整个入口不可用。
最后一点是关于横向扩展的判断。当单机配置已经加不动了,或者单机故障的爆炸半径已经大到不可接受时,就应当考虑横向拆分而不是继续堆配置。常见的拆分维度包括按域名拆、按业务线拆、按地域拆。拆分之后每一台的规格可以降下来,故障影响面也变小,代价是运维复杂度上升。这个取舍点通常出现在十万并发这个量级附近。
下面这些条目来自真实的工程实践,每一条都对应一种具体的故障表现。它们不是理论风险,而是反复出现的坑。
第一条,只按带宽估,不按 PPS 估。这是最普遍的错误。带宽监控显示还有余量,但包速率已经打满,表现出来的是丢包和延迟抖动,而不是带宽告警。评估时一定要把平均包长算进去。
第二条,忘记调文件描述符上限。把 maxconn 设得很大,ulimit 却还是默认值,连接数到一定量级后开始报错。而且进程级的限制必须在服务启动配置里设置,只在命令行临时修改对服务进程无效。
第三条,把 maxconn 设得远远超过内存承受能力。maxconn 是个配置值,不是能力值。设成十万而内存只有 8GB,在连接真的堆到十万时,进程会被内存压力拖垮,甚至被系统终止。正确做法是先按内存算出能承受的连接数上限,再反推 maxconn。
第四条,日志同步写拖垮吞吐。详细日志加上同步落盘,在高请求速率下会把磁盘延迟注入请求路径。正确做法是异步写入、独立日志盘、必要时采样。
第五条,健康检查只用 TCP。端口能连上不代表服务可用,线程池耗尽、依赖打满的情况下 TCP 检查完全失效。要用 HTTP 检查并验证响应内容。
第六条,单臂部署时回程路径不对称。请求从负载均衡器进去,响应却从另一条路径回来,中间设备看不到完整的会话,可能直接丢弃。部署前必须确认往返路径一致。
第七条,忽略了 TLS 会话复用导致握手打爆 CPU。没有开启复用,或者会话缓存配得太小,导致本可以复用的连接反复做完整握手,在移动端占比高的业务上这个代价尤其大。
第八条,没有做连接数限流被突发冲垮。来自单个源或者单个接口的突发流量,会在没有限流的情况下吃光连接配额,影响所有其他业务。应当在入口侧按源、按接口设置连接与速率限制。
第九条,主备两台放在同一机架或同一可用区。机架断电、交换机故障会同时打掉两台,高可用失效。至少跨机架,重要业务考虑跨可用区。
第十条,VRRP 心跳只有单一路径。心跳链路一断就脑裂,且脑裂的表现极易被误判为网络故障。心跳要做冗余,并引入仲裁机制。
第十一条,照搬过时的内核调优笔记。某些参数在新内核上已被移除或行为改变,照搬要么无效要么有害。任何内核参数调整都应该先理解它解决什么问题,再确认在当前内核上是否仍然适用。
第十二条,nf_conntrack 表被打满。表满之后新连接静默丢弃,日志提示不明显,排查成本高。要么调大,要么让业务流量不走跟踪。
第十三条,本地端口范围耗尽。短连接高并发回源时,本地端口可能不够用,表现为到后端的连接建立失败。要扩大端口范围并尽量使用长连接回源。
第十四条,nbthread 与核心数不匹配。线程数多于物理核引入调度竞争,少于物理核则浪费资源,跨 NUMA 节点还会带来内存访问延迟。
第十五条,网卡多队列没开或者中断集中在一颗核。表现为总 CPU 不高但单核打满、接收队列丢包。要开启 RSS 并正确配置中断亲和性,条件受限时用 RPS。
第十六条,没有在上线前做峰值压测。配置是不是够,只有压测能回答。压测要覆盖连接数、新建速率、PPS 三个维度,而不是只测吞吐。压测结果才是一切容量判断的依据,其余都是估算。
配置买对了只是第一步,上线前还需要一轮校验,把估算值换成实测值。压测的顺序建议按维度拆开做,而不是混在一起跑一次。
第一个维度是并发连接数。目标是验证在目标连接数下内存占用是否符合估算,并且延迟没有因为内存压力而恶化。压测时要同时观察 HAProxy 自身的内存占用、系统可用内存、以及内核侧 socket 缓冲的占用,三者相加才是真实成本。
第二个维度是新建连接速率。目标是验证 CPU 在目标新建速率下的余量,尤其是开启了 TLS 的场景。这个维度最容易暴露握手相关的问题,比如会话复用没生效、证书链配置不合理、或者加密库没有走硬件加速。
第三个维度是包速率。目标是验证网卡、中断和内核队列在目标 PPS 下是否稳定,观察有没有丢包、软中断是否集中在单核、队列有没有溢出。这个维度在小包业务上往往是第一个触顶的。
压测之外,还要做一次故障演练:手动停掉主节点,观察 VIP 漂移时间、已建立连接的中断情况、以及备节点能否承接全量流量。演练还要覆盖脑裂场景,验证仲裁机制是否真的生效。没有演练过的高可用方案,在真正故障时的表现是不可预测的。
一万网络(天下数据品牌)深耕 19 年,成立于 2007 年,在服务器租用与托管领域积累了大量面向高并发场景的交付经验。对于自建 HAProxy 负载均衡层的客户,可提供的方向包括:按连接模型匹配 CPU 核数与内存规格的裸金属与物理服务器、支持多队列的万兆网卡配置、独立的日志盘规划、以及面向中国香港等节点的优化回程线路与专属带宽形态。具体机型、可用配置与网络方案,以业务顾问根据实际情况确认为准。
在实际选型沟通中,有价值的不是报出一个配置数字,而是把业务形态讲清楚:平均包长是多少、长短连接的比例如何、是否终止 TLS、峰值新建连接速率大概在什么量级、故障切换的容忍时间是多少秒。这几个数字一旦明确,配置区间就会收敛得非常快。反过来,如果只说"我要一台高配的负载均衡服务器",那么选出来的配置大概率是错的,要么是浪费,要么是不够。
最后要强调的是,本文给出的所有量级数字都是按通用默认配置推演的估算,用于采购阶段的容量判断,不构成任何性能承诺。真实 capacity 只能由压测给出。选型阶段多花一点时间把连接模型、包模型和握手模型算清楚,比上线之后再换机器要划算得多。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品