
做过几年负载均衡线上服务从单机扛流量到多节点横向扩展HAProxy 是我用下来最顺手的一个。它不像某些商业方案那样重也不像自己写 Nginx 配置那样要处理太多边界情况一个 haproxy.cfg 文件就能把四层和七层的流量调度、健康检查、故障摘除全部管起来。这篇文章不聊基础概念而是直接围绕“高级功能配置 实用案例”来拆把我实际部署过程中的配置思路、参数选择和踩坑记录整理出来给正在考虑把 HAProxy 用于生产环境的朋友做一个参考。1. 高级功能整体设计与选型分析1.1 HAProxy 的定位不只是反向代理很多人把 HAProxy 和 Nginx 放在一起比较实际上两者的侧重点有明显差异。Nginx 的优势在于 Web 服务能力、缓存、静态文件处理以及强大的模块生态HAProxy 的核心则集中在高可用负载均衡这件事上它天生就考虑到了代理层的稳定性、连接管理、会话保持、健康检查、故障转移等生产环境刚需。所谓“高级功能”在我理解里可以划分成四层能力第一层是流量调度策略包括多种负载均衡算法和会话保持机制第二层是流量治理能力通过 ACL、请求重写、内容路由等方式实现精细化管理第三层是可靠性与容灾包括自定义健康检查、被动摘除、graceful shutdown第四层是可观测性与调试包括统计页、socket 命令行、日志审计、指标采集。普通配置只关注第一层高级配置则是把后面三层也纳入统一的配置体系中。1.2 为什么选择 HAProxy 而不是自己写网关在设计之初我其实对比过几种方案Nginx upstream、Keepalived LVS、HAProxy 单节点、以及自研 L4 转发服务。最后选择 HAProxy主要原因是它把很多“需要自己实现”的细节内置了。连接管理HAProxy 对 keep-alive 连接、空闲连接、连接复用的处理非常成熟不像自研方案要考虑 TIME_WAIT、ESTABLISHED 状态的清理策略。健康检查机制支持 TCP、HTTP、自定义发送数据包、SSL、LDAP、Redis、MySQL 等多种探测方式配置一次即可自动摘除异常节点。灵活的调度算法内置 roundrobin、leastconn、source、uri、hdr、random 等还能结合 stick table 实现更复杂的路由决策。四层与七层混用同一个 HAProxy 实例可以同时处理 MySQL 的 TCP 转发和 Web 的 HTTP 负载均衡不用拆成多个组件。与 Keepalived 配合成熟生产环境中 Keepalived 提供 VIP 漂移HAProxy 负责流量分发两者分工明确方案非常成熟。从实际运维的角度HAProxy 的配置热加载能力也很关键haproxy -c -f haproxy.cfg校验语法后可以直接 reload对运行中的连接影响远小于重启进程的方式。2. 核心配置细节与性能调优2.1 全局段与性能参数的一次完整调优很多教程把 global 段草草带过实际上这里的参数直接影响高并发场景下的表现。下面是我线上环境使用的一组核心配置包含了关键参数的解释。global log /dev/log local0 debug maxconn 50000 nbthread 4 cpu-map 1 0 cpu-map 2 1 cpu-map 3 2 cpu-map 4 3 tune.bufsize 32768 tune.maxrewrite 16384 tune.ssl.default-dh-param 2048 spread-checks 5 stats socket /var/run/haproxy.sock mode 600 level admin几个重点参数展开说一下。maxconn 50000单实例最大连接数要根据后端服务器容量和 ulimit 同时调整。Linux 下需要同步修改/etc/security/limits.conf中 nofile 限制否则进程无法打开足够的文件描述符。nbthread 4启用多线程。老版本 HAProxy 通过 nbproc 启动多进程但从 1.8 开始推荐使用多线程模式因为多进程模式下各进程之间的 stick table 不共享而多线程天然共享内存状态。多线程模式下要注意cpu-map的绑定把线程固定到物理核避免上下文切换带来的性能抖动。tune.bufsize和tune.maxrewrite这两个是缓冲区配置。bufsize决定每个连接分配的 buffer 大小默认 1638416KB调大到 32768 可以提升大响应体场景的性能但也会增加内存占用内存计算公式约为maxconn × bufsize × 2。maxrewrite是保留给 HTTP 头重写使用的空间ACL、cookie 修改、请求头插入等操作越多需要的空间越大。spread-checks 5这个参数容易被忽略。它表示在 5% 的范围内随机延迟健康检查的发起时间避免多个后端同时被检查造成“检查风暴”。在后端节点很多时没有这个参数会出现所有健康检查请求在同一秒打到后端的情况。stats socket这是运维排查的关键入口后面第 4 节会专门讲用它做什么。2.2 负载均衡算法的选择逻辑HAProxy 的负载均衡算法远不止 roundrobin选错算法会导致流量分担不均甚至把连接全打到一台机器上。我用实际场景来说明每个算法的适用边界。算法调度方式适用场景注意事项roundrobin轮流调度后端性能均衡的 Web 集群配置最简单leastconn选择当前连接数最少的节点长连接服务WebSocket、MySQL短连接场景效果不明显source对源 IP 做哈希需要 IP 级会话保持节点增减会打乱哈希结果uri对 URI 做哈希缓存服务、对象存储相同 URI 请求会打到同一节点hdr对指定 Header 做哈希按用户 ID 分流Header 缺失时需配置 fallbackrandom随机 权重大规模集群、测试环境分布比较均匀但会话保持需配合 stick table强调一下source和hdr的区别。source是对来源 IP 做一致性哈希同一客户端 IP 固定访问同一后端hdr则可以基于Cookie或自定义 Header 来做比如hdr(X-User-ID)这样即使客户端 IP 变化弱网切换、NAT 变化只要用户 ID 不变请求依然落到同一台后端对缓存命中率非常有帮助。我之前维护过一个商品详情服务最初用 roundrobin 调度结果压测时发现每个后端的内存都被缓存数据撑爆。后来改成balance hdr(X-User-ID)并按用户维度做本地缓存缓存命中率从 40% 提升到 85% 以上后端内存压力直接降了 60%。2.3 健康检查的高级配置从能用到好用健康检查是负载均衡的“感知层”配置不好后端节点已经故障了流量仍然被打进去用户侧就会看到超时或 5xx。基础配置一般是optionhttpchk GET /health_check但对于不同协议的端口需要更精细的方式。TCP 层自定义探测backend mysql_servers mode tcp option tcp-check tcp-check connect tcp-check send SELECT 1\r\n tcp-check expect string 1 server db1 192.168.1.10:3306 check inter 3s fall 3 rise 2 server db2 192.168.1.11:3306 check inter 3s fall 3 rise 2这个配置会对 MySQL 端口发送SELECT 1的文本探测能收到包含1的响应才认为节点健康。对比 TCP 只做三次握手的探测方式这种方式能发现“端口活着但服务不可用”的情况。同理Redis 可以用PING\r\n并期待PONG。HTTP 健康检查的状态码与请求路径定制backend web_servers mode http option httpchk GET /api/health HTTP/1.1\r\nHost:\ monitoring.example.com http-check expect status 200-299 server web1 192.168.1.20:8080 check inter 5s fall 3 rise 2这里的核心点健康检查默认走的 Host 可能和后端业务域名不一致有些应用会基于 Host 做路由导致健康检查请求返回 404。所以要显式加上Host头让健康检查请求走正确的虚拟主机配置。fall 3 rise 2的含义是连续 3 次失败才标记为 DOWN连续 2 次成功才标记为 UP。这两个参数不要设置得太小否则网络抖动会导致节点频繁切换状态流量在节点间来回弹跳。被动健康检查Redispatch策略除了主动探测还可以开启被动检查backend web_servers mode http option redispatch option abortonclose retries 3option redispatch表示当后端节点返回 502、503、504 时把请求重新调度到其他节点。主动健康检查是“周期性的体检”被动重试是“发现异常后的补救”两者配合可以让短暂故障对用户几乎无感。2.4 ACL 与内容路由把流量精确分到不同后端ACLAccess Control List是 HAProxy 高级功能中最好用也最容易用错的部分。它的核心逻辑是先用acl声明匹配条件再用use_backend或block等动作响应匹配结果。一个典型的场景把 API 请求和静态资源请求分发到不同的后端集群。frontend web_front bind *:80 mode http acl is_api path_beg /api/ acl is_static path_beg /static/ /images/ /css/ /js/ use_backend api_servers if is_api use_backend static_servers if is_static default_backend web_serversACL 的关键在于理解path_beg、path_end、hdr_reg、src这些匹配方法。我用得比较多的有path_beg /api/路径以/api/开头path_end .php .jsp路径以指定后缀结尾hdr_reg(host) ^api\.Host 头匹配正则src 10.0.0.0/8来源 IP 属于指定网段hdr_sub(cookie) session_idCookie 中包含指定关键字ACL 匹配还有一个容易忽略的点多个条件之间的逻辑关系。if is_api or is_static是或逻辑if is_api is_static是与逻辑需要手动加or/and关键词不要默认以为逗号或空格就是或。3. 实用场景案例落地3.1 案例一基于 AC L 的灰度发布灰度发布是生产环境最常见的需求。HAProxy 可以按来源 IP、Header、Cookie 等条件将部分流量打到新版本节点在验证稳定后再逐步扩大流量比例。配置思路如下新版本和后端旧版本各建一个 backend 组通过 ACL 匹配灰度条件命中则走新版本服务否则走旧版本服务。frontend web_front bind *:80 mode http acl is_gray_user hdr_sub(cookie) gray1 acl is_internal src 192.168.1.0/24 use_backend new_servers if is_gray_user or is_internal default_backend old_servers backend old_servers mode http balance roundrobin server web-1 192.168.1.21:8080 check inter 5s server web-2 192.168.1.22:8080 check inter 5s backend new_servers mode http balance roundrobin server web-new-1 192.168.1.30:8080 check inter 5s server web-new-2 192.168.1.31:8080 check inter 5s注意一个实操细节灰度发布中如果新版本节点健康检查失败HAProxy 会把节点标记为 DOWN如果此时没有显式配置 fallback灰度流量会直接报 503。所以建议在灰度期间开启option redispatch并把fall调大给新版本服务更宽裕的启动时间避免因启动慢被误杀。3.2 案例二MySQL 读写分离与 TCP 四层转发先说背景。我在一段时间内维护过一个低频写、高频读的订单查询服务数据库主从架构已经就位但读写分离的逻辑想放在应用层之外去做避免每个业务服务都要改连接池配置。HAProxy 的四层转发正好适合做这件事。核心思路是主库和从库分别配置 backend用不同端口区分读写入口。应用层连接 3307 端口走从库连接 3306 端口走主库。listen mysql_master bind *:3306 mode tcp option tcp-check tcp-check connect tcp-check send SELECT 1\r\n tcp-check expect string 1 server db-master 192.168.1.100:3306 check inter 5s fall 3 rise 2 listen mysql_slave bind *:3307 mode tcp balance leastconn option tcp-check tcp-check connect tcp-check send SELECT 1\r\n tcp-check expect string 1 server db-slave-1 192.168.1.101:3306 check inter 5s fall 3 rise 2 server db-slave-2 192.168.1.102:3306 check inter 5s fall 3 rise 2这里的负载均衡算法选择了leastconn因为 MySQL 连接属于长连接如果用 roundrobin 调度新建立的连接会轮流分配到各从库但每个连接持续的时间不同会导致某些从库连接数偏高。leastconn 每次调度时选择当前连接数最少的节点能让连接分布更均匀。再补充一个和主从切换相关的注意点当主库节点发生故障时HAProxy 只会把故障节点摘除不会自动将写流量切到从库。我习惯配合 Keepalived 做 VIP 漂移当检测到 HAProxy 主节点不可用时VIP 自动切换到备节点的 HAProxy备节点上配置的仍然是原主库地址。如果原主库真的挂了还需要在 HAProxy 配置里手动调整server db-master ...的地址指向新的主库然后 reload。3.3 案例三HTTP 服务的会话保持与 Stick table后端服务如果是无状态设计可以完全不做会话保持但很多老系统依赖 Session这就必须保证同一用户的请求始终到达同一节点否则用户登录状态会频繁失效。会话保持有两种方式一种是通过cookie指令让 HAProxy 在响应中注入 Set-Cookie后端根据 Cookie 值识别节点另一种是使用stick table记录用户 IP 或用户 ID 与后端节点的映射关系。使用 Cookie 的方式更简单也更可靠但需要后端节点不覆盖客户端 Cookiebackend web_servers mode http balance roundrobin cookie SERVERID insert indirect nocache server web-1 192.168.1.21:8080 cookie s1 check inter 5s fall 3 rise 2 server web-2 192.168.1.22:8080 cookie s2 check inter 5s fall 3 rise 2insert表示如果客户端没有 CookieHAProxy 会在响应中插入indirect表示如果客户端已经带了 Cookie则不会强制覆盖而是直接使用nocache是告诉中间缓存不要缓存 Set-Cookie 头。使用 Stick table 的方式更灵活可以基于多种 key比如用户 ID、URL 参数等。它在 HAProxy 内部维护一张表记录算法算出的 key 与后端节点的映射下次匹配到相同 key 时直接把请求转发到相同节点。backend api_servers mode http balance roundrobin stick-table type string len 64 size 1m expire 30m stick on hdr(X-User-ID) server api-1 192.168.1.40:8080 check inter 5s server api-2 192.168.1.41:8080 check inter 5stype string len 64表示 key 类型是字符串最多取 64 字节size 1m表示这张表最多保存 100 万个条目expire 30m表示条目 30 分钟无访问自动过期。要注意的是stick table 是保存在 HAProxy 进程内存中的reload 后表中数据不会丢失前提是配置中表名和 key 规则一致但如果使用了多进程模式nbproc不同进程之间的 stick table 不共享会导致同一个 key 在不同 worker 进程里落到不同后端。这也是我推荐使用多线程模式的原因之一。3.4 案例四利用 Stick table 实现接口限流接口限流过去一般靠后端应用或网关实现但 HAProxy 也能做一部分而且对后端完全透明不用改业务代码。核心原理使用 stick table 记录单位时间内的请求次数超过阈值后返回 429 或直接断开连接。frontend api_front bind *:8080 mode http stick-table type ip size 100k expire 10s store http_req_cnt(10s) http-request track-sc0 src acl too_many_req sc_http_req_cnt(0) gt 100 http-request deny deny_status 429 if too_many_req default_backend api_servers这一行解释一下store http_req_cnt(10s)表示在表里记录每个源 IP 在 10 秒窗口内的请求次数http-request track-sc0 src表示把当前请求的源 IP 纳入计数器sc_http_req_cnt(0) gt 100检查计数器是否超过 100。这个方案的优点是内存开销小10 万个 IP 的表在内存中只占约 10MB 左右完全可接受缺点是粒度比较粗只能按 IP 维度做全局限流不能区分 API 路径。如果需要更细粒度可以配合 URL 参数一起作为 key比如stick-table type string len 128 size 100k expire 10s store http_req_cnt(10s)加上http-request track-sc0 hdr(X-User-ID)按用户 ID 维度限流。3.5 案例五SSL 终结与 HTTP/2 支持现在线上业务基本都要求 HTTPSHAProxy 可以直接做 SSL 终结将客户端 HTTPS 请求解密后通过 HTTP 转给后端这样后端不需要处理证书和加解密。frontend web_https bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1 mode http option httplog default_backend web_serverscrt指定证书路径注意 HAProxy 要求的证书格式是一个 PEM 文件里面同时包含私钥和证书链顺序是私钥 服务器证书 中间证书。很多初次配置 SSL 的人会在这里踩坑证书格式不对直接报错。alpn h2,http/1.1是开启 HTTP/2 的关键。HTTP/2 对长连接复用、头压缩都有明显优化给 API 接口和高延迟链路带来的性能提升非常可观。但要注意HTTP/2 的流量在 HAProxy 内部会被转成 HTTP/1.1 发给后端所以后端不需要做任何适配。如果有多张证书需要挂载可以配置证书目录而不是单文件bind *:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1目录下每个 PEM 文件对应一个域名HAProxy 会自动根据 SNI 选择合适的证书。这一点对于单 IP 部署多个 HTTPS 域名的场景非常实用。3.6 案例六Keepalived HAProxy 构建高可用负载均衡方案单台 HAProxy 本身就是单点一旦进程挂掉或机器宕机所有后端流量都会中断。高可用方案我使用 Keepalived HAProxy 组合Keepalived 负责 VIP 漂移HAProxy 负责流量分发。Keepalived 侧的核心配置如下两台机器的配置只有 state 和 priority 不同global_defs { notification_email { adminexample.com } } vrrp_script check_haproxy { script /etc/keepalived/check_haproxy.sh interval 2 weight -20 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 virtual_ipaddress { 192.168.1.200/24 dev eth0 } track_script { check_haproxy } }check_haproxy.sh的作用是关键。它检查 HAProxy 进程是否存活如果进程不存在则尝试启动启动失败则exit 1Keepalived 收到非零退出码后会降低本机优先级把 VIP 漂移到备用节点。#!/bin/bash if ! pgrep -x haproxy /dev/null then systemctl start haproxy sleep 2 if ! pgrep -x haproxy /dev/null then exit 1 fi fi exit 0两台 HAProxy 节点上的配置保持一致所有健康检查、负载均衡逻辑都相同VIP 在任何时间只在一台节点上生效应用连接 VIP 即可整个切换过程对客户端基本无感。4. 常见问题与排查技巧实录4.1 配置语法校验与热加载HAProxy 的配置文件一旦写错reload 时服务可能直接挂掉所以在任何变更之前先用-c模式做语法校验这一步绝对不能省haproxy -c -f /etc/haproxy/haproxy.cfg校验通过后再执行 reloadsystemctl reload haproxyreload 过程中 HAProxy 会先启动一个新进程新进程监听了 socket 并接管新连接旧进程会在旧连接全部处理完后退出。这个机制保证了业务不中断但对长连接场景如果某个连接长时间不关闭旧进程会一直占用资源。所以长连接服务重载时可以重启而不是 reloadsystemctl restart haproxy4.2 统计页与 Socket 命令行排查HAProxy 内置的统计页面在定位问题上非常有用。配置里加上listen stats bind *:9000 mode http stats enable stats uri /stats stats refresh 10s stats auth admin:yourpassword stats admin if LOCALHOST打开http://服务器IP:9000/stats就能看到每个 frontend、backend、server 的实时状态当前连接数、请求速率、健康检查状态、上下行流量等。页面中Server列显示节点的 DRAIN、MAINT、DOWN、UP 状态可以参考判断节点是否被摘除、是否被置为维修模式。stats socket提供的是命令行接口运维脚本可以直接调用echo show stat | socat stdio /var/run/haproxy.sock echo show info | socat stdio /var/run/haproxy.sock echo show serv state | socat stdio /var/run/haproxy.sock还可以通过 socket 操作节点状态# 将节点设为维护模式下线不接收新连接 echo set server web_servers/web-1 state MAINT | socat stdio /var/run/haproxy.sock # 重新启用节点 echo set server web_servers/web-1 state READY | socat stdio /var/run/haproxy.sock这种方式在临时下线、排除故障节点时非常高效不需要改动配置文件也不用 reload。4.3 日志配置与常见排查方法默认情况下 HAProxy 日志可能只输出到 console生产环境建议把日志写到独立文件方便排查。以下是配置方法global log /dev/log local0 info log /dev/log local1 notice defaults option httplog log-format %ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hrl %hsl %{Q}r关键字段说明%ci:%cp客户端 IP 和端口%b/%sbackend 名称/server 名称%STHTTP 状态码%Tr响应时间%ac/%fc/%bc/%sc/%rc各阶段的连接数%hrl %hsl请求头大小、响应头大小实际排查中最常见的问题有两个第一连接数飙升但请求量没涨。这种情况首先要看统计页里的Curr Conns是否超过阈值同时用ss -s检查系统连接状态观察是否存在大量 TIME_WAIT。如果是 TIME_WAIT 过多说明开启了短连接模式可以在 defaults 里调整option http-keep-alive和timeout http-keep-alive。第二部分后端节点被误判为 DOWN。遇到这种情况不要急着改fall值先看健康检查的响应内容。可以用 curl 手工模拟健康检查请求比如curl -H Host: monitoring.example.com http://192.168.1.20:8080/api/health确认后端返回的状态码是否符合expect条件。还有一个容易被忽略的次数健康检查会走独立的源 IP如果后端配置了基于 IP 的访问控制或限流健康检查请求可能被后端拒绝导致节点被误判为 DOWN。解决办法是在后端防火墙白名单中放行 HAProxy 的出口 IP或调整 ACL 策略。4.4 常见错误速查表现象可能原因解决方法配置 reload 失败语法错误、PEM 证书格式不对先执行haproxy -c -f haproxy.cfg节点一直 DOWN健康检查 Host 和业务 Host 不一致在option httpchk中显式指定 Host 头请求 503所有后端节点 DOWN 或未配置 default_backend查看 stats 页节点状态用户 Session 不断失效会话保持配置缺失配置 cookie insert 或 stick table一个节点连接数特别高长连接服务配了 roundrobin改用 leastconn监控告警连接数飙升短连接未开启 keep-alive调整 timeout http-keep-aliveSSL 握手报错证书链不完整在 PEM 中补齐中间证书reload 后旧进程不退出长连接长时间保持对长连接场景使用 restart4.5 关于超时时间的配置心得HAProxy 的超时配置是很多人会忽视但又影响体验的关键。我的建议是分成三类设置defaults mode http timeout connect 5s timeout client 30s timeout server 30s timeout http-keep-alive 60s timeout queue 30stimeout connect建立与后端连接的超时。设置太大会让用户长时间卡在加载转圈太小后端负载高时容易误报失败5 秒是相对平衡的值。timeout client/timeout server客户端/后端的读超时。timeout http-keep-aliveHTTP 长连接的空闲时间。如果是 WebSocket 或 grpc 这类长连接场景需要在对应的 backend 中单独调大backend websocket_servers mode http timeout tunnel 3600s server ws-1 192.168.1.60:8080 check inter 10stimeout tunnel一旦设置两端之间的连接会进入“隧道模式”在这个 socket 上不会做任何 HTTP 层超时处理适用于 WebSocket、SSE 等需要长连接的场景。没有配置这一项会导致 websocket 连接在默认 client/server 超时后被断开。5. 关于 HAProxy 后续还能怎么扩展我个人在实际使用中最深的体会是HAProxy 的高级功能不是在配置里堆参数而是要对它的数据流模型有清晰的理解。拿健康检查来说它不只是“出个心跳请求”而是完整的探活机制探活策略和被动重试配合起来才能把故障影响降到最低。拿 stick table 来说它表面是会话保持实际上还能用来做限流、防刷和数据统计。最后再分享一个小技巧线上变更 HAProxy 配置前先用haproxy -c -V -f haproxy.cfg同时完成语法校验和详细输出确认没有警告项再 reload。这个习惯帮我在日常维护中避免了很多次因配置写错导致的线上事故。HAProxy 文档虽然长但每个配置项背后的语义都很清晰值得花时间把haproxy-docs里的 option、timeout、acl 部分精读一遍对排错帮助非常大。