实验观察Nginx中keepalive_timeout参数的具体作用

实验背景和原理

在默认情况下,浏览器向nginx发起请求时,http请求头中设置的是connection:keep-alive,可以利用这个连接来传输多个文件,例如js/html/css/jpg等,从而避免重复建立多个连接的开销,所以默认是keepalive,所以在通信完成后,浏览器不会主动关闭连接的。即使客户端设置了保持连接,服务器也可以主动关闭此连接,这里暂时先不讨论。

这样就产生一个问题,在高并发场景下,短时间内会产生大量的这样的连接,每个连接会占用一定的服务器资源(主要占用内存,占用打开文件数,同时如果是代理的话,要考虑nginx向后端服务,例如php发起连接,这样的连接应该不多),这不是我们希望看到的,服务端希望客户端如果完成访问,就尽快释放连接,避免长时间消耗资源。

所以调整一个合适值,对于服务效能很重要,一方面要避免重复建立连接的开销,另一方面要避免客户端长期占用连接资源不放。

关于nginx中keepalive_timeout的用法:

使用环境:http, server, location

使用格式:keepalive_timeout arg1 [arg2]

keepalive_timeout接收2个参数:

arg1必选:nginx向客户端发起断开连接的超时时间
arg2可选:nginx会在响应头信息中设置Keep-Alive:timeout=ar2,让客户端主动关闭连接。

所以有些情况下,浏览器会忽略这个设置。下面的情况可以看到,设置了arg2后并无效果。

该指令定义一个keep-alive的时长,keep-alive能够使客户端到服务器的连接在一定时间内持续有效。在这个时间内,客户端对服务器的访问不需要再次建立连接或重新连接。

实验环境配置准备

客户端IP:192.168.0.16
服务器端IP:192.168.0.21

服务器端部署nginx服务器,客户端使用浏览器访问

nginx就放一个普通html页面,这里实验重点在于观察tcp的通信过程。

# file: nginx.conf
http {
  keepalive_timeout  15;
  #这里单位是s
}

在nginx服务器上使用tcpdump抓的包

nginx监听的端口为80,执行抓包命令:

$ tcpdump tcp port 80

实验一:keepalive_timout 为15s的情况

客户端在12s时刻发起请求,27s时刻nginx向客户端发送FIN,请求断开连接。

11:53:12.013881 IP 192.168.0.16.52119 > 192.168.0.21.http: Flags [S], seq 1647038819, win 8192, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
11:53:12.013953 IP 192.168.0.21.http > 192.168.0.16.52119: Flags [S.], seq 2440006518, ack 1647038820, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
11:53:12.014227 IP 192.168.0.16.52119 > 192.168.0.21.http: Flags [.], ack 1, win 16425, length 0
11:53:12.015674 IP 192.168.0.16.52119 > 192.168.0.21.http: Flags [P.], seq 1:534, ack 1, win 16425, length 533
11:53:12.015720 IP 192.168.0.21.http > 192.168.0.16.52119: Flags [.], ack 534, win 123, length 0
11:53:12.015908 IP 192.168.0.21.http > 192.168.0.16.52119: Flags [P.], seq 1:173, ack 534, win 123, length 172
11:53:12.213230 IP 192.168.0.16.52119 > 192.168.0.21.http: Flags [.], ack 173, win 16382, length 0
11:53:27.031171 IP 192.168.0.21.http > 192.168.0.16.52119: Flags [F.], seq 173, ack 534, win 123, length 0
11:53:27.031388 IP 192.168.0.16.52119 > 192.168.0.21.http: Flags [.], ack 174, win 16382, length 0


11:54:12.036632 IP 192.168.0.16.52119 > 192.168.0.21.http: Flags [.], seq 533:534, ack 174, win 16382, length 1
11:54:12.036678 IP 192.168.0.21.http > 192.168.0.16.52119: Flags [.], ack 534, win 123, length 0
11:54:57.028996 IP 192.168.0.16.52119 > 192.168.0.21.http: Flags [.], seq 533:534, ack 174, win 16382, length 1
11:54:57.029037 IP 192.168.0.21.http > 192.168.0.16.52119: Flags [R], seq 2440006692, win 0, length 0

实验二:keepalive_timout 为6s的情况

客户端在07s时刻发起请求,13s时刻nginx向客户端发送FIN,请求断开连接。

16:13:07.986580 IP 192.168.0.16.53518 > 192.168.0.21.http: Flags [S], seq 141352550, win 8192, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
16:13:07.986653 IP 192.168.0.21.http > 192.168.0.16.53518: Flags [S.], seq 3684334522, ack 141352551, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
16:13:07.986853 IP 192.168.0.16.53518 > 192.168.0.21.http: Flags [.], ack 1, win 16425, length 0
16:13:07.988210 IP 192.168.0.16.53518 > 192.168.0.21.http: Flags [P.], seq 1:534, ack 1, win 16425, length 533
16:13:07.988239 IP 192.168.0.21.http > 192.168.0.16.53518: Flags [.], ack 534, win 123, length 0
16:13:07.988911 IP 192.168.0.21.http > 192.168.0.16.53518: Flags [P.], seq 1:173, ack 534, win 123, length 172
16:13:08.186352 IP 192.168.0.16.53518 > 192.168.0.21.http: Flags [.], ack 173, win 16382, length 0
16:13:13.995178 IP 192.168.0.21.http > 192.168.0.16.53518: Flags [F.], seq 173, ack 534, win 123, length 0
16:13:13.995424 IP 192.168.0.16.53518 > 192.168.0.21.http: Flags [.], ack 174, win 16382, length 0
16:13:55.937133 IP 192.168.0.16.53518 > 192.168.0.21.http: Flags [F.], seq 534, ack 174, win 16382, length 0
16:13:55.937168 IP 192.168.0.21.http > 192.168.0.16.53518: Flags [.], ack 535, win 123, length 0

