下面这篇推文转自微信公众号《阿里技术》:https://mp.weixin.qq.com/s/yH3PzGEFopbpA-jw4MythQ

这篇文章写的是NIO的socket通信,三次握手成功,但是出现Server没有响应Client,是因为backlog过小。

我前篇文章<Netty之HelloWorld>就写到对于ChannelOption.SO_BACKING的解释,而Netty确实也是用的NIO。下面再将解释放出来。

* 对于ChannelOption.SO_BACKING的解释:
* 服务器端TCP内核模型维护有两个队列,我们称之为A,B吧。
* 客户端向服务端connect的时候,会发送带有SYN标志的包(第一次握手),
* 服务器收到客户端发来的SYN时,向客户端发送SYN ACK确认(第二次握手),
* 此时TCP内核模块把客户端连接加入A队列中,然后服务器收到客户端发来的ACK时(第三次握手),
* TCP内核模块把客户端连接从A队列移到B队列,连接完成,应用程序的accpet会返回。
* 也就是说accept从B队列中取出完成三次握手的连接。
* A队列和B队列的长度之和是blacklog。当A,B队列的长度之和大于backlog时,新连接将会被TCP内核拒绝。
* 所以说,如果backlog过小,可能会初选accept速度跟不上,A,B队列满了,导致新的客户端无法连接
* 要注意的是,backlog对程序支持的连接数并没影响,backlog影响的只是还没被accept取出的连接

好了,不说那么多了,下面看看阿里大牛是如何入手解决这个问题的。

问题描述

场景:JAVA的client和server,使用socket通信。server使用NIO。

1.间歇性得出现client向server建立连接三次握手已经完成,但server的selector没有响应到这连接。

2.出问题的时间点,会同时有很多连接出现这个问题。

3.selector没有销毁重建,一直用的都是一个。

4.程序刚启动的时候必会出现一些,之后会间歇性出现。

分析问题

正常TCP建连接三次握手过程:

  • 第一步:client 发送 syn 到server 发起握手;

  • 第二步:server 收到 syn后回复syn+ack给client;

  • 第三步:client 收到syn+ack后,回复server一个ack表示收到了server的syn+ack(此时client的56911端口的连接已经是established)。

从问题的描述来看,有点像TCP建连接的时候全连接队列(accept队列,后面具体讲)满了,尤其是症状2、4. 为了证明是这个原因,马上通过 netstat -s | egrep "listen" 去看队列的溢出统计数据:

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

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

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握手过程中建连接的流程和队列

