HAProxy七层代理实战:配置、ACL路由与会话保持全解析

HAProxy七层代理实战:配置、ACL路由与会话保持全解析 说实话网上聊 HAProxy 的文章不少但大多是拿官方文档翻译一遍配置贴出来就完事很少有人把“为什么要这样配”“踩过哪些坑”讲清楚。我自己从最开始用 Nginx 做负载均衡到后来因为会话保持和四层转发需求被迫换到 HAProxy折腾了一两周才把一套相对靠谱的七层代理方案跑稳。今天干脆把这段时间的实验过程、踩坑记录和最终沉淀下来的配置思路一次性整理出来。这篇文章适用于正在选型负载均衡方案的运维、后端开发以及那些被“会话保持”和“请求路由”折磨过的兄弟——看完至少能少走一半弯路。1. 为什么偏偏选 HAProxy 做七层代理选型这件事没有最好只有最合适。先把背景说清楚当时我负责的项目是一个标准的 Web 应用集群前面需要统一的流量入口后端有三台应用服务器后面还挂了一组 Redis 做 Session 存储。需求拆解下来其实就三条HTTP 层面的负载均衡、按 URL 路径做灰度发布、以及长连接场景下的稳定性。当时手头可以用的是 Nginx 和 HAProxy所以在动手之前我把两者的定位和边界仔细捋了一遍。1.1 七层代理和四层代理究竟差在哪很多新手容易把“七层”和“四层”搞混其实一句话就能讲明白四层代理工作在传输层它只认识 IP 和端口数据包到了它手里它看一眼目标地址和端口就直接转发根本不关心包里面装的是 HTTP 还是 HTTPS。而七层代理工作在应用层它能把 HTTP 请求完整读出来看得到 URL、Header、Cookie甚至请求体内容然后基于这些信息做路由决策。打个比方四层代理就像一个小区门口的门卫只要你的门禁卡能刷开他就放你进去根本不管你是去 3 号楼找人还是去地下车库取车。七层代理则更像前台接待他会问你找谁、去哪个部门、有没有预约然后根据这些信息把你带到对应的人面前。这个区别直接决定了 HAProxy 能做很多 Nginx 也能做、但 LVS 这类四层方案完全做不了的事情。比如同一个域名下/api/开头的请求转发到后端 A 集群/static/开头的请求转发到后端 B 集群再比如根据请求头里的User-Agent判断是手机端还是 PC 端然后分发到不同的服务。这些在四层代理上是完全不可能实现的。1.2 HAProxy 和 Nginx、LVS 的取舍先说说为什么我没有继续用 Nginx。Nginx 做反向代理确实很强配置也灵活但有一个痛点我一直不太满意它处理长连接和连接耗尽问题的表现不如 HAProxy 稳定。我自己实测过在高并发长连接场景下HAProxy 对连接的管理更精细内存占用更可控而且它专门为代理场景设计了连接池和健康检查机制这一块明显更专业。再来说 LVS。LVS 是纯四层方案性能确实无敌但它有个致命短板不知道请求内容没法做基于 URL 的转发和修改。而且 LVS 的配置和维护门槛高对普通业务团队来说不够友好。我见过不少团队用 LVS Keepalived 搭了一套结果每次加后端节点都要小心翼翼地改配置运维成本不低。HAProxy 正好卡在中间性能比 Nginx 更强尤其在连接管理上功能比 LVS 丰富太多七层能力、ACL、强大的健康检查、内置统计页面。所以如果你的需求是“HTTP/HTTPS 流量入口 灵活路由 会话保持”HAProxy 是性价比最高的选择没有之一。2. 七层代理的核心机制HAProxy 到底在做什么这一节不讲废话直接拆解 HAProxy 在处理一个 HTTP 请求时内部到底发生了什么。很多人在配置 HAProxy 时一脸懵根本原因不是不会写配置而是不知道每个配置项对应的是哪个环节。2.1 一个 HTTP 请求在 HAProxy 里的完整旅程当客户端发来一个 HTTP 请求HAProxy 的处理流程大致可以分成四步第一步Kernel 接收请求TCP 三次握手完成后请求数据到达 HAProxy 所在机器的网卡。此时 HAProxy 会选择一个空闲的连接线程来接管这个连接这个选择过程受nbthread和maxconn参数影响。第二步解析请求HAProxy 会读取并解析 HTTP 头部包括请求行、Header 字段、Cookie 等。注意HAProxy 默认是解析完整个头部才做转发决策所以它能看到完整的信息来做路由。第三步匹配 ACL 和规则这是 HAProxy 七层能力的核心。它会拿解析出来的内容依次匹配配置里的 ACL 规则一旦命中就根据对应的use_backend或reqiset等指令来决定将请求转给哪个后端组。ACL 的匹配顺序很关键HAProxy 是按配置文件中的顺序从上到下依次匹配命中即停。所以匹配范围小的规则一定要写在前面否则会被前面的宽泛规则吞掉。第四步建立后端连接并转发HAProxy 根据后端服务器组配置的负载均衡算法从健康检查通过的服务器列表里挑一台建立连接把请求转发过去然后将后端的响应原路返回给客户端。整个过程对客户端来说是完全透明的。这个流程看起来简单但每一步都有很多细节可以优化。比如第三步里ACL 写的不好路由就会乱第四步里连接池配置不对高并发下就会大量 TIME_WAIT。2.2 负载均衡算法选型不是所有场景都适合 roundrobinHAProxy 内置的负载均衡算法有十几种但我实际生产环境常用的就那几种这里直接给结论。roundrobin是最基础的轮询算法每个请求按顺序轮流分发到后端服务器。它适合后端服务器配置相同时的场景。但注意如果后端服务器的性能差异比较大——比如一台是 4 核 8G另一台是 2 核 4G——轮询会导致性能差的机器成为瓶颈。这种情况下应该用weighted roundrobin也就是给两台机器配不同的权重比如server web1 192.168.1.10:8080 weight 2让流量按权重比例分发。leastconn是最少连接算法新请求会分发给当前活跃连接数最少的后端服务器。这个算法非常适用于长连接场景比如 WebSocket、消息推送服务。因为这些连接一旦建立会长时间占用如果用轮询最先被请求到的那台机器可能已经堆了几千个连接而其他机器却很闲。最少连接算法就能有效避免这种情况。source是源地址哈希算法同一客户端的请求会始终分发到同一台后端服务器。如果你不想用 Redis 做集中式 Session用这个算法可以达到简单的会话保持效果。但要注意它必须在 LVS 那种四层入口层做或者保证客户端 IP 不变化的前提才行。如果客户端经过多层 NAT源地址哈希的效果就会大打折扣。选择算法没有银弹我的经验是短请求高并发用 roundrobin 配合权重长连接或者需要会话保持用 leastconn 或 source需要灰度时靠 ACL 强制指定后端。如果后端性能差距大就手动调权重不要幻想算法能自动发现性能差异。2.3 ACL 才是七层代理的灵魂HAProxy 的 ACL 是它区别于 Nginx 和 LVS 的最大特色也是七层代理能力的集中体现。简单理解ACL 就是一组“条件表达式”用来匹配请求的特征。ACL 的语法是acl 名称 测试方法 参数。其中测试方法有很多种最常用的几个acl is_api path_beg /api/匹配请求路径是否以/api/开头。acl is_static path_end .jpg .png .css匹配路径是否以某几个后缀结尾。acl is_mobile hdr_sub User-Agent iPhone Android在User-Agent头里匹配是否包含特定字符。acl has_login_cookie req.cook_sub login_token匹配 Cookie 里是否包含指定字段。这些 ACL 定义好之后配合use_backend指令就能实现路由acl is_api path_beg /api/ acl is_static path_end .jpg .png .css use_backend api_servers if is_api use_backend static_servers if is_static这套机制的意义在于HAProxy 不再只是盲目转发流量而是变成了一个“智能调度器”。灰度发布就是靠这个实现的——给新版本单独起一组后端然后通过 ACL 将带有特定 Header 或 Cookie 的请求导流到新版本服务器上逐步放量。我后面实验部分会专门演示这个用法。3. 完整实验从零搭建一套七层代理前面把原理讲透了现在是动手环节。我的实验环境很简单一台装了 HAProxy 的 CentOS 7 机器作为代理入口三台后端服务器分别跑着不同的服务用来模拟负载均衡和路由场景。为了让实验可复现我把整个流程分成四步走。3.1 实验环境准备我这边后端用了三台虚拟机IP 分别规划为192.168.56.101跑 Nginx返回内容标明Server-1192.168.56.102跑 Nginx返回内容标明Server-2192.168.56.103跑一个简单的 Node.js 服务模拟 API 服务HAProxy 安装很简单CentOS 上直接执行yum install haproxy -y装完先确认版本haproxy -v我在实验时用的是 HAProxy 2.0 以上的版本有些配置项在老版本里不兼容后面会提到。3.2 编写第一份 haproxy.cfgHAProxy 的配置文件默认在/etc/haproxy/haproxy.cfg分成global、defaults、frontend、backend等段落。第一份配置我写得很保守目的就是先把流量转起来global log 127.0.0.1 local2 chroot /var/lib/haproxy pidfile /var/run/haproxy.pid maxconn 4000 user haproxy group haproxy daemon nbthread 4 defaults mode http log global option httplog option dontlognull option http-server-close timeout connect 5000ms timeout client 50000ms timeout server 50000ms frontend web_front bind *:80 default_backend web_servers backend web_servers balance roundrobin server web1 192.168.56.101:8080 check inter 3s fall 3 rise 2 server web2 192.168.56.102:8080 check inter 3s check这段配置有几个关键点说一下。mode http指定了工作模式为七层 HTTP 模式这是实现七层代理的前提。option http-server-close表示客户端和后端服务器之间是短连接模式即处理完一个请求后 HAProxy 会主动关闭后端连接减少资源占用。如果你的业务是长连接这里要改成option http-keep-alive。timeout系列参数必须在生产环境仔细调我见过太多人根本没配超时结果默认值把系统坑了。connect 是 HAProxy 向后端发起连接的超时时间client 是客户端连接的空闲超时server 是后端连接的空闲超时。实验环境我给了 5 秒和 50 秒生产环境要根据业务和网络状况调整。check inter 3s fall 3 rise 2是健康检查配置每 3 秒检查一次后端服务器连续失败 3 次标记为宕机连续成功 2 次标记为恢复。这里的fall和rise直接影响故障转移的速度。我习惯把 fall 设置成 2 或 3因为一次失败往往是网络抖动设置成 1 会导致后端服务器一有波动就被摘掉反而引发雪崩。启动服务systemctl start haproxy systemctl enable haproxy然后用浏览器或者 curl 访问 HAProxy 所在机器的 80 端口多刷几次会看到请求交替落到 Server-1 和 Server-2 上。3.3 热重载和动态配置HAProxy 有个非常实用的特性配置热重载。修改配置文件后不需要重启进程执行下面的命令即可平滑加载新配置haproxy -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)-sf参数告诉 HAProxy 先让旧进程优雅退出再启动新进程。这个过程不会中断现有连接对业务完全无感。不过要注意HAProxy 的热重载并不是严格意义上的动态配置。它实际上是先启动一个新进程接管监听端口然后让旧进程处理完手头的连接后退出。这个机制已经够用了但如果你想在不重载的情况下修改后端服务器列表需要 HAProxy 2.0 以上版本配合 Data Plane API 或者使用runtime API。实验阶段用热重载就够了。我在实际部署中有一个习惯每次修改配置先执行haproxy -c -f /etc/haproxy/haproxy.cfg做语法检查确认没问题再热重载。这个习惯帮我避免了很多次因为配置写错导致的线上事故。3.4 核心实验基于路径的灰度发布与分流现在来做这个实验最有意思的部分用 HAProxy 实现一个简单的灰度发布。假设我的后端有三组服务v1是稳定版本v2是灰度版本还有一个独立的静态资源服务器。我希望实现的效果是普通用户访问默认走 v1 版本。带上Cookie: versiongray的请求走 v2 版本。/static/开头的请求直接走静态资源服务器。配置如下frontend web_front bind *:80 # 定义 ACL acl is_static path_beg /static/ acl is_gray_user req.cook_sub versiongray acl is_v2_path path_beg /new/ # 路由规则注意顺序 use_backend static_servers if is_static use_backend app_v2 if is_gray_user or is_v2_path default_backend app_v1 backend app_v1 balance roundrobin server web1 192.168.56.101:8080 check inter 3s fall 3 rise 2 server web2 192.168.56.102:8080 check inter 3s fall 3 rise 2 backend app_v2 balance roundrobin server web3 192.168.56.103:8081 check inter 3s fall 3 rise 2 backend static_servers balance roundrobin server static1 192.168.56.104:80 check inter 3s fall 3 rise 2这个配置的核心在于use_backend的匹配顺序。HAProxy 会从上到下依次评估每条规则只要有一条命中就立即执行后面的规则不再检查。实际验证一下# 默认请求应该打到 v1 curl http://192.168.56.10/ # 带灰度 Cookie 的请求应该打到 v2 curl --cookie versiongray http://192.168.56.10/ # 访问静态资源路径应该打到独立静态服务器 curl http://192.168.56.10/static/logo.png这三条命令的结果会清晰地展示 HAProxy 七层路由的能力。灰度发布的逻辑就藏在这个配置里——先用小流量验证 v2 版本稳定之后把default_backend从app_v1改成app_v2就完成了全量切换整个过程不需要改动代码只需要热重载配置。3.5 会话保持实战再做一个实验会话保持。前面讲了source算法可以做简单的会话保持但更精细的做法是通过 Cookie 实现。HAProxy 在backend里加一行配置就能实现基于 Cookie 的会话保持backend app_v1 balance roundrobin cookie SERVERID insert indirect nocache server web1 192.168.56.101:8080 cookie web1 check inter 3s fall 3 rise 2 server web2 192.168.56.102:8080 cookie web2 check inter 3s fall 3 rise 2这个配置的工作原理是当请求第一次到达 HAProxy 时它根据负载均衡算法选一台后端然后在响应里插入一个名为SERVERID的 Cookie值为该后端服务器设置的名字web1 或 web2。客户端后续请求带着这个 Cookie 到达 HAProxy 时HAProxy 直接根据 Cookie 值定向转发不再重新负载均衡。等等这不就破坏了负载均衡的效果吗所以配置里用了indirect参数它的作用是后端服务器本身可以不识别这个 CookieHAProxy 在转发请求给后端之前会先把SERVERID这个 Cookie 删除。这样后端应用无感知同时客户端又能保持会话。nocache参数则防止中间代理缓存这个 Set-Cookie 响应避免干扰会话保持。这种基于 Cookie 的会话保持比基于源 IP 的source算法更精确。源 IP 方式在用户通过手机 4G/5G 网络访问时可能一个基站出口 IP 对应大量用户全部哈希到同一台服务器上很容易导致过载。Cookie 方式则粒度到单个用户所以我在生产环境基本都是用 Cookie 方式。4. 常见问题与排查技巧实录再好的方案落地时肯定会踩坑。这一节我把实验过程中遇到的最典型的几个问题和排查思路记录一下希望能帮你省下几个小时的排查时间。4.1 健康检查误判导致流量全挂第一次配置健康检查时我犯过一个错误HAProxy 默认的健康检查是 TCP 连接检查它只检查后端端口能不能连通不检查服务是否真正可用。后来我的一台后端服务器出现了应用假死现象——进程还在端口能连上但接口已经超时。结果 HAProxy 还是把流量转发过去导致了大量 502。解决办法是使用 HTTP 健康检查backend web_servers balance roundrobin option httpchk HEAD /healthz server web1 192.168.56.101:8080 check inter 3s fall 3 rise 2option httpchk会让 HAProxy 定期向后端发送 HTTP HEAD 请求只有当后端返回 2xx 或 3xx 状态码时才认为服务器健康。建议后端应用务必实现一个专门的/healthz接口返回当前服务的真实健康状态。检查深度取决于你在这个接口里做了多少检查逻辑可以是简单的200 OK也可以复杂到检查数据库连接、内存使用率等。这里有个小细节option httpchk默认检查的是根路径/如果后端某些接口不支持 HEAD 方法可以改成option httpchk GET /healthz用 GET 请求代替。4.2 会话保持不生效的排查思路如果你的会话保持配置了但不生效通常可以从三个方向排查。第一检查indirect参数是否配置正确。如果没加indirect后端应用有可能会覆盖 HAProxy 设置的 Cookie导致下一次请求无法识别。第二检查 Cookie 的路径和域。有时候后端应用设置了Path/api的 Cookie会导致部分请求带不上会话 Cookie从而绕过 HAProxy 的会话保持逻辑。第三检查组网环境。如果客户端和 HAProxy 之间经过了 CDN 或者其他代理这些中间层可能会剥离或者修改 Cookie。这种情况往往需要在 HAProxy 上做更复杂的配置比如通过 URL 重写或者自定义 Header 来传递会话信息。实验阶段最容易忽略的是第一点我之前就是配置里漏写了indirect结果排查了半天最后查看响应头才发现 HAProxy 设置的 Cookie 被后端应用覆盖了。4.3 高并发下的性能调优参数如果你的业务体量比较大下面几个参数值得重点关注。maxconn在 global 段定义了 HAProxy 能够接受的最大连接数。它同时受操作系统文件描述符限制影响所以调大这个值之前要同步调整ulimit -n。我见过有人把 HAProxy 的maxconn调到 10 万但操作系统的fs.file-max没调结果进程刚启动就报 too many open files整个服务直接挂掉。nbthread设置了 HAProxy 的工作线程数一般设置为 CPU 核心数即可不用贪多。我实测过线程数超过 CPU 核心数后性能提升非常有限反而因为线程切换带来额外开销。timeout http-keep-alive也很关键。如果你的业务允许 keep-alive这是保持客户端连接的空闲时间上限。设置太短会导致频繁建立新连接增加延迟设置太长则可能堆积大量空闲连接占用内存和文件描述符。我的经验是 10 到 30 秒比较平衡。还有一个容易被忽略的点是option tcp-smart-accept和option tcp-smart-connect这两个选项能让内核在 accept 和 connect 时进行一定的优化减少不必要的网络包交互对于高并发短连接场景有明显收益。我在压测实验中开启后QPS 大概提升了 5% 到 8%。4.4 巧用内置监控页HAProxy 自带一个监控统计页面很多人不知道或者没用起来其实这个页面在排查问题时非常有用。在 frontend 或 listen 段添加如下配置listen stats bind :8404 mode http stats enable stats uri /stats stats realm HAProxy\ Statistics stats auth admin:admin123配置好之后浏览器访问http://HAProxy所在机器IP:8404/stats输入用户名密码就能看到实时的连接数、会话速率、后端服务器健康状态、请求队列长度等数据。这个页面最大的价值在于能直观地看到每台后端服务器的Session Rate和Response Time。当你怀疑负载不均衡时看这个页面大概三秒钟就能确认。后面我做压测时也习惯开着这个页面一边压一边观察后端的连接数变化排查问题比看日志快得多。5. 写在实验之后做完这套实验我对 HAProxy 七层代理算是有了一份比较完整的认知。它最打动我的不是有多强的性能——虽然性能确实强——而是它把“请求路由”这件事做得如此灵活。一个 AC L规则、一个use_backend指令就能实现灰度发布、AB 测试、会话保持这些在业务层面上极其有价值的功能而且整套配置逻辑清晰可维护性比一堆 Nginx 配置好了不知道多少倍。我的个人建议是如果你的团队正在找入口层负载均衡方案业务以 HTTP/HTTPS 为主需要灵活的路由策略和精细的健康检查HAProxy 值得花点时间深入了解。实验环境搭起来成本很低一台虚拟机加几个后端服务就能开始玩但搞懂后回报很高。最后再分享一个实操中的细节HAProxy 配置文件里注释非常关键特别是 ACL 这种可读性比较差的语法我在生产环境里给每条 ACL 和 use_backend 都写了注释后面维护时能少死很多脑细胞。