跳转到主要内容

HAProxy 是什么及其实现原理

HAProxy 的角色、边界、事件驱动架构及请求处理模型

“HAProxy” 用于指代产品,而 “haproxy” 用于指代可执行程序、软件包或进程。然而,两者常被互换使用,且均发音为 H-A-Proxy。早期,“haproxy” 曾代表“高可用性代理”(high availability proxy),名称以两个独立单词书写,但如今其含义已仅限于“HAProxy”。

3.1. HAProxy 是与非

HAProxy 是:

  • TCP 代理:可从监听套接字接收 TCP 连接,连接至服务器,并将这两个套接字绑定在一起,从而实现双向流量传输;支持任一侧使用 IPv4、IPv6 甚至 Unix 套接字,因此可提供一种简便方式,实现不同地址族之间的地址转换。

  • 一个 HTTP 反向代理(在 HTTP 术语中称为“网关”):它表现为一个服务器,通过监听 TCP 套接字接收连接上的 HTTP 请求,并使用不同的连接将这些请求转发至服务器。它可在任一侧使用任意组合的 HTTP/1.x 或 HTTP/2,并在使用 ALPN 通过 TLS 时甚至会自动检测每侧所使用的协议。

  • SSL 终止器 / 初始化器 / 卸载器:来自客户端的连接、去往服务器的连接,甚至两条连接均可使用 SSL/TLS。可按名称(SNI)应用大量设置,且可在运行时更新而无需重启。此类配置具有极强的可扩展性,已有部署报告支持数万至数十万张证书。

  • TCP 正常化器:由于连接由操作系统本地终止,双方之间不存在关联,因此无效数据包、异常标志组合、窗口通告、序列号异常、不完整连接(如 SYN 洪水)等异常流量不会传递到另一方。这可保护脆弱的 TCP 栈免受协议攻击,同时允许在不修改服务器 TCP 栈设置的前提下,对客户端连接参数进行优化。

  • HTTP 正常化器:当配置为处理 HTTP 流量时,仅允许完整且有效的请求通过。这可有效防范多种基于协议的攻击。此外,对于规范中允许容忍的协议偏差,会进行修正,以避免其在服务器端引发问题(例如,多行头)。

  • 一个 HTTP 修复工具:可用于修改、修复、添加、删除或重写 URL,以及任意请求或响应头。这有助于解决复杂环境中的互操作性问题。

  • 基于内容的切换器:可根据请求中的任意元素决定将请求或连接传递给哪个服务器。因此,可在同一端口上处理多种协议(例如 HTTP、HTTPS、SSH)。

  • 服务器负载均衡器:可对 TCP 连接和 HTTP 请求进行负载均衡。在 TCP 模式下,负载均衡决策针对整个连接作出。在 HTTP 模式下,决策针对每个请求作出。

  • 流量调节器:可在多个位置实施速率限制,防止服务器过载,根据流量内容调整流量优先级,甚至通过标记数据包将此类信息传递至下层及外部网络组件。

  • 针对 DDoS 和服务滥用的防护:可针对每个 IP 地址、URL、Cookie 等维护大量统计信息,检测到滥用行为时采取动作(如放慢违规者、阻止其访问、将其引导至过时内容等)。

  • 网络故障排查的观察点:由于日志中报告的信息精度较高,常用于缩小某些网络相关问题的排查范围。

  • HTTP 压缩卸载器:可对服务器未压缩的响应进行压缩,从而缩短客户端在连接质量较差或使用高延迟移动网络时的页面加载时间。

  • 缓存代理:可将响应缓存在内存中,使后续对同一对象的请求无需再承担从服务器进行另一次网络传输的开销,只要该对象仍存在且有效即可。但 HAProxy 不会将对象存储到任何持久化存储中。请注意,此缓存功能旨在实现免维护运行,仅专注于节省 haproxy 的宝贵资源,而非节省服务器资源。旨在优化服务器的缓存需要更多的调优和灵活性。如需此类高级缓存功能,请使用 Varnish Cache,它与 haproxy 完美集成,尤其在任一侧需要 SSL/TLS 时。

  • FastCGI 网关:FastCGI 可被视为 HTTP 的另一种表现形式,因此 HAProxy 可直接对由任意 FastCGI 应用服务器组合构成的服务器群进行负载均衡,无需在中间插入另一层网关。这可节省资源并降低维护成本。

