跳转到主要内容

12. 调试与性能问题

排查崩溃、卡顿、延迟和吞吐量问题的方法

当 HAProxy 以 “-d” 选项启动时,它将以前台模式运行,并为每个事件打印一行输出,例如接收到的连接、连接结束,以及每个请求或响应头行。此调试输出在内容被处理前发出,因此不会考虑本地修改。主要用途是无需运行网络嗅探器即可查看请求和响应。当多个连接并行处理时,输出可读性会降低,但位于 examples/ 目录中的 “debug2ansi” 和 “debug2html” 脚本可显著改善此问题,通过为输出着色提升可读性。

如果 HAProxy 发现 HTTP/1.x 请求或响应格式错误而将其拒绝,最佳做法是连接到 CLI 并执行 “show errors” 命令。该命令将报告每个前端和后端最后捕获的故障 HTTP/1.x 请求和响应,包含所有必要信息,以精确定位被拒绝的输入流中的首个字符。此信息有时用于向客户或开发人员证明其代码中存在缺陷。在此情况下,可以使用 “option accept-unsafe-violations-in-http-request” 来放宽请求检查(但仍保留捕获功能),或使用其对应选项 “option accept-unsafe-violations-in-http-response” 来放宽来自服务器的响应检查。具体详情请参见配置手册。

示例:

> show errors
Total events captured on [13/Oct/2015:13:43:47.169]: 1

