nginx 反向代理大大增加了最坏情况下的延迟

nginx 反向代理大大增加了最坏情况下的延迟

(编辑:部分理解并解决,见评论)

我有一个设置,nginx 在 CherryPy 应用服务器前面充当反向代理。我使用 ab 来比较通过 nginx 与不通过 nginx 的性能,并注意到前一种情况的最坏情况性能要差得多:

$ ab -n 200 -c 10 'http://localhost/noop'
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software:        nginx
Server Hostname:        localhost
Server Port:            80

Document Path:          /noop
Document Length:        0 bytes

Concurrency Level:      10
Time taken for tests:   3.145 seconds
Complete requests:      200
Failed requests:        0
Write errors:           0
Total transferred:      29600 bytes
HTML transferred:       0 bytes
Requests per second:    63.60 [#/sec] (mean)
Time per request:       157.243 [ms] (mean)
Time per request:       15.724 [ms] (mean, across all concurrent requests)
Transfer rate:          9.19 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:     5   48 211.7     31    3007
Waiting:        5   48 211.7     31    3007
Total:          5   48 211.7     31    3007

Percentage of the requests served within a certain time (ms)
  50%     31
  66%     36
  75%     39
  80%     41
  90%     46
  95%     51
  98%     77
  99%    252
 100%   3007 (longest request)
$ ab -n 200 -c 10 'http://localhost:8080/noop'
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software:        CherryPy/3.2.0
Server Hostname:        localhost
Server Port:            8080

Document Path:          /noop
Document Length:        0 bytes

Concurrency Level:      10
Time taken for tests:   0.564 seconds
Complete requests:      200
Failed requests:        0
Write errors:           0
Total transferred:      27600 bytes
HTML transferred:       0 bytes
Requests per second:    354.58 [#/sec] (mean)
Time per request:       28.202 [ms] (mean)
Time per request:       2.820 [ms] (mean, across all concurrent requests)
Transfer rate:          47.79 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   1.7      0      11
Processing:     6   26  23.5     24     248
Waiting:        3   25  23.6     23     248
Total:          6   26  23.4     24     248

Percentage of the requests served within a certain time (ms)
  50%     24
  66%     27
  75%     29
  80%     31
  90%     34
  95%     40
  98%     51
  99%    234
 100%    248 (longest request)

这可能是什么原因造成的?我唯一能想到的是,nginx 向后端发送请求的顺序与请求到达的顺序不同,但这似乎难以置信。

该机器是具有 2 个核心的 EC2 c1.medium 实例,CherryPy 使用具有 10 个线程的线程池,并且 nginx 具有 worker_connections = 1024。

更新:另外两个令人困惑的发现:

  • 在给定并发性的情况下,发送更多请求可提高性能。在并发性为 40 和 40 个请求的情况下,我得到的中位数时间为 3 秒,最大值为 10.5 秒;在并发性为 40 和 200 个请求的情况下,我得到的中位数为 38 毫秒(!),最大值为 7.5 秒。事实上,200 个请求的总时间更少!(6.5 秒 vs. 40 个请求的 7.5 秒)。这一切都是可重复的。
  • 使用 strace 监控两个 nginx 工作进程可大大提高其性能,例如,将平均时间从 3 秒缩短至 77 毫秒,而不会明显改变其行为。(我使用非平凡的 API 调用进行了测试,并确认 strace 不会改变响应,并且所有这些性能观察结果仍然有效。)这也是可重复的。

答案1

第一次运行中 3 秒的最坏情况ab看起来像是数据包丢失。这可能是由于配置的缓冲区/资源不足造成的,以下是一些可能的原因(无特定顺序):

  • 后端的监听队列太小,导致偶尔的监听队列溢出(在这种情况下,Linux 通常配置为仅丢弃 SYN 数据包,因此无法区分是数据包丢失;查看netstat -s | grep listen是否是问题所在)。
  • 本地主机上的状态防火墙接近其状态数量的限制,并因此丢弃一些随机 SYN 数据包。
  • 由于套接字处于 TIME_WAIT 状态,系统没有套接字/本地端口,请参阅这个问题如果您使用的是 Linux。

您必须仔细检查操作系统以找出原因并相应地配置操作系统。您可能还需要遵循一些针对操作系统的网络子系统调优指南。请注意,EC2 在这里可能有点具体,因为有报告称 EC2 实例上的网络性能非常有限。

从 nginx 的角度来看,任何解决方案或多或少都是错误的(因为问题不在于 nginx,而在于无法应对负载并丢弃数据包的操作系统)。不过,您可以尝试一些技巧来减少操作系统网络子系统的负载:

  • 配置保持与后端的连接
  • 配置后端来监听 unix 域套接字(如果您的后端支持它),并配置 nginx 来代理对它的请求。

答案2

NGINX 使用 HTTP/1.0 进行后端连接,默认情况下没有 keepalive(请参阅 Maxim 文章中有关后端 keepalive 的链接),因此这意味着为每个请求建立一个新的后端连接,这会稍微增加延迟。您可能还应该有更多工作进程,2* CPU 核心数,至少 5 个。如果您有超过 10 个并发请求,您可能还需要 CherryPy 中的更多线程。

相关内容