一次HTTP connect-timeout的排查(下)

回顾-上期的遗留问题

  • 疑惑1:按道理全连接队列满了,但是客户端的连接请求是已经接收到SYN+ACK了,所以对于客户端来说该连接已经建立了,为啥会报connect timeout ? 应该是read timeout或者connect reset 。
  • 疑惑2:全连接队列满了,但客户端的请求认为连接ESTABLISH状态,可以继续发送数据请求。这时候服务端如何处理?

TCP三次握手

  • client发送syn给server端。
  • server接收到client的syn,这个时候则会将相关信息放到半链接队列(syn queue),并且发送syn+ack发送给client。
  • client接受到server的syn+ack之后,发送一个ack给server告诉server我接收到了。这个时候server就会将相关信息放到全连接队列(accept queue)中。


    image.png

回顾-TCP三次握手-半连接队列和全连接队列

  1. 当 client 通过 connect 向 server 发出 SYN 包时,client 会维护一个 socket 等待队列,而 server 会维护一个 SYN 队列
  2. 此时进入半链接的状态,如果 socket 等待队列满了,server 则会丢弃,而 client 也会由此返回 connection time out;只要是 client 没有收到 SYN+ACK,3s 之后,client 会再次发送,如果依然没有收到,9s 之后会继续发送
  3. 半连接 syn 队列的长度为 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog) 决定
  4. 当 server 收到 client 的 SYN 包后,会返回 SYN, ACK 的包加以确认,client 的 TCP 协议栈会唤醒 socket 等待队列,发出 connect 调用
  5. client 返回 ACK 的包后,server 会进入一个新的叫 accept 的队列,该队列的长度为 min(backlog, somaxconn),默认情况下,somaxconn 的值为 128,表示最多有 129 的 ESTAB 的连接等待 accept(),而 backlog 的值则由 int listen(int sockfd, int backlog) 中的第二个参数指定,listen 里面的 backlog 的含义请看这里。
  6. 当accept 队列满了之后,即使 client 继续向 server 发送 ACK 的包,也会不被相应,此时,server 通过 /proc/sys/net/ipv4/tcp_abort_on_overflow 来决定如何返回,0 表示直接丢丢弃该 ACK,1 表示发送 RST 通知 client;相应的,client 则会分别返回 read timeout 或者 connection reset by peer。
    (在自己的测试验证过程中,实际情况还会产生:服务器会随机的忽略收到的 SYN,建立起来的连接数可以无限的增加,只不过客户端会遇到延时以及超时的情况。)

验证疑惑1:全连接队列满了情况怎么样?

  1. 启动一个TCP服务端Socket,只对端口做listen监听,不进行accept操作。(port = 9999, backlog = 2)
  2. 分别调用4次该端口(采用nc命令: nc ip port的方式)
  3. 服务器的TCP参数 cat /proc/sys/net/ipv4/tcp_abort_on_overflow = 0
    PS: 注意以下的代码,servserSocket.accpet()是注释掉的,代表不会从全连接队列中获取请求信息。


    image.png
第1次Socket连接:
13:55:31.379052 IP 10.127.4.74.56720 > 10.16.30.142.9999: Flags [S], seq 1290846985, win 8192, options [mss 1360,nop,wscale 8,nop,nop,sackOK], length 0
13:55:31.379065 IP 10.16.30.142.9999 > 10.127.4.74.56720: Flags [S.], seq 3765322754, ack 1290846986, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:31.406038 IP 10.127.4.74.56720 > 10.16.30.142.9999: Flags [.], ack 1, win 69, length 0
第2次Socket连接:
13:55:33.188479 IP 10.127.4.74.56728 > 10.16.30.142.9999: Flags [S], seq 3220053736, win 8192, options [mss 1360,nop,wscale 8,nop,nop,sackOK], length 0
13:55:33.188494 IP 10.16.30.142.9999 > 10.127.4.74.56728: Flags [S.], seq 3458261601, ack 3220053737, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:33.208279 IP 10.127.4.74.56728 > 10.16.30.142.9999: Flags [.], ack 1, win 69, length 0
第3次Socket连接:
13:55:34.974518 IP 10.127.4.74.56735 > 10.16.30.142.9999: Flags [S], seq 1314208155, win 8192, options [mss 1360,nop,wscale 8,nop,nop,sackOK], length 0
13:55:34.974539 IP 10.16.30.142.9999 > 10.127.4.74.56735: Flags [S.], seq 3817678685, ack 1314208156, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:34.999211 IP 10.127.4.74.56735 > 10.16.30.142.9999: Flags [.], ack 1, win 69, length 0
第4次Socket连接
backlog配置的2,全连接队列个数为2+1=3,
第4次开始溢出,/proc/sys/net/ipv4/tcp_abort_on_overflow 配置的为0,则系统会默认重试5次SYN+ACK(重试次数见: /proc/sys/net/ipv4/tcp_synack_retries)
13:55:36.990148 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [S], seq 713717214, win 8192, options [mss 1360,nop,wscale 8,nop,nop,sackOK], length 0
13:55:36.990170 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:37.011510 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, length 0
13:55:38.389731 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:38.401817 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, options [nop,nop,sack 1 {0:1}], length 0
13:55:40.589773 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:40.607068 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, options [nop,nop,sack 1 {0:1}], length 0
13:55:44.789766 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:44.804919 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, options [nop,nop,sack 1 {0:1}], length 0
13:55:52.789749 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:52.804957 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, options [nop,nop,sack 1 {0:1}], length 0
13:56:08.789786 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:56:08.847814 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, options [nop,nop,sack 1 {0:1}], length 0

