ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

HAProxy入门到实践:四层/七层转发与负载均衡配置详解

HAProxy入门到实践:四层/七层转发与负载均衡配置详解

1. 为什么要用HAProxy:先搞清楚它在整个架构里站哪个位置

先说一个我经常碰到的场景:手上有两台后端服务,业务逻辑一模一样,指望一台挂了另一台顶上;或者流量上来了,一台机器CPU飙到90%,另一台闲着。最土的办法是什么?改DNS、改前端配置、甚至手动切IP。这套做法的问题不在于麻烦,而在于切换速度完全取决于人反应快不快,半夜三点出故障,等人爬起来改配置,业务早就凉透了。

HAProxy就是来解决这个问题的。它是一台专门的流量入口机,前面接用户的请求,后面接真正干活的服务器。用户不管后面有几台、哪台活着、哪台在升级维护,只看到一个入口地址就行。由HAProxy决定这个请求该去谁家、谁状态不好就暂时不发给它、谁恢复健康了再把流量加回来。整个过程不需要改任何客户端配置。

很多人问,Nginx不也能做反向代理吗,为什么非要用HAProxy?我的理解是这样的:Nginx本质是一个Web服务器,反向代理只是它的一个能力强项;HAProxy则是一门心思做代理和负载均衡。它不碰静态文件、不用管PHP-FPM,专注做连接调度、健康检查、会话保持、访问控制这些事情。而且它的资源占用非常克制,纯粹处理转发时性能相当能打。初学阶段不用纠结谁更好,你只需要知道:当你想要的只是「入口统一 + 故障转移 + 流量分发」这个能力的时候,HAProxy是配置成本最低、理解成本也最低的选择。

在碰配置文件之前,有两个概念必须掰扯清楚,因为它直接决定了你配置文件开头的mode该写http还是tcp。

  • 四层转发(Layer 4):HAProxy只看到连接的目标IP和端口,直接把TCP流量原样转发给后端。它不关心你在传输的是网页、数据库协议还是游戏数据包。
  • 七层转发(Layer 7):HAProxy拿到HTTP请求之后,会拆开解析里面的URL、Header、Cookie这些信息,然后根据规则决定转发到哪个后端。能做更精细的路由,但也意味着它必须理解HTTP协议。

打个不太严谨的比方:四层是快递中转站,只看包裹上的地址,原封不动转运;七层是客服前台,先帮你拆开信封看一眼内容,再决定这个诉求该转给哪个部门。

明白了这个区别,后面看配置文件里的mode参数就不会发怵了:你的服务是HTTP应用,用七层模式;你的服务是任意TCP协议,用四层模式。很多人配置出问题,一半以上都是这里选错了。

2. 配置文件的骨架:global、defaults、frontend、backend到底各管什么事

HAProxy的配置文件结构其实非常规整,一共就四个主要段落,再加上一个可以在简单场景下替代前后端组合的listen段。先贴一份我平时给入门读者演示用的最小完整配置,后面逐段拆。

global log 127.0.0.1 local0 info maxconn 10000 user haproxy group haproxy daemon stats socket /var/lib/haproxy/admin.sock mode 660 level admin nbthread 4 defaults mode http retries 3 timeout connect 5s timeout client 30s timeout server 30s option httplog option dontlognull option redispatch frontend web_front bind *:80 default_backend web_servers backend web_servers balance roundrobin option httpchk GET /healthz server web1 10.0.0.10:8080 check inter 3s fall 3 rise 2 weight 1 server web2 10.0.0.11:8080 check inter 3s fall 3 rise 2 weight 1 backup

这份配置看起来不长,但是每一行都是我建议你先照抄、后面再改的东西。下面逐段讲为什么这么写。

2.1 global段:进程级别的基础设定

