redis TIME_WAIT问题

我们知道TIME_WAIT状态是因为没有收到FIN-ACK的ack才会存在的。 下面这个是我们常见的tcp建立和关闭的流程图。

说下场景吧:

使用ab -n -c 对某一个接口进行压测,没有加-k的参数。
nginx是集成了lua-resty-redis进行使用的。所有lua代码里都加了

1
2
3
4
5
local ok, err = red:close()
if not ok then
ngx.log(ngx.ERR, "failed close redis: ", err)
return
end

redis的相关配置如下

1
2
timeout 3
tcp-keepalive 0

也就是不启用keepalive,timeout为3秒。

但是测试后发现,操作系统过了63秒才把所有TIME_WAIT回收了。

那这里就出现了2个问题:

  1. 我每次都close了,为什么还会有TIME_WAIT出现?
  2. 为什么是刚好60秒+timeout的时间回收?

下面是抓包的图例:

可以看到只有我nginx给redis的fin-ack回了ack,但是redis没有给nginx的fin-ack回ack。

1
2
3
$ redis-cli -h 127.0.0.1 client list
id=2 addr=10.10.11.18:6379 fd=5 name= age=23699 idle=0 flags=M db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=set
id=5099 addr=127.0.0.1:23233 fd=6 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client

而且通过client list里也没有对应的连接,说明至少在redis应用认为这个连接是已经关闭了的。但是下面的网络状态里确实是实实在在存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ netstat -anpl | grep  '127.0.0.1:6379'
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN 32249/redis-server
tcp 0 0 127.0.0.1:18935 127.0.0.1:6379 TIME_WAIT -
tcp 0 0 127.0.0.1:22933 127.0.0.1:6379 TIME_WAIT -
tcp 0 0 127.0.0.1:21807 127.0.0.1:6379 TIME_WAIT -
tcp 0 0 127.0.0.1:23023 127.0.0.1:6379 TIME_WAIT -
tcp 0 0 127.0.0.1:22617 127.0.0.1:6379 TIME_WAIT -
tcp 0 0 127.0.0.1:19381 127.0.0.1:6379 TIME_WAIT -
tcp 0 0 127.0.0.1:19825 127.0.0.1:6379 TIME_WAIT -
tcp 0 0 127.0.0.1:20345 127.0.0.1:6379 TIME_WAIT -
tcp 0 0 127.0.0.1:22963 127.0.0.1:6379 TIME_WAIT -
tcp 0 0 127.0.0.1:20971 127.0.0.1:6379 TIME_WAIT -
tcp 0 0 127.0.0.1:22411 127.0.0.1:6379 TIME_WAIT -

TIME_WAIT的官方的解释是“FINs have been received and ACK has been sent passive TCP. active TCP is waiting 2MSLs to remove the connection from the connection table.”
也就是说redis已经发了ack了,但是nginx未必会收到,所以需要等待2MSLs时间。
我们这个系统的MSL是30秒

1
2
$ cat /proc/sys/net/ipv4/tcp_fin_timeout
30

所以正常就是60秒回收,然后加上之前设置的3秒timeout时间,那就是63秒回收了。第二个问题解了,那第一个还是没有出现转机。

按这个思路分析,看来是redis层面已经断开了,但是tcp层面却没有。看了下时间,这都是发生在几微秒之间的,会不会是因为这个原因呢。

想了下会不会是由于ab客户端的问题呢,因为nginx本身也有一样数量的TIME_WAIT存在。但是我尝试使用curl以http1.0的协议,同时header里加上了close也依然会产生TIME_WAIT。

1
2
3
4
5
6
7
8
9
10
$ netstat -anpl | grep  '120.xx.yy.zz:80'
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 120.xx.yy.zz:80 120.xx.yy.zz:57813 TIME_WAIT -
tcp 0 0 120.xx.yy.zz:80 120.xx.yy.zz:60279 TIME_WAIT -
tcp 0 0 120.xx.yy.zz:80 120.xx.yy.zz:58449 TIME_WAIT -
tcp 0 0 120.xx.yy.zz:80 120.xx.yy.zz:58837 TIME_WAIT -
tcp 0 0 120.xx.yy.zz:80 120.xx.yy.zz:59959 TIME_WAIT -
tcp 0 0 120.xx.yy.zz:80 120.xx.yy.zz:57651 TIME_WAIT -
tcp 0 0 120.xx.yy.zz:80 120.xx.yy.zz:59497 TIME_WAIT -

下面这个是把nginx的keepalive改为了0,请求里加了close和keepalivetime=0了,结果依然如此。现在只能说这个TIME_WAIT是所有服务器端都会碰到的问题了,
你没有要求客户端都回你一个FIN-ACK的ACK。但是在linux下的避免方法是可以设置net.ipv4.tcp_max_tw_buckets,这样限制了整体的timewait的数量。或者就是尽量用keepalive了,这个也是现在推荐的方式。