验证结果:抓包的TCP DUMP 显示,在第4次的时候全连接队列发生溢出:在收到客户端的ack确认时候(时间13:55:37.011510),由于backlog已满,所以服务端直接丢弃该ACK确认,再次重试响应SYN+ACK(时间13:55:38.389731) 。反复5次以后,客户端出现read-timeout。 这个就证实了开章首提的疑惑1确实如我们所想

验证疑惑1:为啥会出现connect-timeout?

启动一个http服务,配置tomcat-acceptCount为1,并发10个线程请求进行,发现出现了一次connect timeout

#正常的三次握手
18:15:14.975879 IP 10.16.80.136.52633 > 10.16.30.142.19966: Flags [S], seq 17196608, win 8192, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
18:15:14.975907 IP 10.16.30.142.19966 > 10.16.80.136.52633: Flags [S.], seq 3788688748, ack 17196609, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
18:15:14.981048 IP 10.16.80.136.52633 > 10.16.30.142.19966: Flags [.], ack 1, win 256, length 0
#由于全队列满随机忽略不响应SYN+ACK:
18:15:14.981065 IP 10.16.80.136.52637 > 10.16.30.142.19966: Flags [S], seq 1960972950, win 8192, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
为啥会出现这种情况,查找了相关Linux资料,如下:

man listen中提到每次收到新SYN包,内核往SYN队列追加一个新连接(除非该队列已满)。但事实并非如此,net/ipv4/tcp_ipv4.c中tcp_v4_conn_request函数负责处理SYN包,请看以下代码:

if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)     
    goto drop;     
  • sk_acceptq_is_full()函数很好理解,根据字面意思就可以看出,该函数是检查连接队列是否已满
  • inet_csk_reqsk_queue_young()函数返回半连接队列中未重传过SYN+ACK段的连接请求块数量。 如果连接队列已满并且半连接队列中的连接请求块中未重传的数量大于1,则会跳转到drop处,丢弃SYN包。如果半连接队列中未重传的请求块数量大于1,则表示未来可能有2个完成的连接,这些新完成的连接要放到连接队列中,但此时连接队列已满。如果在接收到三次握手中最后的ACK后连接队列中没有空闲的位置,会忽略接收到的ACK包,连接建立会推迟,所以此时最好丢掉部分新的连接请求,空出资源以完成正在进行的连接建立过程。还要注意,这个判断并没有考虑半连接队列是否已满的问题。从这里可以看出,即使开启了SYN cookies机制并不意味着一定可以完成连接的建立。
  • 参考资料:https://blog.csdn.net/justlinux2010/article/details/12619761
    自己按照理解画了一个简图,如下:
    image.png

结论:所以正是由于inet_csk_reqsk_queue_young()函数机制的存在,所以在极端情况下,全连接队列满了,也会引起客户端出现connect-timeout的情况

验证疑惑2:全连接队列满了,客户端继续发送数据请求。这时候服务端如何处理?

这个其实就是经典的client fooling问题
由于全队列满的情况对于客户端来说是透明的,因为请求方已经收到了服务端的SYN+ACK,状态已经变为ESTABLISH,客户端认为连接是建立的,所以继续发消息,那么此种情况下(全队列满,服务端没有对应连接,客户端认为连接建立成功),客户端仍发消息会产生什情况?,试验如下:


image.png

结论:可以发现客户端发送的数据包如果没有收到服务端的ack, 客户端会自动重试几次,如果仍没收到ack,则客户端发送RST指令断开已服务端的连接。

小结

TCP握手的全连接队列、半连接队列溢出这种问题很容易被大家忽视,但是又很关键。 一旦溢出,从各项指标比如CPU, GC情况, 线程状态看都很正常,但是压力上不去,从服务端上看其他的请求的响应时间又很快(见上篇的pinpoint监控图)。
希望本文能够帮助大家理解TCP 全连接、半连接队列的概念,以及出现问题如何排查。
如文章有理解不足之处,也请大伙帮忙指出,感谢

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • 《大佛普拉斯》是由黄信尧执导,叶如芬、钟孟宏监制的剧情片。由陈竹升、庄益增、戴立忍、纳豆、张少怀等主演。该片于20...
    Alina娜阅读 1,021评论 0 4
  • 金河的手上戴着一串精致的佛珠, 佛珠一共有15颗,小叶紫檀材质,每一颗佛珠的花纹都展现出别样的样貌,每次感觉紧张或...
    艳南天阅读 294评论 0 0
  • 如果提前了解,你会拥有的人生,不知道是否还会有勇气前来。 2018,即将过去,这一年很特别,这一年充满变化与挑战,...
    妙曼风景阅读 1,799评论 1 6