global段配置的是HAProxy进程本身的行为,不是针对某个站点或某个后端。它是整份配置文件里优先级最高、也最需要谨慎修改的一段。

  • log:配置日志输出位置。写成log 127.0.0.1 local0表示发到本机的syslog设施,info是日志级别。你后面排查问题时,几乎全靠它说话,所以这行不要省。
  • maxconn:进程级最大连接数,10000是我在中小流量的测试环境里常用的起始值。它并不单纯取决于你设多大,还要看系统文件描述符上限,配置里调高了但操作系统不放开,一样白搭。
  • user/group:启动后降权运行,避免用root跑HAProxy。安全配置的基本功。
  • daemon:让进程在后台运行,配合systemd管理时很顺手。
  • stats socket:启用本地管理套接字,配合socat工具可以运行中动态调整权重、启用或禁用某台后端服务器,不用重启进程。这行是排错和生产运维的利器,等你对配置有感觉了就会离不开它。
  • nbthread:在高版本HAProxy中用于开启多线程。老版本里常见的是nbproc多进程模式,但进程模式在现代版本已经不受推荐,我更建议你直接了当地用线程模式,省去一堆进程间通讯和运维上的麻烦。

2.2 defaults段:给后面所有frontend和backend上的默认值

defaults最大的价值是消除重复配置。HAProxy的配置风格是每条规则都可以在后端、前端里再声明一遍来覆盖默认值,而defaults则帮你把那些「几乎每段都想写一遍」的参数统一收口。

  • mode http:声明默认工作在七层HTTP模式。如果你的站里既有HTTP服务又有纯TCP转发,可以在具体的frontend或backend里单独覆盖回mode tcp。
  • retries:连接后端失败后的重试次数。这个数字不要设太大,否则用户请求会在单个后端上耗很久,后面讲排错时你会看到它造成的一个经典现象。
  • 三个timeout参数:connect是建立到后端连接的超时,client是等待客户端发送数据的超时,server是等待后端返回数据的超时。这三个值一旦缺失,HAProxy会直接使用内置默认值,但通常不符合你的业务预期,更麻烦的是日志里会不断出现警告。我建议新手从 connect 5s、client 30s、server 30s 起步,后续根据业务调整。
  • option httplog:让日志记录HTTP相关的信息,包括状态码、请求路径、响应字节数等。调试问题时没有它,你连用户请求到底成没成功都看不到。
  • option dontlognull:不记录既没有请求也没有响应的空连接。否则网上扫描器之类的连接会让你日志膨胀得没法看。
  • option redispatch:当某台后端服务器在重试过程中挂了,允许把请求重新分发给其他后端。retries配合redispatch,才真正实现了「一台挂了不影响用户体验」。

2.3 frontend段:流量入口,负责接收和初步判断

frontend定义的是HAProxy对外暴露的端口和入口规则。它只回答两个问题:从哪里接流量;接到流量之后往哪儿送。

  • bind *:80:监听所有网卡的80端口。你也可以只监听某个特定IP,比如内网管理网段的IP,这样外部就访问不到这个入口。
  • default_backend web_servers:当请求没有匹配到任何转发规则时,默认送到哪个后端。入门阶段你只需要这一个默认路由就行。
  • option forwardfor等进阶选项我暂时不写,等你把基础跑通后再加上,否则所有请求在日志里看到的客户端IP都是HAProxy自己的IP,排查问题时会一头雾水。

frontend是后面做高级路由(按域名、按URL前缀分流到不同后端)的落脚点,但初学阶段请克制住,先把简单的转发跑通再说。

2.4 backend段:真实干活服务器的名单和调度策略