HAProxy 不是:

  • 显式 HTTP 代理,即浏览器用于访问互联网的代理。此类任务已有诸多优秀的开源软件专门支持,例如 Squid。然而,可在此类代理前安装 HAProxy,以提供负载均衡和高可用性。

  • 数据清理器:不会修改请求或响应的正文内容。

  • 静态 Web 服务器:启动期间,它会将自身隔离在 chroot 环境中并放弃特权,因此启动后将不会执行任何文件系统访问操作。因此,它无法被用作静态 Web 服务器(动态服务器可通过 FastCGI 支持)。此类用途有众多优秀的开源软件可供选择,例如 Apache 或 Nginx,HAProxy 可轻松安装在它们前端,以提供负载均衡、高可用性和加速功能。

  • 基于数据包的负载均衡器:它看不到 IP 数据包和 UDP 数据报,不会执行 NAT,更不会执行 DSR。这些任务应由更低层级完成。某些基于内核的组件(如 IPVS(Linux 虚拟服务器))已能很好地完成此类工作,并与 HAProxy 完美互补。

3.2. HAProxy 的工作原理

HAProxy 是一个基于事件驱动、非阻塞的引擎,结合了极快的 I/O 层与基于优先级的多线程调度器。由于其设计目标是数据转发,架构经过优化,能够在最少的操作下尽可能快速地传输数据。它通过尽可能将连接绑定到同一 CPU 来提升 CPU 缓存的使用效率。因此,它采用分层模型,在每一层都提供绕行机制,确保数据仅在必要时才传递到更高层级。大部分处理工作在内核中完成,HAProxy 尽最大努力通过提供一些提示或在预判某些操作稍后可以合并时避免它们,来帮助内核尽可能快地完成工作。典型数据显示,在 TCP 或 HTTP 关闭模式下,HAProxy 占用约 15% 的处理时间,内核占用 85%;在 HTTP 持久连接模式下,HAProxy 占用约 30%,内核占用 70%。

一个进程可以运行多个代理实例;有报告称单个进程中运行多达 300000 个不同代理实例也能正常工作。对于超过 99% 的用户而言,单核、单 CPU 的配置已经绰绰有余,因此鼓励容器和虚拟机用户使用所能获得的最小镜像,以降低运营成本并简化故障排查。然而,HAProxy 所运行的机器绝不能使用交换(swap),其 CPU 也不得被人为限速(如虚拟化环境中低于单 CPU 的分配),也不得与计算密集型进程共享,否则会导致极高的上下文切换延迟。

多线程机制可通过每个 CPU 核心使用一个线程的方式,充分利用所有可用的处理能力。该机制在处理 SSL 或需要超过 40 Gbps 的数据转发速率时尤为有用。在此类场景下,避免多个物理 CPU 之间的通信至关重要,否则可能导致网络栈及 HAProxy 本身出现严重瓶颈。尽管对部分用户而言看似反直觉,但在面对某些性能问题时,首先要做的往往是减少 HAProxy 所运行的 CPU 数量。

HAProxy 仅需 haproxy 可执行文件和配置文件即可运行。为实现日志记录,强烈建议配置好 syslog 守护进程并设置日志轮转。日志也可发送至 stdout/stderr,这在容器环境中尤为有用。配置文件在启动前被解析,随后 HAProxy 会尝试绑定所有监听套接字,若任何环节失败则拒绝启动。一旦越过此阶段,便不再可能失败。这意味着不存在运行时故障,若 HAProxy 确认启动,则将持续正常运行直至被停止。

HAProxy 启动后,将恰好执行以下 3 件事:

  • 处理入站连接;

  • 定期检查服务器状态(称为健康检查);

  • 与其他 HAProxy 节点交换信息。

处理传入连接是迄今为止最复杂的任务,因为它依赖于众多配置选项,但可以概括为以下 9 个步骤:

  • 接受来自属于名为“前端”(frontend)的配置实体的监听套接字的入站连接,该前端引用一个或多个监听地址;

  • 对这些连接应用前端特定的处理规则,可能造成连接被阻断、修改某些头,或拦截连接以执行某些内部小程序,例如统计页面或 CLI;

  • 将这些传入连接传递至另一个代表服务器群的配置实体,即“后端”,该后端包含服务器列表以及此服务器群的负载均衡策略;

  • 对这些连接应用后端特定的处理规则;

  • 根据负载均衡策略决定将连接转发至哪台服务器;

  • 对响应数据应用后端特定的处理规则;

  • 对响应数据应用前端特定的处理规则;

  • 发送日志以详细报告发生的情况;

  • 在 HTTP 中,循环回到第二步以等待新请求;否则关闭连接。

前端和后端有时被视为半代理,因为它们仅关注端到端连接的一侧;前端仅关注客户端,而后端仅关注服务器。HAProxy 还支持全代理,全代理恰好就是一个前端和一个后端的并集。当需要进行 HTTP 处理时,配置通常会划分为前端和后端,因为它们开启了大量可能性:任意前端都可以将连接传递给任意后端。在仅使用 TCP 的代理中,使用前端和后端很少带来好处,而使用全代理反而可能让配置更易读。