实验三:keepalive_timout 为0s的情况

那么长连接在传输结束后就会被关闭。返回时设置头Connection: close

16:36:33.526022 IP 192.168.0.16.53988 > 192.168.0.21.http: Flags [S], seq 453414335, win 8192, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
16:36:33.526097 IP 192.168.0.21.http > 192.168.0.16.53988: Flags [S.], seq 1445206998, ack 453414336, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
16:36:33.526391 IP 192.168.0.16.53988 > 192.168.0.21.http: Flags [.], ack 1, win 16425, length 0
16:36:33.528002 IP 192.168.0.16.53988 > 192.168.0.21.http: Flags [P.], seq 1:534, ack 1, win 16425, length 533
16:36:33.528032 IP 192.168.0.21.http > 192.168.0.16.53988: Flags [.], ack 534, win 123, length 0
16:36:33.528402 IP 192.168.0.21.http > 192.168.0.16.53988: Flags [P.], seq 1:168, ack 534, win 123, length 167
16:36:33.528497 IP 192.168.0.21.http > 192.168.0.16.53988: Flags [F.], seq 168, ack 534, win 123, length 0
16:36:33.528748 IP 192.168.0.16.53988 > 192.168.0.21.http: Flags [.], ack 169, win 16383, length 0
16:36:33.529182 IP 192.168.0.16.53988 > 192.168.0.21.http: Flags [F.], seq 534, ack 169, win 16383, length 0
16:36:33.529200 IP 192.168.0.21.http > 192.168.0.16.53988: Flags [.], ack 535, win 123, length 0

实验四:keepalive_timeout 30s, Keep-Alive:timeout=5;

keepalive_timeout 30 5;

16:42:32.941249 IP 192.168.0.16.54128 > 192.168.0.21.http: Flags [S], seq 1359017244, win 8192, options [mss 1460,nop,wscale 2,nop,nop,sackOK], length 0
16:42:32.941316 IP 192.168.0.21.http > 192.168.0.16.54128: Flags [S.], seq 2951328040, ack 1359017245, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
16:42:32.941524 IP 192.168.0.16.54128 > 192.168.0.21.http: Flags [.], ack 1, win 16425, length 0
16:42:32.947169 IP 192.168.0.16.54128 > 192.168.0.21.http: Flags [P.], seq 1:534, ack 1, win 16425, length 533
16:42:32.947218 IP 192.168.0.21.http > 192.168.0.16.54128: Flags [.], ack 534, win 123, length 0
16:42:32.947747 IP 192.168.0.21.http > 192.168.0.16.54128: Flags [P.], seq 1:196, ack 534, win 123, length 195
16:42:33.148722 IP 192.168.0.21.http > 192.168.0.16.54128: Flags [P.], seq 1:196, ack 534, win 123, length 195
16:42:33.148886 IP 192.168.0.16.54128 > 192.168.0.21.http: Flags [.], ack 196, win 16376, options [nop,nop,sack 1 {1:196}], length 0
16:43:02.979017 IP 192.168.0.21.http > 192.168.0.16.54128: Flags [F.], seq 196, ack 534, win 123, length 0
16:43:02.979265 IP 192.168.0.16.54128 > 192.168.0.21.http: Flags [.], ack 197, win 16376, length 0
16:43:21.010604 IP 192.168.0.16.54128 > 192.168.0.21.http: Flags [F.], seq 534, ack 197, win 16376, length 0
16:43:21.010644 IP 192.168.0.21.http > 192.168.0.16.54128: Flags [.], ack 535, win 123, length 0

这里看到第二个参数并没有让客户端自己发起关闭。也就是并没有起作用,存疑待考。

同时在客户端Windows使用wireshark抓的包

备注:这里wireshark抓的包没有tcpdump详细,并没有显示出,我这里使用的过滤参数为:ip.addr == 192.168.0.21 && tcp.port == 80

keepalive_timout 为15s的情况

keepalive_timout 为6s的情况

通信过程总结

  1. 客户端向服务器端发起http请求(发起syn)
  2. 执行数据传输过程
  3. 超时时间到,nginx发送fin到客户端,执行四次挥手,释放连接

在释放连接后,服务器上的ESTABLISHED状态也会消失,使用netstat -antp | grep nginx查看

tcp        0      0 192.168.0.21:80             192.168.0.16:53424          ESTABLISHED 23034/nginx   

keepalive_timeout什么时候会触发执行?

keepalive_timeout = fin发起的时刻 - 客户端初次与服务器建立连接连nginx的时间(syn发起的时间)

如果连接断开,客户端会重新请求开启连接。

几个疑问

  • wireshark并没有显示双向通信过程,暂时不知怎么设置参数。
  • wireshark最后回有一个RST的包,不知是何故,而tcpdump并没有这样的情况。所以建议还是以tcpdump命令为准。
  • 如果keepalive_timout设置为0,则响应头信息返回Connection: close,连接立即关闭。
  • 第二个参数并没有起作用