最近碰到一个问题,Client 端连接服务器总是抛异常。在反复定位分析、并查阅各种资料搞懂后,我发现并没有文章能把这两个队列以及怎么观察他们的指标说清楚。

问题描述

场景:Java 的 Client 和 Server,使用 Socket 通信。Server 使用 NIO。

问题

  • 间歇性出现 Client 向 Server 建立连接三次握手已经完成,但 Server 的 Selector 没有响应到该连接。
  • 出问题的时间点,会同时有很多连接出现这个问题。
  • Selector 没有销毁重建,一直用的都是一个。
  • 程序刚启动的时候必会出现一些,之后会间歇性出现。

分析问题

正常 TCP 建连接三次握手过程,分为如下三个步骤:

  1. Client 发送 Syn 到 Server 发起握手。
  2. Server 收到 Syn 后回复 Syn + Ack 给 Client。
  3. Client 收到 Syn + Ack后,回复 Server 一个 Ack 表示收到了 Server 的 Syn + Ack(此时 Client 的 56911 端口的连接已经是 Established)。

从问题的描述来看,有点像 TCP 建连接的时候全连接队列(Accept 队列,后面具体讲)满了。

尤其是症状 2、4 为了证明是这个原因,马上通过 netstat -s | egrep "listen" 去看队列的溢出统计数据:

667399 times the listen queue of a socket overflowed

反复看了几次之后发现这个overflowed 一直在增加,那么可以明确的是server上全连接队列一定溢出了。

接着查看溢出后,OS怎么处理:

# cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0

tcp_abort_on_overflow 为0表示:如果三次握手第三步的时候全连接队列满了那么server扔掉client 发过来的ack(在server端认为连接还没建立起来)

为了证明客户端应用代码的异常跟全连接队列满有关系,我先把tcp_abort_on_overflow修改成 1,1表示第三步的时候如果全连接队列满了,server发送一个reset包给client,表示废掉这个握手过程和这个连接(本来在server端这个连接就还没建立起来)。

接着测试,这时在客户端异常中可以看到很多connection reset by peer的错误,到此证明客户端错误是这个原因导致的(逻辑严谨、快速证明问题的关键点所在)。

于是开发同学翻看java 源代码发现socket 默认的backlog(这个值控制全连接队列的大小,后面再详述)是50,于是改大重新跑,经过12个小时以上的压测,这个错误一次都没出现了,同时观察到 overflowed 也不再增加了。

到此问题解决,简单来说TCP三次握手后有个accept队列,进到这个队列才能从Listen变成accept,默认backlog 值是50,很容易就满了。满了之后握手第三步的时候server就忽略了client发过来的ack包(隔一段时间server重发握手第二步的syn+ack包给client),如果这个连接一直排不上队就异常了。

但是不能只是满足问题的解决,而是要去复盘解决过程,中间涉及到了哪些知识点是我所缺失或者理解不到位的。

这个问题除了上面的异常信息表现出来之外,还有没有更明确地指征来查看和确认这个问题。

深入理解TCP握手过程中建连接的流程和队列

如上图所示,这里有两个队列:syns queue(半连接队列);accept queue(全连接队列)。

三次握手中,在第一步server收到client的syn后,把这个连接信息放到半连接队列中,同时回复syn+ack给client(第二步);

题外话,比如syn floods 攻击就是针对半连接队列的,攻击方不停地建连接,但是建连接的时候只做第一步,第二步中攻击方收到server的syn+ack后故意扔掉什么也不做,导致server上这个队列满其他正常请求无法进来。

第三步的时候server收到client的ack,如果这时全连接队列没满,那么从半连接队列拿出这个连接的信息放入到全连接队列中,否则按tcp_abort_on_overflow指示的执行。

这时如果全连接队列满了并且tcp_abort_on_overflow是0的话,server过一段时间再次发送syn+ack给client(也就是重新走握手的第二步),如果client超时等待比较短,client就很容易异常了。