backend段最核心的东西有两个:把流量按什么算法分给谁,以及怎么判断谁还活着。

  • balance roundrobin:最简单的轮询调度算法。每个新请求会轮流发给后端列表里的下一台机器。对入门和小流量场景,这是最直观、最好理解的选择。后面还有leastconn(最少连接优先)和source(按来源IP哈希),它们的适用场景我会在第四部分结合实际故障讲。
  • option httpchk GET /healthz:定义健康检查的探测方式,向每台后端服务器发送一个HTTP GET请求,请求路径是/healthz。这个路径应该在应用里显式提供一个轻量的、能被匿名访问的接口,而不是拿首页当探活路径。首页可能因为依赖了数据库而出错,也可能很重,探活就一直不稳定。
  • server web1 10.0.0.10:8080 check inter 3s fall 3 rise 2 weight 1:这是backend段里最需要逐词理解的一行。web1是给这台后端起的别名,这个别名出现在日志和统计页面里,命名尽量有意义;10.0.0.10:8080是后端地址;check开启对这台机器的健康检查;inter 3s每3秒探测一次;fall 3连续失败3次后判定为宕机;rise 2连续成功2次后判定为恢复;weight 1权重,默认是1,后面有需要了再调。若加上backup关键字,表示这台机器是备用节点,只有其他非backup节点全部不可用时才会接管流量。

2.5 listen段:一个段落搞定转发

如果只是做一个非常简单的四层TCP转发,不想拆frontend和backend,可以用listen把入口和后端写在一起:

listen mysql_replica bind *:3306 mode tcp balance leastconn server db1 10.0.0.20:3306 check server db2 10.0.0.21:3306 check backup

这段配置的意思是:HAProxy在自己机器的3306端口接收MySQL客户端连接,把流量转发给后端的db1;db1不可用时,流量走到backup节点db2。listen适合场景简单、不需要区分入口和后端规则的场合,读起来像一段自包含的端口转发配置。

3. 三组可以直接照抄的基础配置:从验证安装到带探活的转发

配置语法看得再明白,不如直接落到机器上跑一遍。这里给出三组我建议你按顺序操作的配置,分别对应三个不同的场景。每组都配套验证方式,照着做就能有反馈。

3.1 第一组:HTTP轮询,用于验证安装和基本转发

假设你手上有两台后端服务,监听在8080端口,返回一段简单的HTML内容。先写一份最精简的配置,验证HAProxy能否正常启动和转发:

global log 127.0.0.1 local0 maxconn 4096 user haproxy group haproxy daemon defaults mode http timeout connect 5s timeout client 30s timeout server 30s frontend web_front bind *:80 default_backend web_servers backend web_servers balance roundrobin server web1 10.0.0.10:8080 check server web2 10.0.0.11:8080 check

操作顺序是这样的:

  1. 用haproxy -c -f /etc/haproxy/haproxy.cfg校验配置语法。这个命令非常关键,写错任何一个参数它都会直接报错,帮你把问题挡在重启之前。
  2. 启动服务并确认进程状态正常。
  3. 在另一台机器上连续执行几次curl http://HAProxy的IP/,观察返回内容。如果两台后端返回的页面里各有一个不同的标识字符串(比如节点编号),你会看到轮询分配是交替出现的。
  4. 手动停掉一台后端服务,再执行几次curl。你会发现请求仍然成功,只是流量全部打到了剩下那一台。这就是健康检查带来的故障转移效果。

这组配置跑通之后,你的HAProxy基础能力就算建立起来了。

3.2 第二组:纯四层TCP转发,把不认识的协议也纳入调度

不是所有服务都是HTTP。比如后端有一组只读的数据库副本,客户端用的是MySQL协议,你只是想做一个入口统一和故障转移,不需要HAProxy解析协议内容,这时候用mode tcp:

listen mysql_ro bind *:3306 mode tcp balance leastconn option log-health-checks server db1 10.0.0.20:3306 check inter 5s fall 3 rise 2 server db2 10.0.0.21:3306 check inter 5s fall 3 rise 2

这组配置里,bind端口和backend保持一致都是3306,客户端连接HAProxy机器的3306端口就等价于连到了后端某一台数据库。option log-health-checks会在日志里记录健康检查的执行结果,方便你确认探活到底成没成功。