[13/Oct/2015:13:43:40.918] frontend HAProxyLocalStats (#2): invalid request
  backend <NONE> (#-1), server <NONE> (#-1), event #0
  src 127.0.0.1:51981, session #0, session flags 0x00000080
  HTTP msg state 26, msg flags 0x00000000, tx flags 0x00000000
  HTTP chunk len 0 bytes, HTTP body len 0 bytes
  buffer flags 0x00808002, out 0 bytes, total 31 bytes
  pending 31 bytes, wrapping at 8040, error at position 13:

  00000  GET /invalid request HTTP/1.1\r\n

CLI 中的 “show info” 命令输出提供了多项有用信息,包括历史上达到的最高连接速率、最高 SSL 密钥速率,以及一般情况下有助于解释 CPU 或内存使用异常的各类信息。示例:

> show info
Name: HAProxy
Version: 1.6-dev7-e32d18-17
Release_date: 2015/10/12
Nbproc: 1
Process_num: 1
Pid: 7949
Uptime: 0d 0h02m39s
Uptime_sec: 159
Memmax_MB: 0
Ulimit-n: 120032
Maxsock: 120032
Maxconn: 60000
Hard_maxconn: 60000
CurrConns: 0
CumConns: 3
CumReq: 3
MaxSslConns: 0
CurrSslConns: 0
CumSslConns: 0
Maxpipes: 0
PipesUsed: 0
PipesFree: 0
ConnRate: 0
ConnRateLimit: 0
MaxConnRate: 1
SessRate: 0
SessRateLimit: 0
MaxSessRate: 1
SslRate: 0
SslRateLimit: 0
MaxSslRate: 0
SslFrontendKeyRate: 0
SslFrontendMaxKeyRate: 0
SslFrontendSessionReuse_pct: 0
SslBackendKeyRate: 0
SslBackendMaxKeyRate: 0
SslCacheLookups: 0
SslCacheMisses: 0
CompressBpsIn: 0
CompressBpsOut: 0
CompressBpsRateLim: 0
ZlibMemUsage: 0
MaxZlibMemUsage: 0
Tasks: 5
Run_queue: 1
Idle_pct: 100
node: wtap
description:

当 HAProxy 新版本中出现看似随机的问题(例如:每第二个请求被中止、偶发崩溃等)时,建议尝试启用内存污染功能。该功能可使每次调用 malloc() 后立即用可配置字节填充内存区域。默认情况下,该字节为 0x50(ASCII 码中的 ‘P’),但也可使用任意其他字节,包括零(其效果等同于 calloc(),可能使问题消失)。通过命令行选项 “-dM” 启用内存污染。该功能会轻微影响性能,不建议在生产环境中使用。若问题在启用该功能后始终存在,或在使用字节零进行污染时完全不出现,则明确表明已发现缺陷,务必报告。否则,若无明显变化,则问题与此无关。

在排查延迟问题时,必须在本地机器上同时使用 strace 和 tcpdump,并在远程系统上另启一个 tcpdump。原因在于处理链中的每个环节都可能存在延迟,必须明确是哪一个环节导致了延迟,才能确定应对方向。实际操作中,本地 tcpdump 会显示输入数据到达的时间点;strace 会显示 HAProxy 接收这些数据的时间点(通过 recv/recvfrom 系统调用)。请注意,OpenSSL 使用 read()/write() 系统调用而非 recv()/send()。strace 还会显示 HAProxy 发送数据的时间点,而 tcpdump 会显示系统将数据发送至网卡的时间点。随后,外部 tcpdump 会显示数据实际被接收的时间点(因为本地 tcpdump 仅显示数据入队时间)。在本地系统上进行嗅探的优势在于,strace 和 tcpdump 使用相同的参考时钟。strace 应配合 “-tts200” 使用,以获取完整的时间戳,并报告足够大的数据块以便读取。tcpdump 应配合 “-nvvttSs0” 使用,以报告完整数据包、真实序列号和完整时间戳。

在实际应用中,HAProxy 几乎总是立即接收到数据(除非机器的 CPU 已饱和,或这些数据无效且未被传递)。如果数据已接收但未发送,通常是因为输出缓冲区已饱和(即接收方消耗数据的速度不够快)。可通过观察轮询机制在一段时间内未通知输出文件描述符可写状态来确认这一点(在 strace 输出中,通常更容易发现数据最终发出的时间点,然后回溯查看写事件何时被通知)。这通常与接收方返回的 ACK 匹配,可通过 tcpdump 检测到。数据发送后,可能在系统中停留一段时间而无任何操作。此时,TCP 拥塞窗口可能受限,无法允许这些数据离开,需等待 ACK 以打开窗口。若流量空闲,数据耗时 40 ms 或 200 ms 才能发出,属于不同问题(并非问题),这是 Nagle 算法阻止空包立即发出,以期后续数据能与其合并。HAProxy 在纯 TCP 模式和隧道中会自动禁用 Nagle。但在转发 HTTP 正文时,Nagle 仍明确启用,这有助于提升性能,通过减少数据包数量。部分不符合 HTTP 规范的应用程序可能对不完整 HTTP 响应消息的延迟敏感。此时需启用 “option http-no-delay” 以禁用 Nagle,从而绕过其设计缺陷,但需注意链路中的其他代理也可能受到类似影响。若 tcpdump 显示数据立即发出,但对端未及时收到,可能表示存在拥塞的广域网链路,或启用了流量控制的局域网导致数据无法发出,更常见的情况是 HAProxy 实际运行在虚拟机中,由于某种原因,虚拟机管理器决定数据无需立即发送。在虚拟化环境中,延迟问题几乎总是由虚拟化层引起,因此为节省时间,建议首先对比虚拟机内部与外部组件的 tcpdump 输出。任何差异均应归因于虚拟机管理器及其配套驱动。

当在 tcpdump 追踪中观察到某些 TCP SACK 段(使用 -vv)时,始终意味着发送方已获得数据包丢失的证据。虽然未观察到 SACK 段并不表示不存在丢包,但若观察到 SACK 段,则明确表明网络存在丢包。网络中出现丢包是正常现象,但丢包率低到肉眼难以察觉的程度。若在追踪中频繁出现 SACK 段,则应深入调查具体发生了什么以及数据包在何处丢失。HTTP 对 TCP 丢包的适应能力较差,会导致延迟显著增加。

“netstat -i” 命令将报告每个接口的统计信息。若某个接口的 Rx-Ovr 计数器持续增长,表明系统资源不足以接收所有传入数据包,导致数据包在被网络驱动程序处理前丢失。Rx-Drp 表示部分接收的数据包因应用程序处理速度不足而在网络协议栈中丢失。在某些攻击期间也可能发生此类情况。Tx-Drp 表示输出队列已满,数据包不得不被丢弃。使用 TCP 时这种情况应极为罕见,但可能表明出站链路已饱和。