在我们的os中retry 第二步的默认次数是2(centos默认是5次):

net.ipv4.tcp_synack_retries =2

如果TCP连接队列溢出,有哪些指标可以看呢?

上述解决过程有点绕,听起来懵,那么下次再出现类似问题有什么更快更明确的手段来确认这个问题呢?(通过具体的、感性的东西来强化我们对知识点的理解和吸收。)

netstat -s

[root@server ~] # netstat -s | egrep "listen|LISTEN"
667399 times the listen queue of a socket overflowed
667399 SYNs to LISTEN sockets ignored

比如上面看到的 667399 times ,表示全连接队列溢出的次数,隔几秒钟执行下,如果这个数字一直在增加的话肯定全连接队列偶尔满了。

ss 命令

[root@server ~]#ss -lnt
Recv -Q Send -Q Loacl Address:Port Peer Address:Port
0 50 *:3306 *:*

上面看到的第二列Send-Q 值是50,表示第三列的listen端口上的全连接队列最大为50,第一列Recv-Q为全连接队列当前使用了多少。

全连接队列的大小取决于:min(backlog, somaxconn) 。backlog是在socket创建的时候传入的,somaxconn是一个os级别的系统参数。

这个时候可以跟我们的代码建立联系了,比如Java创建ServerSocket的时候会让你传入backlog的值:

ServerSocket()
Creates an unbound server socket.
ServerSocket(int port)
Creates a server socket,bound to the specified port.
ServerSocket(int port, int backlog)
Creates a server socket and binds it to the specified local port number, with the specified backlog.
ServerSocket(int port, int backlog, InetAddress bindAddr)
Creates a server with the specified port, listen backlog, and local IP address to bind to.

来自JDK帮助文档

半连接队列的大小取决于:max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog),不同版本的os会有些差异。

我们写代码的时候从来没有想过这个backlog或者说大多时候就没给他值(那么默认就是50),直接忽视了他。

首先这是一个知识点的盲点;其次也许哪天你在哪篇文章中看到了这个参数,当时有点印象,但是过一阵子就忘了,这是知识之间没有建立连接,不是体系化的。

但是如果你跟我一样首先经历了这个问题的痛苦,然后在压力和痛苦的驱动自己去找为什么,同时能够把为什么从代码层推理理解到OS层,那么这个知识点你才算是比较好地掌握了,也会成为你的知识体系在TCP或者性能方面成长自我生长的一个有力抓手。

netstat 命令

netstat 跟 ss 命令一样也能看到 Send-Q、Recv-Q 这些状态信息,不过如果这个连接不是 Listen 状态的话,Recv-Q 就是指收到的数据还在缓存中,还没被进程读取,这个值就是还没被进程读取的 bytes。

$netstat -tn
Active Internet connections(w/o servers)
Proto Recv -Q Send -Q Local Address Foreign Address State
tcp0 0 server:8182 client-1:15260 SYN_RECV
tcp0 28 server:22 client-1:51708 ESTABLISHED
tcp0 0 server:2376 client-1:60269 ESTABLISHED

netstat -tn 看到的 Recv-Q 跟全连接半连接没有关系,这里特意拿出来说一下是因为容易跟 ss -lnt 的 Recv-Q 搞混淆,顺便建立知识体系,巩固相关知识点 。

比如如下netstat -t 看到的Recv-Q有大量数据堆积,那么一般是CPU处理不过来导致的:

上面是通过一些具体的工具、指标来认识全连接队列(工程效率的手段)。

实践验证一下上面的理解

把java中backlog改成10(越小越容易溢出),继续跑压力,这个时候client又开始报异常了,然后在server上通过 ss 命令观察到:

Fri May 5 13:50:23 CST 2017
Recv -Q Send -Q Local Address:port Peer Address:Port
11 10 *:3306 *:*