之所以用mode tcp而不是mode http,原因是流量本身不是HTTP请求,HAProxy如果把HTTP解析逻辑套上去,反而会因为看不懂协议而报错或者破坏数据完整性。TCP模式下HAProxy做的事情只是转发字节流,对协议一无所知也没关系。

3.3 第三组:加权重、备用节点和统计页面

当你开始觉得「轮询平均分配不够用」时,就该接触权重和backup了。这组配置解决的是两个很常见的问题:某台机器性能更强,希望它多接一点流量;某台机器只在极端情况下启用,平时不参与。

frontend web_front bind *:80 default_backend web_servers backend web_servers balance roundrobin option httpchk GET /healthz server web1 10.0.0.10:8080 check weight 3 server web2 10.0.0.11:8080 check weight 1 server web3 10.0.0.12:8080 check backup

这里weight 3和weight 1表示web1每收到3个请求,web2收到1个。backup节点平时不会被分配任何请求,只有当web1和web2都不可用时,流量才会转到web3上。注意:backup节点的健康检查照常执行,避免它被选中的时候本身就已经死了。

另外,建议在任意frontend或listen段落里加上统计页面配置:

frontend stats_front bind *:8404 stats enable stats uri /stats stats auth admin:your_password

加完之后,浏览器打开http://HAProxy的IP:8404/stats,输入账号密码就能看到每台后端的实时状态:UP、DOWN、当前连接数、会话速率、健康检查成功与失败次数。必须说,这个页面第一时间就能帮你判断「后端是不是挂了、流量有没有在按预期分配」,入门阶段学会看它,能省掉一半的排错时间。stats auth里的密码请换成足够强度的值,并且这个管理端口不要暴露到公网,只监听内网IP会更稳妥。

4. 配置语法正确但功能不达标:三个高频故障的完整排查链路

配置写对了、服务也启动了、按理说应该没问题了,但实际跑起来就是不对劲。下面这三个现象是我见过的最高频case,我把每个的排查链路完整写出来,你照着走一遍基本能定位。

4.1 后端明明活着,却一直返回503

现象:打开HAProxy入口地址,返回503 Service Unavailable;但直接curl后端IP,服务响应一切正常。

排查链路:

  1. 先看统计页面里后端状态。如果显示的是DOWN而不是没有配置健康检查,说明探活失败了。
  2. 然后思考探活到底是怎么走的。配置里写option httpchk GET /healthz,那HAProxy就是用HTTP GET去请求后端,判断条件是2xx和3xx算成功,4xx和5xx算失败。
  3. 最常见的坑出现在后端端口只监听了127.0.0.1上。你直接在后端机器上curl http://127.0.0.1:8080/healthz当然成功,但HAProxy是从另一台机器去访问的,请求到达不了。用ss -lntp | grep 8080看监听地址,如果只有127.0.0.1,把服务监听地址改成0.0.0.0或内网IP即可。
  4. 还有一个隐蔽情况:探活地址返回302跳转。HAProxy默认把3xx视为成功,所以不会宕机,但如果你后续看日志觉得诡异,就要知道3xx不算失败这件事。
  5. 确认最近一次探活的日志记录。日志里能看到探活请求是从哪个IP发出的、状态码是多少、耗时多久。把日志和统计页面的计数结合起来看,基本不会误判。

这个过程的关键在于:不要一看到503就怀疑HAProxy坏了,它多数时候只是如实地把「后端不可达」这个状态暴露给你。

4.2 mode选错导致长连接被断开

现象:后端是WebSocket服务,或者某些保持长连接的TCP业务。客户端连上之后,过一段时间连接就被断开,日志里能看到超时记录;但短请求一切正常。

