本文档不提供任何配置方面的帮助或提示,但它解释了在哪里可以找到相关文档。下面的概述旨在帮助您按名称搜索章节并在文档中导航。致文档贡献者:本文档的格式为每行 80 列,缩进使用偶数个空格,不使用制表符。请严格遵守这些规则,以便在任何地方都能轻松打印。如果您添加章节,请更新下面的概述以便于搜索。
| 1. | 可用文档 | |
2. |
负载均衡和负载均衡器的快速入门 | |
3. |
HAProxy 简介 | |
| 3.1. | ||
| 3.2. | ||
| 3.3. | ||
| 3.3.1. | ||
| 3.3.2. | ||
| 3.3.3. | ||
| 3.3.4. | ||
| 3.3.5. | ||
| 3.3.6. | ||
| 3.3.7. | ||
| 3.3.8. | ||
| 3.4. | ||
| 3.4.1. | ||
| 3.4.2. | ||
| 3.4.3. | ||
| 3.4.4. | ||
| 3.4.5. | ||
| 3.4.6. | ||
| 3.4.7. | ||
| 3.4.8. | ||
| 3.5. | ||
| 3.5.1. | ||
| 3.5.2. | ||
| 3.5.3. | ||
| 3.5.4. | ||
| 3.6. | ||
| 3.7. | ||
4. |
配套产品和替代方案 | |
| 4.1. | ||
| 4.2. | ||
| 4.3. | ||
| 4.4. | ||
5. |
联系方式 |
完整的 HAProxy 文档包含在以下文件中。请务必查阅相关文档,以节省时间并根据您的需求获得最准确的答复。另外,如果问题的答案已存在于这些文档中,请不要将问题发送到邮件列表。 - intro.txt (本文档):介绍负载均衡的基础知识、HAProxy 作为一款产品的功能、它不能做什么、一些需要避免的已知陷阱、一些特定于操作系统的限制、如何获取它、它如何演进、如何确保您运行的是包含所有已知修复的版本、如何更新它,以及它的补充和替代方案。 - management.txt:解释如何启动 haproxy、如何在运行时管理它、如何在多个节点上管理它,以及如何进行无缝升级。 - configuration.txt:参考手册,详细说明所有配置关键字及其选项。在需要进行配置更改时使用。 - coding-style.txt:这是为希望向项目贡献代码的开发人员准备的。它解释了代码应采用的风格。它不是很严格,也不是所有代码库都完全遵守,但与它偏离太多的贡献将被拒绝。 - proxy-protocol.txt:这是 PROXY 协议事实上的规范,该协议由 HAProxy 和一些第三方产品实现。 - README:如何从源代码构建 HAProxy
负载均衡旨在通过聚合多个组件,以达到超过单个组件处理能力的总处理能力,而无需最终用户的任何干预,并以可扩展的方式实现。这使得在单个组件执行一次操作的时间内,可以同时执行更多的操作。然而,单个操作仍然一次只在一个组件上执行,并且不会比没有负载均衡时更快。它总是需要至少与可用组件数量一样多的操作和一个高效的负载均衡机制,才能充分利用所有组件并从负载均衡中受益。一个很好的例子是高速公路上的车道数量,它允许同样多的汽车在同一时间段内通过,而不会增加它们的单车速度。 负载均衡的例子: - 多处理器系统中的进程调度 - 链路负载均衡(例如 EtherChannel, Bonding) - IP 地址负载均衡(例如 ECMP, DNS 轮询) - 服务器负载均衡(通过负载均衡器) 执行负载均衡操作的机制或组件被称为负载均衡器。在 Web 环境中,这些组件被称为“网络负载均衡器”,更普遍地称为“负载均衡器”,因为这项活动是迄z-远最知名的负载均衡案例。 负载均衡器可以作用于: - 链路层:这被称为链路负载均衡,它在于选择将数据包发送到哪个网络链路; - 网络层:这被称为网络负载均衡,它在于选择一系列数据包将遵循的路由; - 服务器层:这被称为服务器负载均衡,它在于决定哪个服务器将处理一个连接或请求。 存在两种不同的技术,它们满足不同的需求,尽管有一些重叠。在每种情况下,重要的是要记住,负载均衡在于将流量从其自然流向中分流,这样做总是需要最起码的谨慎,以维持所有路由决策之间所需的一致性。 第一种技术作用于数据包级别,或多或少地独立处理数据包。输入和输出数据包之间存在一对一的关系,因此可以使用常规的网络嗅探器在负载均衡器的两侧跟踪流量。这项技术可以非常便宜且速度极快。它通常在硬件(ASIC)中实现,以达到线速,例如执行 ECMP 的交换机。它通常是无状态的,但也可以是状态化的(考虑数据包所属的会话,称为 L4-LB 或 L4),如果数据包未被修改,可能支持 DSR(直接服务器返回,无需再次通过 LB),但几乎不提供内容感知能力。这项技术非常适合网络级负载均衡,尽管有时也用于高速下的非常基本的服务器负载均衡。 第二种技术作用于会话内容。它要求输入流被重组并作为一个整体进行处理。内容可能会被修改,输出流被分割成新的数据包。因此,这通常由代理执行,它们通常被称为第 7 层负载均衡器或 L7。这意味着两侧有两个独立的连接,输入和输出数据包的大小和数量之间没有关系。客户端和服务器不需要使用相同的协议(例如 IPv4 vs IPv6,明文 vs SSL)。操作总是状态化的,返回的流量必须通过负载均衡器。额外的处理带来了成本,所以并不总是能够达到线速,特别是在小数据包的情况下。另一方面,它提供了广泛的可能性,并且通常由纯软件实现,即使嵌入到硬件设备中。这项技术非常适合服务器负载均衡。 基于数据包的负载均衡器通常以直通模式部署,因此它们安装在流量的正常路径上,并根据配置分流流量。返回的流量不一定通过负载均衡器。可能会对网络目标地址进行一些修改,以便将流量引导到正确的目的地。在这种情况下,返回的流量必须通过负载均衡器。如果路由无法实现这一点,负载均衡器也可能用自己的地址替换数据包的源地址,以强制返回的流量通过它。 基于代理的负载均衡器作为具有自己 IP 地址和端口的服务器部署,无需改变架构。有时这需要对应用程序进行一些调整,以便客户端被正确引导到负载均衡器的 IP 地址,而不是直接引导到服务器的地址。一些负载均衡器可能需要调整某些服务器的响应来实现这一点(例如 HTTP 重定向中使用的 HTTP Location 标头字段)。 一些基于代理的负载均衡器可能会拦截非自己地址的流量,并在连接到服务器时伪造客户端的地址。这使它们可以像常规路由器或防火墙一样以直通模式部署,非常类似于基于数据包的负载均衡器。这对于结合了数据包模式和代理模式的产品尤其受欢迎。在这种情况下,DSR 显然仍然不可能,返回的流量仍然必须路由回负载均衡器。 一种非常可扩展的分层方法是,让一个前端路由器从多个负载均衡的链路上接收流量,并使用 ECMP 将此流量分配给第一层的多个状态化基于数据包的负载均衡器(L4)。这些 L4 负载均衡器再将流量传递给更多数量的基于代理的负载均衡器(L7),后者必须解析内容以决定最终哪个服务器将接收流量。 组件和流量可能路径的数量增加了故障的风险;在非常大的环境中,永久性地有一些故障组件正在被修复或更换甚至是很正常的。在不了解整个堆栈健康状况的情况下进行负载均衡会显著降低可用性。因此,任何正常的负载均衡器都会验证它打算将流量传递到的组件是否仍然存活且可达,并会停止向故障组件传递流量。 这可以通过多种方法实现。最常见的一种是定期发送探测以确保组件仍然可操作。这些探测被称为“健康检查”。它们必须能代表要处理的故障类型。例如,基于 ping 的检查无法检测到 Web 服务器已崩溃且不再监听端口,而连接到该端口将验证这一点,更高级的请求甚至可以验证服务器仍在工作且其依赖的数据库仍然可访问。健康检查通常涉及几次重试以应对偶尔的测量错误。检查之间的周期必须足够短,以确保故障组件在发生错误后不会被使用太长时间。 其他方法包括对发送到目的地的生产流量进行抽样,以观察其是否被正确处理,并剔除返回不当响应的组件。然而,这需要牺牲一部分生产流量,而这并不总是可以接受的。将这两种机制结合起来可以两全其美,两者都用于检测故障,而只有健康检查用于检测故障的结束。 最后一种方法涉及集中报告:一个中央监控代理定期向所有负载均衡器更新所有组件的状态。这为所有组件提供了基础设施的全局视图,尽管有时准确性或响应性较低。它最适合拥有许多负载均衡器和许多服务器的环境。 第 7 层负载均衡器还面临另一个挑战,称为粘性(stickiness)或持久性(persistence)。其原理是,它们通常必须将来自同一来源(例如最终用户)的多个后续请求或连接导向到同一目标。最著名的例子是在线商店的购物车。如果每次点击都导致一个新的连接,用户必须总是被发送到存放他购物车的服务器。内容感知使得更容易在请求中发现一些元素来识别要将请求发送到的服务器,但这并不总是足够。例如,如果使用源地址作为选择服务器的密钥,可以决定使用基于哈希的算法,并且根据地址除以可用服务器数量的结果,将给定的 IP 地址总是发送到同一台服务器。但如果一台服务器发生故障,结果就会改变,所有用户会突然被发送到不同的服务器并丢失他们的购物车。解决这个问题的方案在于记住选择的目标,这样每次看到同一个访问者时,无论可用服务器的数量如何,他都会被导向到同一台服务器。该信息可以存储在负载均衡器的内存中,在这种情况下,如果它不是唯一的负载均衡器,可能需要复制到其他负载均衡器;或者它也可以存储在客户端的内存中,使用各种方法,前提是客户端能够在每个请求中都带回这些信息(Cookie 插入、重定向到子域等)。这种机制提供了额外的好处,即不必依赖不稳定或分布不均的信息(如源 IP 地址)。这实际上是采用第 7 层负载均衡器而不是第 4 层负载均衡器的最强有力的理由。 为了提取诸如 cookie、主机头字段、URL 等信息,负载均衡器可能需要解密 SSL/TLS 流量,甚至可能在将其传递给服务器时重新加密。这项昂贵的任务解释了为什么在一些高流量的基础设施中,有时可能会有很多负载均衡器。 由于第 7 层负载均衡器可能对流量执行许多复杂的操作(解密、解析、修改、匹配 cookie、决定发送到哪个服务器等),它肯定会引起一些麻烦,并且很常被指责为许多它仅仅是揭示出来的问题的罪魁祸首。通常会发现服务器不稳定,会周期性地上下线;或者对于 Web 服务器,它们提供的页面中带有一些硬编码的链接,迫使客户端直接连接到某个特定服务器而不通过负载均衡器;或者它们在高负载下响应时间极长,导致超时。这就是为什么日志记录是第 7 层负载均衡一个极其重要的方面。一旦报告了问题,重要的是要弄清楚负载均衡器是否做出了错误的决定,如果是,为什么,以便不再发生。
HAProxy 写成“HAProxy”以指代产品,写成“haproxy”以指代可执行程序、软件包或进程。然而,两者通常都用于这两个目的,并且读作 H-A-Proxy。很早以前,“haproxy”代表“high availability proxy”,并且这个名字被写成两个独立的单词,尽管现在它只代表“HAProxy”。
HAProxy 是: - 一个 TCP 代理:它可以从一个监听套接字接受 TCP 连接,连接到一个服务器,并将这些套接字连接在一起,允许流量双向流动;IPv4、IPv6 甚至 UNIX 套接字在两端都受支持,因此这可以提供一种在不同地址族之间轻松转换地址的方法。 - 一个 HTTP 反向代理(在 HTTP 术语中称为“网关”):它表现得像一个服务器,通过在监听的 TCP 套接字上接受的连接接收 HTTP 请求,并使用不同的连接将这些连接中的请求传递给服务器。它可以在任何一端使用 HTTP/1.x 或 HTTP/2 的任意组合,甚至在使用 ALPN over 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 压缩卸载器:它可以压缩服务器未压缩的响应,从而为连接性差或使用高延迟移动网络的客户端减少页面加载时间。 - 一个缓存代理:它可以在 RAM 中缓存响应,以便对同一对象的后续请求可以避免从服务器再次进行网络传输的成本,只要该对象仍然存在且有效。然而,它不会将对象存储到任何持久性存储中。请注意,此缓存功能设计为免维护,并且只专注于节省 haproxy 的宝贵资源,而不是节省服务器的资源。旨在优化服务器的缓存需要更多的调整和灵活性。如果您需要这种高级缓存,请使用 Varnish Cache,它与 haproxy 完美集成,尤其是在任何一端需要 SSL/TLS 时。 - 一个 FastCGI 网关:FastCGI 可以被看作是 HTTP 的一种不同表示形式,因此,HAProxy 可以直接对包含任何 FastCGI 应用程序服务器组合的服务器场进行负载均衡,而无需在它们之间插入另一层网关。这可以节省资源并降低维护成本。 HAProxy 不是: - 一个显式 HTTP 代理,即浏览器用来访问互联网的代理。有许多优秀的开源软件专门用于此任务,例如 Squid。但是,HAProxy 可以安装在此类代理的前面,以提供负载均衡和高可用性。 - 一个数据清洗器:它不会修改请求或响应的主体部分。 - 一个静态 Web 服务器:在启动期间,它会将自己隔离在一个 chroot jail 中并放弃其权限,因此一旦启动,它将不会执行任何文件系统访问。因此,它不能被用作静态 Web 服务器(但通过 FastCGI 支持动态服务器)。有许多优秀的开源软件可以做到这一点,例如 Apache 或 Nginx,并且 HAProxy 可以轻松地安装在它们前面,以提供负载均衡、高可用性和加速。 - 一个基于数据包的负载均衡器:它看不到 IP 数据包或 UDP 数据报,不会执行 NAT,更不会执行 DSR。这些是下层协议的任务。一些基于内核的组件,如 IPVS(Linux 虚拟服务器),已经很好地完成了这些任务,并与 HAProxy 完美互补。
HAProxy 是一个事件驱动的、非阻塞的引擎,它结合了一个非常快速的 I/O 层和一个基于优先级的多线程调度器。由于其设计目标是数据转发,其架构经过优化,以尽可能少的操作尽快移动数据。它专注于通过尽可能长时间地将连接固定在同一个 CPU 上来优化 CPU 缓存的效率。因此,它实现了一个分层模型,在每一层都提供旁路机制,确保数据除非需要,否则不会到达更高层。大部分处理都在内核中执行,HAProxy 通过给出一些提示或在猜测某些操作可以稍后分组时避免这些操作,尽力帮助内核尽快完成工作。因此,典型数据显示,在 TCP 或 HTTP close 模式下,15% 的处理时间花费在 HAProxy 上,而 85% 在内核中;在 HTTP keep-alive 模式下,大约 30% 用于 HAProxy,70% 用于内核。 一个进程可以运行许多代理实例;据报道,单个进程中配置多达 300,000 个不同代理的配置也能正常运行。对于超过 99% 的用户来说,单核、单 CPU 的设置已绰绰有余,因此,鼓励容器和虚拟机的用户使用他们能获得的绝对最小的镜像,以节省运营成本并简化故障排查。但是,HAProxy 运行的机器绝不能进行交换(swap),其 CPU 也不能被人为限制(hypervisor 中的子 CPU 分配),也不能与计算密集型进程共享,这会导致非常高的上下文切换延迟。 线程允许通过为每个 CPU 核心使用一个线程来利用所有可用的处理能力。这对于 SSL 或需要超过 40 Gbps 的数据转发速率时尤其有用。在这种情况下,避免多个物理 CPU 之间的通信至关重要,这可能导致网络堆栈和 HAProxy 本身的严重瓶颈。虽然对某些人来说可能违反直觉,但当面临性能问题时,首先要做的事情往往是减少 HAProxy 运行的 CPU 数量。 HAProxy 只需要 haproxy 可执行文件和一个配置文件即可运行。对于日志记录,强烈建议配置好 syslog 守护进程和日志轮转。日志也可以发送到 stdout/stderr,这在容器内部可能很有用。 配置文件在启动前被解析,然后 HAProxy 尝试绑定所有监听套接字,如果任何操作失败,则拒绝启动。过了这一点,它就不会再失败了。这意味着没有运行时失败,如果它接受启动,它将一直工作直到被停止。 一旦 HAProxy 启动,它只做三件事: - 处理传入连接; - 定期检查服务器的状态(称为健康检查); - 与其他 haproxy 节点交换信息。 处理传入连接是迄今为止最复杂的任务,因为它依赖于大量的配置可能性,但可以总结为以下 9 个步骤: - 从属于一个称为 "frontend" 的配置实体的监听套接字接受传入连接,该实体引用一个或多个监听地址; - 对这些连接应用前端特定的处理规则,这可能导致阻止它们、修改某些标头,或拦截它们以执行一些内部小程序,如统计页面或 CLI; - 将这些传入连接传递给另一个代表服务器场的配置实体,称为 "backend",其中包含服务器列表和该服务器场的负载均衡策略; - 对这些连接应用后端特定的处理规则; - 根据负载均衡策略决定将连接转发到哪个服务器; - 对响应数据应用后端特定的处理规则; - 对响应数据应用前端特定的处理规则; - 发出一条日志,详细报告发生了什么; - 在 HTTP 中,循环回到第二步以等待新请求,否则关闭连接。 前端和后端有时被认为是半个代理,因为它们只关注端到端连接的一侧;前端只关心客户端,而后端只关心服务器。HAProxy 也支持完整的代理,这正是前端和后端的结合。当需要 HTTP 处理时,配置通常会分为前端和后端,因为它们开启了许多可能性,任何前端都可以将连接传递给任何后端。对于纯 TCP 代理,使用前端和后端很少能带来好处,使用完整代理的配置可能更具可读性。
本节将列举 HAProxy 实现的许多功能,其中一些是任何现代负载均衡器通常期望的,而另一些则是 HAProxy 架构的直接优势。更高级的功能将在下一节中详细介绍。
代理是将数据在客户端和服务器之间通过两个独立的连接进行传输的行为。HAProxy 在代理和连接管理方面支持以下基本功能: - 为服务器提供一个干净的连接,以保护它们免受任何客户端侧的缺陷或攻击; - 监听多个 IP 地址和/或端口,甚至端口范围; - 透明接受:拦截针对任何任意 IP 地址的流量,即使该地址不属于本地系统; - 服务器端口无需与监听端口相关联,甚至可以通过一个固定的偏移量进行转换(对范围很有用); - 透明连接:在连接到服务器时,如果需要,可以伪造客户端(或任何)的 IP 地址; - 在多站点负载均衡器中为服务器提供一个可靠的返回 IP 地址; - 通过缓冲区和可能的短连接为服务器卸载,以减少其并发连接数和内存占用; - 优化 TCP 栈(例如 SACK)、拥塞控制,并减少 RTT 影响; - 支持两端不同的协议族(例如 IPv4/IPv6/Unix); - 超时强制执行:HAProxy 根据连接所处的阶段支持多个级别的超时,这样死掉的客户端或服务器,或攻击者,就不会被长时间占用资源; - 协议验证:对 HTTP、SSL 或有效负载进行检查,并拒绝无效的协议元素,除非被指示无论如何都要接受它们; - 策略执行:确保只有被允许的内容才能被转发; - 传入和传出的连接都可以限制在特定的网络命名空间(仅限 Linux),这使得构建跨容器、多租户的负载均衡器变得容易; - PROXY 协议即使对于非 HTTP 流量也能向服务器呈现客户端的 IP 地址。这是 HAProxy 的一个扩展,目前已被许多第三方产品采用,在撰写本文时至少包括以下这些: - 客户端:haproxy、stud、stunnel、exaproxy、ELB、squid - 服务器:haproxy、stud、postfix、exim、nginx、squid、node.js、varnish
根据谷歌工程师的说法,HAProxy 的 SSL 栈被认为是功能最丰富的之一 (http://istlsfastyet.com/)。使其功能相当完整的常用特性包括: - 基于 SNI 的多主机托管,对站点数量没有限制,并专注于性能。已知至少有一个部署运行着 50000 个域名及其各自的证书; - 支持通配符证书,减少了对大量证书的需求; - 基于证书的客户端认证,并可配置在未能提供有效证书时的策略。例如,这允许呈现一个不同的服务器场来重新生成客户端证书; - 对后端服务器的认证确保了后端服务器是真实的,而不是中间人; - 与后端服务器的认证让后端服务器知道连接到它的是预期的 haproxy 节点; - TLS NPN 和 ALPN 扩展使得可以可靠地卸载 SPDY/HTTP2 连接,并以明文形式将其传递给后端服务器; - OCSP stapling 通过在客户端请求证书状态时内联传递 OCSP 响应,进一步减少了首页加载时间; - 动态记录大小调整提供了高性能和低延迟,并通过让浏览器在数据包仍在传输中时开始获取新对象,显著减少了页面加载时间; - 可永久访问所有相关的 SSL/TLS 层信息,用于日志记录、访问控制、报告等。这些元素可以嵌入到 HTTP 标头中,甚至作为 PROXY 协议扩展,以便卸载了 SSL 的服务器能够获得所有它自己执行 SSL 终止时会拥有的信息。 - 检测、记录并阻止某些已知的攻击,即使是在易受攻击的 SSL 库上,例如影响某些 OpenSSL 版本的“心脏出血”攻击。 - 支持无状态会话恢复(RFC 5077 TLS Ticket 扩展)。TLS ticket 可以通过 CLI 更新,这为通过频繁轮换 ticket 来实现完美前向保密提供了方法。
HAProxy 非常注重可用性。因此,它关心服务器的状态,并向其他网络组件报告自身的状态: - 服务器状态通过每个服务器的参数进行持续监控。这确保了到服务器的路径对常规流量是可操作的; - 健康检查支持上下线转换的两种滞后机制,以防止状态抖动; - 检查可以发送到不同的地址/端口/协议:这使得检查一个被认为代表多个服务的单一服务变得容易,例如一个 HTTP+HTTPS 服务器的 HTTPS 端口。 - 服务器可以跟踪其他服务器并同时下线:这确保了托管多个服务的服务器可以原子性地失败,没有人会被发送到一个部分失败的服务器; - 代理可以部署在服务器上以监控负载和健康状况:服务器可能有兴趣独立于健康检查所能看到的情况报告其负载、操作状态、管理状态。通过在服务器上运行一个简单的代理,除了验证整个路径的健康检查之外,还可以考虑服务器对其自身健康状况的看法; - 提供多种检查方法:TCP 连接、HTTP 请求、SMTP hello、SSL hello、LDAP、SQL、Redis、send/expect 脚本,所有这些都可以带或不带 SSL; - 状态变化会在日志和统计页面中通知,并附带失败原因(例如,检测到失败时收到的 HTTP 响应)。在这种变化发生时,也可以向可配置的地址发送电子邮件; - 服务器状态也会在统计接口上报告,并可用于制定路由决策,以便根据其大小和/或健康状况将流量发送到不同的服务器场(例如,数据中心间链路丢失); - HAProxy 可以使用健康检查请求向服务器传递信息,例如它们的名称、权重、服务器场中其他服务器的数量等,以便服务器可以根据这些信息调整其响应和决策(例如,推迟备份以保留更多可用的 CPU); - 服务器可以使用健康检查报告比简单的开/关更详细的状态(例如,我希望停止,请停止发送新访客); - HAProxy 本身可以向外部组件(如路由器或其他负载均衡器)报告其状态,从而可以构建非常完整的多路径和多层基础设施。
就像任何严肃的负载均衡器一样,HAProxy 非常注重可用性,以确保最佳的全局服务连续性: - 只使用有效的服务器;其他服务器会自动从负载均衡服务器场中剔除;但在某些条件下,仍然可以强制使用它们; - 支持优雅关闭,这样可以在不影响任何连接的情况下将服务器从服务器场中移除; - 当活动服务器宕机时,备份服务器会自动启用并替换它们,以便在可能的情况下不会丢失会话。这也允许构建多条路径到达同一服务器(例如,多个网络接口); - 当太多服务器宕机时,能够为服务器场返回一个全局失败状态。这与监控功能相结合,使得上游组件可以为给定服务选择一个不同的负载均衡节点; - 无状态设计使得构建集群变得容易:通过设计,HAProxy 尽力确保最高的服务连续性,而无需存储可能在发生故障时丢失的信息。这确保了接管尽可能无缝; - 与标准的 VRRP 守护进程 keepalived 良好集成:HAProxy 可以轻松地将其状态告知 keepalived,并且能很好地处理浮动虚拟 IP 地址。 注意:仅在基于集群的解决方案(Heartbeat, ...)之上使用 IP 冗余协议(VRRP/CARP),因为它们提供最快、最无缝、最可靠的切换。
HAProxy 提供了一套相当完整的负载均衡功能,其中大部分功能在许多其他负载均衡产品中并不具备: - 支持不少于 10 种负载均衡算法,其中一些算法应用于输入数据,以提供无限的可能性。最常见的有:round-robin(轮询,适用于短连接,依次选择每个服务器)、leastconn(最少连接,适用于长连接,选择连接数最少的服务器中最近最少使用的那个)、source(源地址哈希,适用于 SSL 场或终端服务器场,服务器直接取决于客户端的源地址)、uri(URI 哈希,适用于 HTTP 缓存,服务器直接取决于 HTTP URI)、hdr(标头哈希,服务器直接取决于特定 HTTP 标头字段的内容)、first(首选,适用于生命周期短的虚拟机,所有连接都集中在尽可能小的服务器子集上,以便未使用的服务器可以关闭); - 以上所有算法都支持每个服务器的权重,这样就可以在一个服务器场中容纳不同代的服务器,或者将一小部分流量引导到特定的服务器(调试模式、运行下一个版本的软件等); - round-robin、leastconn 和一致性哈希支持动态权重;这允许通过 CLI 或甚至通过在服务器上运行的代理动态修改服务器权重; - 只要支持动态权重,就支持慢启动;这允许服务器逐步承接流量。对于需要在运行时编译类以及需要在满负荷运行前填充的冷缓存的脆弱应用程序服务器来说,这是一个重要功能; - 哈希可以应用于各种元素,如客户端源地址、URL 组件、查询字符串元素、标头字段值、POST 参数、RDP cookie; - 一致性哈希保护服务器场免受在添加或移除服务器时的大规模重新分布。这在大型缓存场中非常重要,并允许使用慢启动来重新填充冷缓存; - 许多内部指标,如每个服务器的连接数、每个后端的连接数、后端中可用的连接槽数量等,使得构建非常高级的负载均衡策略成为可能。
如果没有粘性(stickiness),应用负载均衡将毫无用处。HAProxy 提供了一套相当全面的可能性来将访问者维持在同一台服务器上,即使在服务器添加/移除、宕机/上线周期等各种事件中也是如此,并且某些方法被设计为能够抵抗多个负载均衡节点之间的距离,因为它们不需要任何复制: - 如果需要,粘性信息可以从不同的地方单独匹配和学习。例如,一个 JSESSIONID cookie 可以在 cookie 和 URL 中同时匹配。最多可以同时学习 8 个并行来源,每个来源都可以指向不同的粘性表; - 粘性信息可以来自请求或响应中可见的任何内容,包括源地址、TCP 负载的偏移量和长度、HTTP 查询字符串元素、标头字段值、cookie 等等。 - 粘性表在多主模式下在所有节点之间复制; - 常用元素如 SSL-ID 或 RDP cookie(用于 TSE 场)可直接访问,以方便操作; - 所有粘性规则都可以通过 ACL 动态设置条件; - 可以决定不粘附到某些服务器,如备份服务器,这样当主服务器恢复时,它会自动接管负载。这在多路径环境中经常使用; - 在 HTTP 中,通常首选不学习任何东西,而是操作一个专用于粘性的 cookie。为此,可以检测、重写、插入或为这样的 cookie 添加前缀,让客户端记住分配了哪个服务器; - 服务器可以在注销时决定更改或清除粘性 cookie,以便离开的访问者自动与服务器解绑; - 使用基于 ACL 的规则,还可以有选择地忽略或强制执行粘性,而不管服务器的状态如何;结合高级健康检查,这有助于管理员在将服务器向全世界展示之前,验证他们正在安装的服务器是否已启动并运行; - 一种创新的机制,用于在 cookie 上设置最大空闲时间和持续时间,确保在永不关闭的设备(智能手机、电视、家用电器)上可以平滑地停止粘性,而无需将其存储在持久存储上; - 多个服务器条目可以共享相同的粘性键,这样在多路径环境中当一条路径中断时,粘性不会丢失; - 软停止确保只有带有粘性信息的用户将继续访问他们被分配到的服务器,但没有新用户会去那里。
日志记录对于负载均衡器来说是一个极其重要的功能,首先因为负载均衡器经常被错误地指责为它所揭示问题的起因,其次因为它位于基础设施的关键点,所有正常和异常活动都需要在这里被分析并与其他组件关联起来。HAProxy 提供非常详细的日志,具有毫秒级的精度和精确的连接接受时间,可以在防火墙日志中搜索(例如,用于 NAT 关联)。默认情况下,TCP 和 HTTP 日志相当详细,包含故障排查所需的一切,例如源 IP 地址和端口、前端、后端、服务器、计时器(请求接收持续时间、排队持续时间、连接建立时间、响应头时间、数据传输时间)、全局进程状态、连接数、队列状态、重试次数、详细的粘性操作和断开原因、带有安全输出编码的头捕获。然后可以扩展或替换此格式,以包括任何采样数据、变量、捕获,从而获得非常详细的信息。例如,可以记录客户端访问的累计请求数或不同 URL 的数量。日志级别可以根据标准 ACL 按请求进行调整,因此可以自动静默一些被认为是污染的日志,而当一小部分流量出现某些异常行为时(例如,源地址的 URL 或 HTTP 错误过多)则发出警告。管理日志也以其自己的级别发出,以通知服务器的丢失或恢复等情况。每个前端和后端可以使用多个独立的日志输出,这简化了多租户环境。日志最好通过 UDP 发送,可以是 JSON 编码的,并且在可配置的行长度后被截断以保证交付。但也可以将它们发送到 stdout/stderr 或任何文件描述符,以及一个客户端可以订阅以检索它们的环形缓冲区。
HAProxy 提供一个基于 Web 的统计信息报告界面,具有身份验证、安全级别和范围。因此,可以为每个托管的客户提供他们自己的页面,只显示他们自己的实例。此页面可以位于常规网站的隐藏 URL 部分,这样就不需要打开新端口。此页面还可以报告其他 HAProxy 节点的可用性,以便一目了然地发现一切是否按预期工作。视图是综合性的,并且有很多详细信息可供访问(例如,错误原因、最后访问时间和最后更改时长等),这些信息也可作为 CSV 表格提供,其他工具可以导入该表格来绘制图表。页面可以自我刷新,以便在大显示器上用作监控页面。在管理模式下,该页面还允许更改服务器状态以简化维护操作。还提供了一个 Prometheus 导出器,以便统计信息可以根据部署以不同的格式进行消耗。
在本节中,列举了一些在 HAProxy 中非常常用但并非在其他负载均衡器上普遍存在的功能。
HAProxy 使用一套广泛的“样本获取函数”来支持信息采样。其原理是提取被称为样本的信息片段,以供立即使用。这用于粘性、构建条件、在日志中生成信息或丰富 HTTP 标头。 样本可以从各种来源获取: - 常量:整数、字符串、IP 地址、二进制块; - 进程:日期、环境变量、服务器/前端/后端/进程状态、字节/连接计数/速率、队列长度、随机数生成器等; - 变量:会话、请求、响应的变量; - 客户端连接:源和目标地址及端口,以及所有相关的统计计数器; - SSL 客户端会话:协议、版本、算法、密码套件、密钥大小、会话 ID、所有客户端和服务器证书字段、证书序列号、SNI、ALPN、NPN、客户端对某些扩展的支持; - 请求和响应缓冲区内容:任意偏移/长度的负载、数据长度、RDP cookie、SSL hello 类型的解码、TLS SNI 的解码; - HTTP(请求和响应):方法、URI、路径、查询字符串参数、状态码、标头值、位置标头值、cookie、捕获、认证、主体元素; 一个样本随后可以经过一系列被称为“转换器”的操作符进行一些转换。转换器消费一个样本并产生一个新的样本,可能类型完全不同。例如,一个转换器可以用来只返回输入字符串的整数长度,或者可以将一个字符串转换为大写。在最终使用之前,任意数量的转换器可以串联应用于一个样本。 在所有可用的样本转换器中,以下是最常用的: - 算术和逻辑运算符:它们使得可以对输入数据进行高级计算,例如计算比率、百分比或简单地从一个单位转换到另一个单位; - IP 地址掩码在需要按更大的网络对某些地址进行分组时非常有用; - 数据表示:URL 解码、base64、十六进制、JSON 字符串、哈希; - 字符串转换:提取固定位置、固定长度的子字符串,提取某些分隔符周围的特定字段,提取某些单词,更改大小写,应用基于正则表达式的替换; - 日期转换:转换为 HTTP 日期格式,本地时间与 UTC 互相转换,添加或删除偏移量; - 在粘性表中查找条目以查找统计信息或分配的服务器; - 从文件中进行基于映射的键值转换(主要用于地理定位)。
映射是一种强大的转换器类型,它通过在启动时将一个两列文件加载到内存中,然后从第一列查找每个输入样本,如果找到条目,则返回第二列中的相应模式,否则返回默认值。输出信息也是一个样本,它可以经历其他转换,包括其他映射查找。映射最常用于将客户端的 IP 地址转换为 AS 号或国家代码,因为它们支持网络地址的最长匹配,但它们可以用于各种其他目的。它们的部分优势在于它们可以从 CLI 或通过其他样本的某些操作进行动态更新,从而使它们能够存储和检索后续访问之间的信息。另一个优势来自基于二进制树的索引,这使得它们即使包含数十万个条目也极其快速,从而使地理位置的设置非常便宜且容易。
HAProxy 中的大多数操作都可以是条件性的。条件是通过使用逻辑运算符(AND、OR、NOT)组合多个 ACL 来构建的。每个 ACL 都是一系列基于以下元素的测试: - 一个样本获取方法,用于检索要测试的元素; - 一个可选的转换器系列,用于转换该元素; - 一个用于匹配的模式列表; - 一种匹配方法,用于指示如何将模式与样本进行比较。 例如,样本可以取自 HTTP 的 "Host" 标头,然后可以将其转换为小写,再使用正则表达式匹配方法与多个正则表达式模式进行匹配。 从技术上讲,ACL 与映射(maps)建立在相同的核心之上,它们共享完全相同的内部结构、模式匹配方法和性能。唯一真正的区别是,它们只返回“找到”或“未找到”,而不是返回一个样本。 在使用方面,ACL 模式可以在配置文件中内联声明,不需要单独的文件。ACL 可以被命名以便于使用或使配置易于理解。一个命名的 ACL 可以被多次声明,它会依次评估所有定义,直到有一个匹配为止。 提供了大约 13 种不同的模式匹配方法,其中包括 IP 地址掩码、整数范围、子字符串、正则表达式。它们像函数一样工作,就像任何编程语言一样,只有需要的部分才会被评估,所以当一个涉及 OR 的条件已经为真时,后续的条件就不会被评估;同样,当一个涉及 AND 的条件已经为假时,条件的其余部分也不会被评估。 声明的 ACL 数量没有实际限制,并且提供了一些常用的 ACL。然而,经验表明,使用大量命名 ACL 的设置很难进行故障排查,有时在内联使用匿名 ACL 会更容易,因为它需要较少的超出当前分析范围的引用。
HAProxy 实现了一种称为基于内容的交换(content-based switching)的机制。其原理是,一个连接或请求到达一个前端(frontend),然后处理该请求或连接所携带的信息,此时可以编写基于 ACL 的条件,利用这些信息来决定哪个后端(backend)将处理该请求。因此,流量会根据请求的内容被导向一个后端或另一个后端。 最常见的例子是使用 Host 标头和/或路径中的元素(子目录或文件名扩展名)来决定一个 HTTP 请求是针对静态对象还是应用程序,并将静态对象的流量路由到一个由快速轻量级服务器组成的后端,而将所有剩余的流量路由到一个更复杂的应用服务器,从而构成一个细粒度的虚拟主机解决方案。这对于让多种技术共存以形成一个更全局的解决方案非常方便。 内容交换的另一个用例是根据各种标准使用不同的负载均衡算法。缓存可能使用 URI 哈希,而应用程序则使用轮询。 最后但同样重要的是,它允许多个客户使用一小部分公共资源,通过强制实施每个后端(也就是每个客户)的连接限制。 内容交换规则的可扩展性非常好,尽管它们的性能可能取决于所用 ACL 的数量和复杂性。但也可以编写动态内容交换规则,其中样本值直接转换为后端名称,而完全不使用 ACL。据报道,这样的配置在生产环境中至少有 300,000 个后端也能正常工作。
粘性表(Stick-tables)通常用于存储粘性信息,即,保留对某个特定访问者被导向的服务器的引用。键(key)是与访问者关联的标识符(其源地址、连接的 SSL ID、HTTP 或 RDP cookie、从 URL 或负载中提取的客户编号等),而存储的值则是服务器的标识符。粘性表的键可以使用三种不同类型的样本:整数、字符串和地址。一个代理中只能引用一个粘性表,并且在任何地方都通过代理名称来指定它。最多可以并行跟踪 8 个键。服务器标识符在请求或响应处理期间,一旦键和服务器都已知,就会被提交。粘性表的内容可以以 active-active 模式与被称为“对等节点(peers)”的其他 HAProxy 节点以及在重载操作期间的新进程进行复制,以便所有负载均衡节点共享相同的信息,并在客户端请求分布在多个节点上时做出相同的路由决策。 由于粘性表是根据识别客户端的信息来索引的,它们也经常被用来存储额外的信息,例如每个客户端的统计数据。额外的统计数据会占用一些额外的空间,需要明确声明。可以存储的统计数据类型包括输入和输出带宽、并发连接数、一段时间内的连接速率和计数、错误的数量和频率、一些特定的标签和计数器等。为了支持保留这些信息而不必强制粘附到某个给定的服务器,实现了一个特殊的“跟踪”功能,允许同时跟踪来自不同表的最多 3 个键,而不管粘性规则如何。每个存储的统计数据都可以从 CLI 中搜索、转储和清除,并增加了实时故障排查的能力。虽然这个机制可以用来给回头客更高的优先级,或者根据良好或不良行为调整交付的服务质量,但它主要用于对抗服务滥用和更广泛的 DDoS 攻击,因为它允许构建复杂的模型,以高处理速度检测某些不良行为。
HAProxy 需要在许多地方处理字符串,例如日志、重定向、标头添加等。为了提供最大的灵活性,引入了“格式化字符串”的概念,最初是为了日志记录目的,因此它仍然被称为“log-format”。这些字符串包含转义字符,允许将各种动态数据(包括变量和样本获取表达式)引入字符串,甚至在结果被转换为字符串时调整编码(例如,添加引号)。这提供了一种强大的方法来构建标头内容、响应数据甚至响应模板,或自定义日志行。此外,为了保持大多数常见字符串的构建简单,提供了大约 50 个特殊标签作为日志中常用信息的快捷方式。
在一个从未为此设计的应用程序前面安装一个负载均衡器可能是一项具有挑战性的任务,如果没有合适的工具。在这种情况下,最常见的操作之一是调整请求和响应头,使负载均衡器看起来像是源服务器,并修复硬编码的信息。这包括更改请求中的路径(强烈建议不要这样做)、修改 Host 标头字段、修改重定向的 Location 响应标头字段、修改 cookie 的路径和域属性等等。 同样,许多服务器有些啰嗦,倾向于在响应中泄漏太多信息,使它们更容易受到针对性攻击。虽然理论上清理这些不是负载均衡器的职责,但在实践中,它位于基础设施中最好的位置,可以保证所有东西都被清理干净。 同样,有时负载均衡器将不得不拦截一些请求,并以重定向到新目标 URL 的方式响应。虽然有些人倾向于混淆重定向和重写,但这是两个完全不同的概念,因为重写使客户端和服务器看到不同的东西(并且对正在访问的页面的位置有分歧),而重定向则要求客户端访问新的 URL,以便它看到与服务器相同的位置。 为了做到这一点,HAProxy 支持各种重写和重定向的可能性,其中包括: - 在请求和响应中基于正则表达式的 URL 和标头重写。正则表达式是修改标头值最常用的工具,因为它们易于操作且易于理解; - 标头也可以根据格式化字符串附加、删除或替换,以便可以在其中传递信息(例如,客户端 TLS 算法和密码套件); - HTTP 重定向可以使用任何 3xx 代码重定向到相对、绝对或完全动态(格式化字符串)的 URI; - HTTP 重定向还支持一些额外的选项,例如设置或清除特定的 cookie、丢弃查询字符串、如果缺少则附加斜杠等等; - 一个强大的“return”指令允许使用动态内容甚至模板文件自定义响应的每个部分,如状态、标头、正文。 - 所有操作都支持基于 ACL 的条件;
HAProxy 做了很多工作来最大化服务可用性,为此,它花费了大量精力来保护服务器免受过载和攻击。 第一个也是最重要的一点是,只有完整且有效的请求才会被转发到服务器。最初的原因是 HAProxy 需要找到它需要的协议元素以与字节流保持同步,第二个原因是,在请求完成之前,无法知道是否有某些元素会改变其语义。这样做的直接好处是服务器不会暴露于无效或不完整的请求。这是对抗 slowloris 攻击的非常有效的保护措施,这种攻击对 HAProxy 几乎没有影响。 另一个重要点是,HAProxy 包含用于存储请求和响应的缓冲区,通过仅在请求完成时才将其发送到服务器,并从本地网络非常迅速地读取整个响应,服务器端连接的使用时间非常短,这最大限度地保留了服务器资源。对此的直接扩展是,HAProxy 可以人为地限制到服务器的并发连接数或未完成的请求数,这保证了即使在流量高峰期间服务器持续以 100% 的容量运行,也绝不会过载。所有超出的请求将被简单地排队,等待一个槽位释放后进行处理。最终,这种巨大的资源节省通常能确保服务器响应时间好得多,以至于最终实际上比使服务器过载还要快。排队的请求可以被重新分派到其他服务器,甚至在客户端中止时在队列中中止,这也保护了服务器免受“刷新效应”的影响,即访问者在加载缓慢的页面上每次点击“刷新”通常会引发一个新的请求,并使服务器保持在过载状态。慢启动机制也保护了正在重启的服务器免受高流量水平的影响,而此时它们仍在完成启动或编译某些类。 关于协议级别的保护,可以放宽 HTTP 解析器以接受不符合标准但无害的请求或响应,甚至修复它们。这使得有问题的应用程序在修复开发期间可以访问。同时,违规的消息会被完整捕获,并附有详细的报告,帮助开发人员在应用程序中发现问题。最危险的协议违规行为会被正确检测、处理和修复。例如,带有两个 Content-Length 头的畸形请求或响应,如果值完全相同则会被修复,如果不同则会被拒绝,因为这成了一个安全问题。协议检查不仅限于 HTTP,也适用于其他协议,如 TLS 或 RDP。 当检测到协议违规或攻击时,有多种选项可以响应用户,例如返回常见的“HTTP 400 bad request”,用 TCP reset 关闭连接,或在一个长延迟后伪造一个错误(“tarpit”)来迷惑攻击者。所有这些都有助于保护服务器,通过阻止违规客户端继续进行变得维护成本非常高的攻击。 HAProxy 还提供了一些更高级的选项来防止意外的数据泄露和会话交叉。它不仅可以记录可疑的服务器响应,还会记录并可选地阻止可能影响特定访问者机密性的响应。一个这样的例子是,一个可缓存的 cookie 出现在一个可缓存的响应中,这可能导致中间缓存将其传递给另一个访问者,从而导致意外的会话共享。
HAProxy 的设计旨在在常规生产环境中保持极高的稳定性和管理安全性。它以单个可执行文件的形式提供,不需要任何安装过程。多个版本可以轻松共存,这意味着可以(并且推荐)按重要性顺序逐步升级实例,而不是一次性迁移所有实例。 配置文件易于版本控制。配置检查是离线完成的,因此不需要重启一个可能会失败的服务。在配置检查期间,可以检测到一些高级错误(例如,一条规则隐藏了另一条,或者无法工作的粘性),并提出详细的警告和配置提示来修复它们。配置文件向后兼容性追溯到很久以前,1.5 版本仍然完全支持 13 年前编写的 1.1 版本的配置,而 1.6 版本仅放弃了对几乎未使用、已过时且可以用不同方式实现的关键字的支持。 配置和软件升级机制是平滑且非破坏性的,它允许新旧进程在系统上共存,每个进程处理自己的连接。系统状态、构建选项和库兼容性在启动时报告。 一些高级功能允许应用程序管理员平滑地停止一台服务器,检测到其上不再有活动后,将其下线,停止它,升级它,并确保在升级期间不承接任何流量,然后通过正常路径再次测试它而不向公众开放,所有这些都无需接触 HAProxy。这确保了即使是复杂的生产操作也可以在营业时间进行,所有技术资源都可用。 该进程试图尽可能节省资源,使用内存池来节省分配时间并限制内存碎片,一旦其内容发送出去就立即释放有效负载缓冲区,并支持强制执行严格的内存限制,超过该限制的连接必须等待缓冲区可用而不是分配更多内存。这个系统有助于在某些严格的环境中保证内存使用。 命令行接口(CLI)可通过 UNIX 或 TCP 套接字使用,以执行许多操作并检索故障排查信息。在此套接字上完成的所有操作都不需要更改配置,因此主要用于临时更改。使用此接口,可以更改服务器的地址、权重和状态,查询统计信息并清除计数器,转储并清除粘性表(可以按关键标准选择性地进行),转储并终止客户端和服务器端的连接,转储捕获的错误并对错误的确切原因和位置进行详细分析,转储、添加和删除 ACL 和映射中的条目,更新 TLS 共享密钥,动态地对任意前端应用连接限制和速率限制(在共享托管环境中很有用),以及禁用特定前端以释放监听端口(在禁止日间操作但仍需修复时很有用)。允许动态更新证书及其配置,以及启用和查询流量处理的每一步跟踪。 对于强制要求 SNMP 的环境,至少存在两种代理,一种随 HAProxy 源码提供,依赖于 Net-SNMP Perl 模块。另一种随商业软件包提供,不需要 Perl。两者在覆盖范围上大致相当。 通常建议在部署 HAProxy 的机器上安装 4 个实用程序: - socat(用于连接到 CLI,尽管某些 netcat 的分支在某种程度上也可以做到); - 最新 HAProxy 版本的 halog:这是日志分析工具,它能极快地(每秒 1 到 2 GB)解析原生的 TCP 和 HTTP 日志,并提取有用的信息和统计数据,如每个 URL 的请求数、每个源地址的请求数、按响应时间或错误率排序的 URL、终止代码等。它被设计为部署在生产服务器上,以帮助排查实时问题,因此必须随时可用; - tcpdump:强烈建议使用它来捕获网络跟踪,以便排查日志中显示的问题。在某个时刻,应用程序和 haproxy 的分析会出现分歧,网络跟踪是判断谁对谁错的唯一方法。通过 tcpdump 检测网络堆栈和 hypervisor 中的错误也相当常见; - strace:它是 tcpdump 的伴侣。它将报告 HAProxy 真正看到的内容,并帮助区分操作系统负责的问题和 HAProxy 负责的问题。当怀疑 HAProxy 中存在错误时,通常会要求使用 Strace;
根据 HAProxy 部署的操作系统,某些额外的功能可能会可用或需要。虽然它在许多平台上都受支持,但 HAProxy 主要在 Linux 上开发,这解释了为什么某些功能仅在该平台上可用。 透明绑定和连接功能、支持将连接绑定到特定网络接口,以及将多个进程绑定到相同 IP 地址和端口的能力仅在 Linux 和 BSD 系统上可用,尽管只有 Linux 会在内核端对传入请求在可用进程之间进行负载均衡。 在 Linux 上,还有许多额外的功能和优化,包括对网络命名空间(也称为“容器”)的支持,允许 HAProxy 成为所有容器之间的网关,能够在客户端连接上设置 MSS、Netfilter 标记和 IP TOS 字段,支持监听端的 TCP FastOpen,TCP 用户超时以让内核在检测到客户端在配置的超时之前消失时快速终止连接,TCP 拼接以让内核在连接的两侧之间转发数据从而避免多次内存复制,能够启用“defer-accept”绑定选项以便仅在内核缓冲区中有数据可用时才收到传入连接的通知,以及能够用确认连接的 ACK 一起发送请求(有时称为“piggy-back”),这通过“tcp-smart-connect”选项启用。在 Linux 上,HAProxy 还非常注意操作 TCP 延迟 ACK,以在网络上尽可能多地节省数据包。 有些系统的时钟不可靠,会在过去和未来之间来回跳跃。这曾经在一些 NUMA 系统上发生,其中多个处理器看到的时间不完全相同,最近在虚拟化环境中变得更常见,其中虚拟时钟与真实时钟没有关系,导致巨大的时间跳跃(有时观察到长达 30 秒)。这通常会对超时强制执行造成很多麻烦。由于这些系统的这个缺陷,HAProxy 维护自己的单调时钟,该时钟基于系统时钟,但会测量和补偿漂移。这确保了即使系统时钟非常糟糕,计时器也能保持合理的准确性,超时也能继续工作。请注意,这个问题会影响在这些系统上运行的所有软件,并非 HAProxy 特有。常见的影响是虚假超时或应用程序冻结。因此,如果在一个系统上检测到这种行为,就必须修复它,无论 HAProxy 是否会自我保护。 在 Linux 上,一个新的启动进程可以与前一个进程通信,以重用其监听文件描述符,从而在进程替换期间监听套接字永远不会中断。
HAProxy 可以通过 Lua 嵌入式语言进行构建,这为复杂的请求或响应操作、路由决策、统计处理等领域开辟了广泛的新可能性。使用 Lua 甚至可以建立到其他服务器的并行连接以交换信息。这样,例如,开发身份验证系统就成为可能(尽管很复杂)。有关如何使用 Lua 的更多信息,请参阅文件“doc/lua-api/index.rst”中的文档。
管理员可以在任何时候通过 CLI 连接并启用各种内部子系统的跟踪。默认提供不同级别的详细信息,因此实际上可以检索从每条请求一行到每条请求 500 行的内容。过滤器以及自动捕获开/关/暂停机制可用,因此实际上可以等待某个特定事件并详细观察它。这对于诊断故障服务器和客户端的协议违规或拒绝服务攻击非常方便。
典型的 CPU 使用数据显示,在 TCP 或 HTTP close 模式下,15% 的处理时间花在 HAProxy 上,而 85% 在内核中;在 HTTP keep-alive 模式下,大约 30% 用于 HAProxy,70% 用于内核。这意味着操作系统及其调优对全局性能有很大影响。用户的用法差异很大,一些人关注带宽,另一些人关注请求率,还有一些人关注连接并发性,还有一些人关注 SSL 性能。本节旨在提供一些元素来帮助完成这项任务。 重要的是要记住,每个操作都有成本,所以每个单独的操作都会在其他操作之上增加其开销,这在某些情况下可能微不足道,但在其他情况下可能占主导地位。当处理来自一个连接的请求时,我们可以说: - 转发数据的成本低于解析请求或响应头; - 解析请求或响应头的成本低于建立然后关闭到服务器的连接; - 建立和关闭连接的成本低于 TLS 恢复操作; - TLS 恢复操作的成本低于带密钥计算的完整 TLS 握手; - 空闲连接的 CPU 成本低于其缓冲区中有数据的连接; - TLS 上下文占用的内存甚至比有数据的连接还多; 因此,在实践中,处理有效负载字节比处理头字节更便宜,因此用大对象(每单位体积请求少)比用小对象(每单位体积请求多)更容易实现高网络带宽。这解释了为什么最大带宽总是用大对象来测量,而请求率或连接率则用小对象来测量。 一些操作在分布在多个 CPU 上的多个进程上扩展得很好,而另一些则不那么好。网络带宽不能扩展得很远,因为对于大对象来说,CPU 很少是瓶颈,主要是网络带宽和到达网络接口的数据总线。由于处理本地端口表时系统中的一些锁,连接率在多个处理器上扩展得不好。持久连接上的请求率扩展得非常好,因为它不涉及太多内存或网络带宽,也不需要访问被锁定的结构。TLS 密钥计算扩展得非常好,因为它是完全 CPU 密集型的。TLS 恢复扩展得中等好,但在大约 4 个进程时达到极限,此时访问共享表的开销抵消了从更多算力中获得的微小收益。 一个调优得非常好的系统可以预期的性能数字在以下范围内。重要的是将它们视为数量级,并预期会因处理器、IRQ 设置、内存类型、网络接口类型、操作系统调优等因素而在任何方向上出现显著变化。以下数字是在运行 Linux 内核 3.10、HAProxy 1.6 和 OpenSSL 1.0.2 的 Core i7 @ 3.7 GHz 上发现的,该机器配备了双端口 10 Gbps 网卡。HAProxy 作为单个进程在单个专用 CPU 内核上运行,另外两个内核专用于网络中断: - 对于 256 kB 或更大的对象,明文最大网络带宽为 20 Gbps,对于 41 kB 或更大的对象为 10 Gbps; - 使用 AES256-GCM 密码套件的大对象,TLS 流量为 4.6 Gbps; - 每秒 83,000 次从客户端到服务器的 TCP 连接; - 每秒 82,000 次从客户端到服务器的 HTTP 连接; - 在服务器关闭模式下每秒 97,000 次 HTTP 请求(与客户端保持 keep-alive,与服务器关闭连接); - 在端到端 keep-alive 模式下每秒 243,000 次 HTTP 请求; - 每秒 300,000 次过滤的 TCP 连接(反 DDoS); - 在持久 TLS 连接上以 keep-alive 模式每秒 160,000 次 HTTPS 请求; - 使用 TLS 恢复连接每秒 13,100 次 HTTPS 请求; - 使用 RSA2048 重新协商的 TLS 连接每秒 1,300 次 HTTPS 连接; - 每 GB RAM 约 20,000 个并发饱和连接,包括系统缓冲区所需的内存;通过仔细调优可以做得更好,但这个结果很容易实现。 - 每 GB RAM 约 8,000 个并发 TLS 连接(仅客户端侧),包括系统缓冲区所需的内存; - 每 GB RAM 约 5,000 个并发端到端 TLS 连接(双侧),包括系统缓冲区所需的内存; 最近一次基准测试,在 AWS 的 64 核 ARM Graviton2 处理器上运行启用多线程的 HAProxy 2.4,在亚毫秒响应时间内达到了每秒 200 万次 HTTPS 请求和 100 Gbps 的流量: https://www.haproxy.com/blog/haproxy-forwards-over-2-million-http-requests-per-second-on-a-single-aws-arm-instance/ 因此,一个好的经验法则是,TLS keep-alive 和 TLS 恢复之间的请求率会除以 10,TLS 恢复和 TLS 重新协商之间也是如此,而 HTTP keep-alive 和 HTTP close 之间只除以 3。另一个好的经验法则是记住,一个带有 AES 指令的高频核心每核心可以处理大约 20 Gbps 的 AES-GCM。另一个好的经验法则是,在同一台服务器上,HAProxy 将能够使以下服务饱和: - 大约 5-10 个静态文件服务器或缓存代理; - 大约 100 个反病毒代理; - 大约 100-1000 个应用服务器,具体取决于所使用的技术。
HAProxy 是一个遵循 GPLv2 许可的开源项目,这意味着每个人都可以重新分发它,但前提是必须在被请求时提供源代码的访问权限,特别是如果进行了任何修改。HAProxy 的主要开发分支被称为“master”或“mainline”,一旦代码被认为稳定,就会从中派生出新的分支。很多网站自愿在生产环境中使用一些开发分支,或是为了参与项目,或是因为他们需要某个最前沿的功能,他们的反馈对于修复错误以及判断正在开发的版本的整体质量和稳定性非常有价值。当代码足够稳定时创建的新分支构成一个稳定版本,并且通常会维护数年,所以即使你没有使用最新的分支,也无需紧急迁移到新分支。一个稳定分支一旦发布,通常只接收错误修复,极少数情况下会进行次要的功能更新,前提是这能让用户的生活更轻松。所有进入稳定分支的修复都必须来自 master 分支。这保证了在升级后不会丢失任何修复。因此,如果你修复了一个错误,请针对 master 分支制作补丁,而不是稳定分支。你甚至可能会发现这个错误已经被修复了。这个过程也确保了稳定分支中的回归问题极为罕见,所以没有任何理由不升级到你当前分支的最新版本。分支号由两个用点分隔的数字组成,例如“1.6”。从 1.9 版本开始,第二个数字为奇数的分支主要侧重于敏感的技术更新,更多地面向高级用户,因为它们比其他分支更容易引发错误。它们只维护大约一年,并且绝不能部署在紧急情况下无法回滚的环境中。一个完整的版本号会包含一个或两个子版本号,用以表示修复的级别。例如,版本 1.5.14 是 1.5 分支在 1.5.0 版本发布后的第 14 个修复版本。它包含了 126 个针对单个错误的修复、24 个文档更新以及 75 个其他向后移植的补丁,其中大部分是为了修复上述 126 个错误。在稳定分支中,现有功能绝不会被修改或移除,以保证在同一分支内的升级总是无害的。HAProxy 可从多个来源获取,发布节奏各不相同: - 官方社区网站:https://haproxy.cn/:此网站提供最新开发版本、所有稳定版本的源代码,以及每个分支的每日快照。发布周期不快,稳定版本之间或开发快照之间相隔数月。非常老的版本在那里仍然受到支持。所有内容仅以源代码形式提供,因此从那里获取的任何东西都需要重新构建和/或重新打包; - GitHub:https://github.com/haproxy/haproxy/:这只是开发分支的镜像,它提供了与问题跟踪器、持续集成和代码覆盖率工具的集成。这专为贡献者准备; - 许多操作系统,如 Linux 发行版和 BSD ports。这些系统通常提供长期维护的版本,这些版本不一定包含官方版本的所有修复,但至少包含关键修复。对于大多数不寻求高级配置、只想让更新变得简单的用户来说,这通常是一个不错的选择; - 来自 http://www.haproxy.com/ 的商业版本:这些是为各种操作系统构建或作为设备提供的受支持的专业软件包,基于最新的稳定版本,并包含许多从下一个版本向后移植的、有强烈需求的功能。对于寻求最新功能并希望拥有稳定分支的可靠性、最快的错误修复响应时间,或者只是想在开源产品之上获得支持合同的用户来说,这是最佳选择;为了确保你使用的版本是你所在分支的最新版本,你需要按以下步骤操作: - 验证你正在运行哪个 HAProxy 可执行文件:一些系统默认附带它,而管理员会将他们的版本安装在系统的其他地方,因此检查启动脚本中使用的是哪一个非常重要; - 确定你的 HAProxy 版本来自哪个来源。为此,通常输入“haproxy -v”就足够了。开发版本会像这样显示,在分支号后面带有“dev”字样: HAProxy version 2.4-dev18-a5357c-137 2021/05/09 - https://haproxy.cn/ 稳定版本会像这样显示,操作系统供应商提供的未经修改的稳定版本也是如此: HAProxy version 1.5.14 2015/07/02 而稳定版本的每日快照会像这样显示,版本号后带有一个十六进制序列,并且日期是快照日期而不是发布日期: HAProxy version 1.5.14-e4766ba 2015/07/29 任何其他格式都可能表示一个带有其自身补丁集的特定于系统的软件包。例如,HAProxy 企业版会以以下格式显示(<分支>-<最新提交>-<修订版>): HAProxy version 1.5.0-994126-357 2015/07/02 请注意,历史上 2.4 之前的版本在报告进程名称时,在“HA”和“Proxy”之间会有一个连字符,包括上面那些为了只显示正确格式而调整过的例子,所以在脚本中最好忽略这个词或使用宽松匹配。此外,现代版本会添加一个链接到项目主页的 URL。最后,2.1 及以上版本将包含一个“Status”行,指示该版本是否可以安全用于生产环境,如果可以,截止到什么时候,以及一个指向影响此版本的已知错误列表的链接。 - 对于特定于系统的软件包,你必须通过你的供应商的软件包仓库或更新系统来检查,以确保你的系统仍在支持期内,并且你的分支仍在获得修复。对于来自 haproxy.org 的社区版本,只需访问该网站,验证你分支的状态,并将最新版本与你的版本进行比较,看看你是否是最新版本。如果不是,你可以升级。如果你的分支不再被维护,那么你肯定已经落后太多了,需要考虑升级到一个更新的分支(这样做时请仔细阅读 README 文件)。HAProxy 的更新方式应与其来源一致。通常它遵循系统供应商升级软件包的方式。如果它是从源代码获取的,请在解压源代码后阅读源代码目录中的 README 文件,并按照你操作系统的说明进行操作。
HAProxy 与下面列出的一些产品集成得相当好,因此即使它们不直接与 HAProxy 相关,也在此处提及。
Apache 是事实上的标准 HTTP 服务器。它是一个非常完整和模块化的项目,支持文件服务和动态内容。它可以作为某些应用服务器的前端。它甚至可以代理请求和缓存响应。在所有这些用例中,通常都需要一个前端负载均衡器。Apache 可以在多种模式下工作,其中一些模式比其他模式更重。某些模块仍然需要更重的预派生(pre-forked)模型,这将妨碍 Apache 在高连接数下的良好扩展。在这种情况下,HAProxy 可以提供巨大的帮助,它能将每个服务器的连接数限制在一个安全值内,并显著加快服务器速度,保护其资源,使其能更好地被应用程序使用。Apache 可以通过使用“mod_rpaf”扩展从 X-Forwarded-For 头部提取客户端地址。当 HAProxy 的配置中指定了“option forwardfor”时,它会自动填充此头部。当 Apache 暴露在互联网上时,HAProxy 还可以为其提供良好的保护,更好地抵御多种类型的 DoS 攻击。
NGINX 是第二个事实上的标准 HTTP 服务器。和 Apache 一样,它涵盖了广泛的功能。NGINX 的构建模型与 HAProxy 类似,因此处理数万个并发连接没有问题。当用作某些应用程序的网关时(例如,使用附带的 PHP FPM),设置一些前端连接限制以减少 PHP 应用程序的负载通常是有益的。HAProxy 在这里显然很有用,既可以作为常规的负载均衡器,也可以作为流量调节器,通过为其减压来加速 PHP。此外,由于这两种产品都因其事件驱动架构而占用极少的 CPU,所以通常很容易将它们都安装在同一个系统上。NGINX 实现了 HAProxy 的 PROXY 协议,因此 HAProxy 可以轻松地将客户端的连接信息传递给 NGINX,从而使应用程序获得所有相关信息。一些基准测试还表明,对于大型静态文件服务,在 NGINX 前端使用 HAProxy 实现一致性哈希可能是有益的,因为它能优化操作系统的缓存命中率,该命中率基本上会乘以服务器节点的数量。
Varnish 是一个智能的缓存反向代理,可能最恰当的描述是 Web 应用加速器。Varnish 不实现 SSL/TLS,并希望将其所有的 CPU 周期都投入到它最擅长的事情上。Varnish 也实现了 HAProxy 的 PROXY 协议,因此 HAProxy 可以非常容易地部署在 Varnish 前端,作为 SSL 卸载器和负载均衡器,并将所有相关的客户端信息传递给它。此外,当服务器提供了压缩对象时,Varnish 天然支持从缓存中解压缩,但它本身不进行压缩。因此,当后端服务器不实现压缩时,可以使用 HAProxy 来压缩传出的数据,尽管除非流量很低,否则在负载均衡器上进行压缩很少是个好主意。当在多个节点上构建大型缓存集群时,HAProxy 可以利用一致性 URL 哈希来智能地将负载分配到缓存节点,并避免缓存重复,从而使总缓存大小等于所有缓存节点的总和。此外,在 HAProxy 上对非常小的、简单的对象进行短时缓存,有时可以节省网络往返次数,并减少 HAProxy 和 Varnish 节点的 CPU 负载。这只有在 Varnish 不对这些对象进行任何处理的情况下才可能实现(这通常被称为“favicon 缓存”的概念,通过这种方式有时可以避免相当大比例的无用下游请求)。但是,不要在任何其他缓存前端长时间(超过几秒钟)启用 HAProxy 缓存,这会显著增加故障排查的复杂性,而不会带来真正显著的节省。
Linux Virtual Server(LVS 或 IPVS)是包含在 Linux 内核中的第 4 层负载均衡器。它在数据包级别工作,处理 TCP 和 UDP。在大多数情况下,它更多的是一种补充而不是替代品,因为它完全没有第 7 层的知识。Pound 是另一款著名的负载均衡器。它比 HAProxy 简单得多,功能也少得多,但对于许多非常基础的配置,两者都可以使用。其作者一直将代码可审计性放在首位,并希望保持功能集的精简。其基于线程的架构在高连接数下的扩展性较差,但它是一款不错的产品。Pen 是一款相当轻量的负载均衡器。它支持 SSL,通过一个固定大小的客户端 IP 地址表来维持持久性。它支持一种面向数据包的模式,使其能够在一定程度上支持直接服务器返回(DSR)和 UDP。它适用于小负载(其持久性表只有 2048 个条目)。NGINX 可以在一定程度上进行负载均衡,尽管这显然不是它的主要功能。它使用生产流量来检测服务器故障,负载均衡算法较为有限,粘性会话(stickiness)功能也非常有限。但在一些它已经存在的简单部署场景中,使用它也是合理的。好处在于,由于它与 HAProxy 集成得很好,当达到其极限时,再添加 HAProxy 也是没有问题的。Varnish 也可以对其后端服务器进行一些负载均衡,并支持真正的健康检查。但它不实现粘性会话,所以就像 NGINX 一样,只要不需要粘性会话,它就足以应付初期需求。同样地,由于 HAProxy 和 Varnish 集成得非常好,以后也很容易将其加入进来以补充功能集。
如果你想就任何事情联系开发者或任何社区成员,最好的方式通常是通过邮件列表,将你的邮件发送到 haproxy@formilux.org。请注意,这个列表是公开的,其存档也是公开的,所以你应该避免泄露敏感信息。这里有数千名不同经验水平的用户,即使是最复杂的问题通常也能相对快速地找到最佳答案。也欢迎提出建议。对于使用电子邮件有困难的用户,可以使用一个 Discourse 平台,地址是 http://discourse.haproxy.org/ 。但请记住,在那里阅读问题的人较少,而且大部分问题都由一个非常小的团队处理。在任何情况下,请对那些奉献自己业余时间帮助他人的人保持耐心和尊重。如果你认为你发现了一个错误但不确定,最好在邮件列表上报告。如果你相当确信你发现了一个错误,你的版本在其分支中是最新的,并且你已经有了一个 GitHub 账户,请随时直接访问 https://github.com/haproxy/haproxy/ 并提交一个 issue,附上所有可能获得的详细信息。再次强调,这是公开的,所以小心不要发布你以后可能会后悔的信息。由于 issue tracker 本身呈现为一个很长的帖子,请避免粘贴非常长的转储内容(几百行或更多),而是将它们作为附件上传。如果你发现了你绝对确定可以被视为严重安全问题,且在公共场所讨论会使许多用户陷入严重麻烦的情况,那么你可以将其连同复现方法发送到 security@haproxy.org。一个由受信任的开发者组成的小团队将会收到它,并能够提出修复方案。我们通常不使用禁令期(embargoes),一旦修复方案可用,它就会被合并。在极少数情况下,可能会与软件供应商协调发布。请注意,这个过程通常会打乱每个人的工作,而且仓促发布的版本有时会引入新的错误,所以除非绝对必要,否则最好避免;因此,对于那些不必要地造成这种额外负担的报告,通常很少会得到重视,而让你的工作获得认可的最好方式通常是提供一个可行的修复方案,它将出现在变更日志中。