按照前面的理解,这个时候我们能看到3306这个端口上的服务全连接队列最大是10,但是现在有11个在队列中和等待进队列的,肯定有一个连接进不去队列要overflow掉,同时也确实能看到overflow的值在不断地增大。

Tomcat和Nginx中的Accept队列参数

Tomcat默认短连接,backlog(Tomcat里面的术语是Accept count)Ali-tomcat默认是200, Apache Tomcat默认100。

#ss -lnt
Recv -Q Send -Q Local Address:port Peer Address:Port
0 100 *: 8080 *:*

Nginx默认是511。

#sudo ss -lnt
State Recv -Q Send -Q Local Address:Port Peer Address:Port
LISTEN 0 511 *: 8085 *:*
LISTEN 0 511 *: 8085 *:*

因为Nginx是多进程模式,所以看到了多个8085,也就是多个进程都监听同一个端口以尽量避免上下文切换来提升性能。

总结

全连接队列、半连接队列溢出这种问题很容易被忽视,但是又很关键,特别是对于一些短连接应用(比如Nginx、PHP,当然他们也是支持长连接的)更容易爆发。 一旦溢出,从cpu、线程状态看起来都比较正常,但是压力上不去,在client看来rt也比较高(rt=网络+排队+真正服务时间),但是从server日志记录的真正服务时间来看rt又很短。jdk、netty等一些框架默认backlog比较小,可能有些情况下导致性能上不去。

希望通过本文能够帮大家理解TCP连接过程中的半连接队列和全连接队列的概念、原理和作用,更关键的是有哪些指标可以明确看到这些问题(工程效率帮助强化对理论的理解)。

另外每个具体问题都是最好学习的机会,光看书理解肯定是不够深刻的,请珍惜每个具体问题,碰到后能够把来龙去脉弄清楚,每个问题都是你对具体知识点通关的好机会。

最后提出相关问题给大家思考

  • 全连接队列满了会影响半连接队列吗?
  • netstat -s看到的overflowed和ignored的数值有什么联系吗?
  • 如果client走完了TCP握手的第三步,在client看来连接已经建立好了,但是server上的对应连接实际没有准备好,这个时候如果client发数据给server,server会怎么处理呢?(有同学说会reset,你觉得呢?)

来源:阿里技术微信公众号

TCP三次握手原理,你真的了解吗?的更多相关文章

  1. TCP三次握手原理详解

    TCP/IP协议不是TCP和IP这两个协议的合称,而是指因特网整个TCP/IP协议族. 从协议分层模型方面来讲,TCP/IP由四个层次组成:网络接口层.网络层.传输层.应用层. TCP协议:即传输控制 ...

  2. TCP 三次握手原理,你真的理解吗?

    最近,阿里中间件小哥哥蛰剑碰到一个问题——client端连接服务器总是抛异常.在反复定位分析.并查阅各种资料文章搞懂后,他发现没有文章把这两个队列以及怎么观察他们的指标说清楚. 因此,蛰剑写下这篇文章 ...

  3. TCP三次握手原理与SYN攻击

    本文内容包括以下几点 1.TCP三次握手四次挥手解析 2.迭代型服务器程序编写,并给出客户端,结合这一模式详细介绍Berkeley套接字的使用 3.介绍SYN攻击的原理 TCP连接建立,传输数据,连接 ...

  4. TCP三次握手原理

    本文主要讲述的是 1.TCP协议三次握手原理,以及为什么要三次握手,两次握手带来的不利后果. 2.TCP协议四次挥手原理,为什么要四次挥手. TCP协议三次握手原理: 首先,给张图片,建立TCP三次握 ...

  5. TCP三次握手四次挥手原理

    转自http://www.cnblogs.com/liuxiaoming/archive/2013/04/27/3047803.html TCP协议三次握手原理: 首先,给张图片,建立TCP三次握手的 ...

  6. TCP三次握手与DDOS攻击原理

    TCP三次握手与DDOS攻击原理 作者:冰盾防火墙 网站:www.bingdun.com 日期:2014-12-09   在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接. ...

  7. 一文彻底搞懂 TCP三次握手、四次挥手过程及原理

    原创文章出自公众号:「码农富哥」,欢迎收藏和关注,如转载请注明出处! TCP 协议简述 TCP 提供面向有连接的通信传输,面向有连接是指在传送数据之前必须先建立连接,数据传送完成后要释放连接. 无论哪 ...

  8. 面试官问我TCP三次握手和四次挥手,我真的是

    候选者:面试官你好,请问面试可以开始了吗 面试官:嗯,开始吧 面试官:今天来聊聊TCP吧,TCP的各个状态还有印象吗? 候选者:还有些许印象的,要不我就来简单说下TCP的三次握手和四次挥手的流程吧 候 ...

  9. 硬不硬你说了算!35 张图解被问千百遍的 TCP 三次握手和四次挥手面试题

    每日一句英语学习,每天进步一点点: 前言 不管面试 Java .C/C++.Python 等开发岗位, TCP 的知识点可以说是的必问的了. 任 TCP 虐我千百遍,我仍待 TCP 如初恋. 遥想小林 ...