排查链路:

  1. 先确认前端和后端的mode。如果你在七层HTTP模式下转发WebSocket流量,大方向就错了。WebSocket虽然建连时用HTTP Upgrade协议,但一旦升级成功,后续就是双向的裸TCP流。HTTP模式下HAProxy可以转发它,但会继续用timeout client和timeout server来约束读写。长连接里如果数据推送间隔超过设定值,就会被判定为超时,然后主动断开。
  2. 用haproxy -c -f校验配置没有语法问题,排除低级错误。
  3. 查看连接断开的时间点,和timeout server的值做对比。如果时间点大致落在连接建立后的X秒,而X正是timeout值,那方向就确定了。
  4. 解决方案:对这类协议改用mode tcp,或者至少单独建一个backend用mode tcp。TCP模式下HAProxy不会解析HTTP内容,也就不存在HTTP超时逻辑,报文只是透传,连接生命周期由客户端和后端自己协商。

这个case的教训是:配置文件本身的语法、参数没有问题,但选错了运行模式,功能就会在极端场景下失效。mode的选择要按业务协议来,不是按端口号来。

4.3 负载均衡算法选错,单台后端被压垮

现象:三台后端配置了轮询,但流量并没有均匀分布。某一台连接数始终是其他几台的好几倍,甚至出现响应变慢。

排查链路:

  1. 先看统计页面的会话计数和当前连接数。如果差异巨大,很可能不是算法的问题,而是算法和场景不匹配。
  2. 想一下你的流量形态。roundrobin按请求轮转,适合大部分HTTP短请求场景;leastconn优先把新连接分配给当前连接数最少的后端,适合长连接、连接耗时长、连接数差距会明显拉开的应用;source按客户端IP做哈希,同一来源IP的请求固定落到同一台后端,适合需要会话保持但又不方便下发Cookie的场景。
  3. 这里有一个经典陷阱:客户端流量经过一层NAT出口,大量用户对外表现为同一个来源IP。此时用source算法做哈希,所有用户会被分配到同一台后端上,其他后端闲着,单机压力自然暴涨。统计页面的「来源IP」列能帮你证实这种情况。
  4. 我的建议是:没有强烈会话粘连需求时,默认用roundrobin;长连接服务用leastconn;需要会话保持但不想改应用时,再考虑source,同时要确认来源IP的分布是均匀的。

算法没有绝对的好坏,只有合不合适。如果发现某条链路确实需要按会话保持,还可以在backend段里配合cookie指令让HAProxy在响应里种Cookie,后续同一用户的请求按Cookie决定后端,这比纯source哈希更可控,但会引入Cookie相关的排错复杂度,入门阶段先了解存在即可。

5. 一些配置之外的习惯,比记住参数更有用

配置HAProxy这件事,本身语法并不难,难的是在长期维护中保持清晰和可预测。几个我实际操作中形成的习惯,顺带分享给你。

第一,配置文件里注释要写得像给下一个值班人看。生产环境的HAProxy配置,不是写给自己看的,是写给深夜被电话叫醒的同事看的。每一台server行后面,至少标注这台机器上跑的是什么服务、上线时间、负责人。哪怕只是两行字,关键时刻能救命。

第二,改完配置别急着重启。先用haproxy -c -f校验,再看haproxy -c -V检查版本相关的兼容性提示,生产环境能用reload就不restart。HAProxy支持优雅reload,连接会平滑过渡到新进程,比直接重启要稳。

第三,管理套接字和统计页面配好之后,花点时间用它来模拟故障。比如通过socat管理接口直接将某台后端标记为DOWN,观察流量是否如期切换到其他节点。这种演练做过一次,真出故障时才不会手忙脚乱。

第四,日志别只开不用。option httplog打开后,日志会记录每条请求转发到了哪台后端。排查「用户请求为什么变慢」「为什么某台后端日志里有异常记录」这类问题时,比对HAProxy日志和应用日志里的时间点,很快就能画出完整的调用链路。

HAProxy的入门配置并不复杂,你真正需要的不是背下每一个参数,而是先把「入口在哪里、后端是谁、探活怎么走、流量怎么分」这四个问题在配置里答清楚。基础跑通之后,ACL路由、限流、重试策略这些高级功能,都是在这个骨架上长出来的。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进