(图片来源:http://www.cnxct.com/something-about-phpfpm-s-backlog/)

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

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

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

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

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

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

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

netstat -s

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

ss 命令

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

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

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

(来自JDK帮助文档:https://docs.oracle.com/javase/7/docs/api/java/net/ServerSocket.html)

半连接队列的大小取决于: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;而 Send 则是发送队列中没有被远程主机确认的 bytes 数。

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

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

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

实践验证一下上面的理解

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

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

Tomcat和Nginx中的Accept队列参数

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

Nginx默认是511

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

总结

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

jdk、netty等一些框架默认backlog比较小,可能有些情况下导致性能上不去。

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

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

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

  1. 全连接队列满了会影响半连接队列吗?

  2. netstat -s看到的overflowed和ignored的数值有什么联系吗?

  3. 如果client走完了TCP握手的第三步,在client看来连接已经建立好了,但是server上的对应连接实际没有准备好,这个时候如果client发数据给server,server会怎么处理呢?(有同学说会reset,你觉得呢?)

提出这些问题,希望以这个知识点为抓手,让你的知识体系开始自我生长。

参考文章

http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html

http://www.cnblogs.com/zengkefu/p/5606696.html

http://www.cnxct.com/something-about-phpfpm-s-backlog/

http://jaseywang.me/2014/07/20/tcp-queue-%E7%9A%84%E4%B8%80%E4%BA%9B%E9%97%AE%E9%A2%98/

http://jin-yang.github.io/blog/network-synack-queue.html#

http://blog.chinaunix.net/uid-20662820-id-4154399.html

NIO编程之三次握手成功而服务器不响应客户端相关推荐

  1. 服务器没有响应客户端,socket编程某些服务器对某些客户端没有响应的问题!!...

    socket编程某些服务器对某些客户端没有响应的问题!! 我是这么用的t=serversocket(inetaddress,2); while(true){ t=y.accept(); out=new ...

  2. SQLServer 2008 已成功与服务器建立连接,但是在登录前的握手期间发生错误。 (provider: SSL Provider, error: 0 - 等待的操作过时。...

    SQLServer 2008 已成功与服务器建立连接,但是在登录前的握手期间发生错误. (provider: SSL Provider, error: 0 - 等待的操作过时. 在用SQL Serve ...

  3. 已成功与服务器建立连接,但是在登录前的握手期间发生错误

    网上解决办法很多,不过一般都不实用,不少是直接复制粘贴的. 以下是真正的解决办法: 登陆sql2005时出现如下问题: 已成功与服务器建立连接,但是在登录前的握手期间发生错误.在连接到 SQL Ser ...

  4. NIO详解(四):NIO编程

    1. NIO类库简介 1.1 缓冲区Buffer Buffer是一个对象,它包含了一些要写入或者要读出的数据.在NIO类库中加入Buffer对象,体现了新库和原来I/O的一个重要区别.在NIO库中,所 ...

  5. 【3月15日】BIO、伪异步IO以及NIO编程实践

    1.引言 从java的I/O体系发展的历史看,先有java.io,后有java.nio.前者一般称之为IO,后者称之为NIO(New IO).但是又由于其特性前者又成为BIO(Block IO),后者 ...

  6. Unix 网络编程(四)- 典型TCP客服服务器程序开发实例及基本套接字API介绍

    转载:http://blog.csdn.net/michael_kong_nju/article/details/43457393 写在开头: 在上一节中我们学习了一些基础的用来支持网络编程的API, ...

  7. Java NIO编程基础

    Java NIO编程基础 Java NIO 基本介绍 NIO的Buffer基本使用 NIO 和 BIO 的比较 NIO 三大核心原理示意图 缓冲区(Buffer) 基本介绍 Buffer 类及其子类 ...

  8. Java NIO 编程:Buffer、Channel、Selector原理详解

    1 Java 中的 I/O模型:BIO.NIO.AIO 1.1 BIO.NIO.AIO概念介绍 I/O 模型简单的理解:就是 用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能. Ja ...

  9. Java NIO编程

    NIO 同步非阻塞的编程方式 主要是解决BIO的大并发问题,NIO最重要的地方是当一个连接创建后,对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以完成,当这个线程中的 ...

最新文章

  1. HDU-3573 Buy Sticks
  2. 值得推荐的C/C++框架和库 【强烈推荐】
  3. rabbitMq自动创建队列
  4. mysql5.6.24教程,mysql5.6.24升级5.7.27之错误汇总
  5. SQLmap的下载和安装,以及其参数大全和使用教程(值得收藏)
  6. Linux文件、目录权限及常用命令
  7. msp430流水灯c语言程序,超详细msp430示例程序汇编.doc
  8. 组策略 之 恢复默认组策略对象命令
  9. Cere Network 将于 Republic、DAO Maker 和 Polkastarter 上进行公售
  10. 用户是如何浏览你的网站的
  11. Atitit 大数据体系树 艾提拉著 数据采集 gui自动化 爬虫 Nui自动化  Ocr技术 Tts语音处理 文档处理(office zip等) html文档处理解析 转换与处理
  12. c++ 取整_MPIP Raw转Raw图简述-C实现
  13. RTCM 协议数据解析
  14. Swing-图表(扇形图的绘制)
  15. shiro原理简介说明
  16. android 签到自定义,Android日历签到,超级简单的实现方式
  17. 统计|如何建立单总体方差的置信区间
  18. Java项目:基于java+ssm生鲜超市进销存管理系统--计算机毕业设计
  19. 详解InnoDB的Buffer Pool
  20. require.js的用法-阮一峰

热门文章

  1. 无线AP和无线路由器的区别
  2. EasyExcel 导入导出Excel文件
  3. 关于C语言用问号表达式(正则表达式)输入三个数求最大数的问题
  4. [Network Analysis] 复杂网络分析总结
  5. [数值计算-10]:一元非线性函数求最小值 - 导数与梯度下降法Python法代码示例
  6. 生成有箭头的流向Line.shp 并通过ArcMap绘制带箭头的图,根据D8算法,输入Flow Direction
  7. geoserver发布地图服务
  8. 狂笑!可笑20分钟的段子!
  9. Java春招面试宝典300题(2023)
  10. codeup27938 星号直角三角形