随机推荐

  1. Tapestry3.0开发概论

    一.TAPESTRY技术的综述. tapestry是平行于struts/Spring MVC/webwork /jsf等开发工具的语言.tapestry是基于组件的开发工具.它具有组件的可重用性.因为 ...

  2. Fiddler无法正常抓取谷歌等浏览器的请求_解决方案

    1-先了解Fiddler工作原理: 正常情况下,fiddler是可以抓chrome的请求的. fiddler会自动给浏览器设置一个代理127.0.0.1端口8888,并且记忆浏览器的代理设置,所有的请 ...

  3. JavaScript 中,定义函数时用 var foo = function () {} 和 function foo() {}有什么区别?

    对于新手来说(本人也是新手-_-!),好像var foo = function () {} 和 function foo(){}并没有什么区别,意识里可能就认为就是两种不同的写法而已.但是,通过网上查 ...

  4. C#工具:CSV文件转换帮助类

    CSV是逗号分隔值格式的文件,其文件以纯文本形式存储表格数据(数字和文本).CSV文件由任意数目的记录组成,记录间以某种换行符分隔:每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号 ...

  5. IOS自带输入法中文不触发KEYUP事件导致vue双向绑定错误问题

    先上图: 可以看到输入框中的内容和弹出框的内容不一致, <input class="am-fr labRight" id="txcode" type=&q ...

  6. python爬虫项目(scrapy-redis分布式爬取房天下租房信息)

    python爬虫scrapy项目(二) 爬取目标:房天下全国租房信息网站(起始url:http://zu.fang.com/cities.aspx) 爬取内容:城市:名字:出租方式:价格:户型:面积: ...

  7. JavaScript知识点 思维导图

    javascript变量 javascript数据类型 javascript运算符 javascript流程语句 javascript数组 javascript字符串函数 javascript函数基础 ...

  8. 少侠学代码系列(二)->JS实现

    少侠:小子,休息好了没,赶紧的 帅气的我:好了好了,嚷什么 少侠:(拔刀)嗯? 帅气的我:少侠,淡定淡定,我们来看秘籍吧,刚刚我们说了JS实现是由三个部分组成的 核心(ECMAScript),文档对象 ...

  9. windows10 企业版完整激活

    windows10 企业版完整激活 cmd管理员运行 1.  以管理员身份执行cmd命令,然后输入以下命令: slmgr.vbs /upk 由于Win10正式版允许在命令提示符界面使用"Ct ...

  10. 如何正确使用Espresso来测试你的Android程序

    UI测试在Android平台上一直都是一个令人头痛的事情, 由于大家平时用的很少, 加之很多文档的缺失, 如果很多东西从头摸索,势必踩坑无数. 自Android24正式淘汰掉了Instrumentat ...