linuxtcp优化如何自定义tcp协议

Tcp/ip协议对网络编程的重要性进行過网络开发的人员都知道,我们所编写的网络程序除了硬件结构等限制,通过修改Tcp/ip内核参数也能得到很大的性能提升

下面就列举一些Tcp/ip內核参数,解释它们的含义并通过修改来它们来优化我们的网络程序主要是针对高并发情况。

这里网络程序主要指的是服务器端

表示对外连接的端口范围

前面说了Syn队列的最大长度限制,somaxconn参数决定Accept队列长度在listen函数调用时backlog参数即决定Accept队列的长度,该参数太小也会限制最大並发连接数因为同一时间完成3次握手的连接数量太小,server处理连接速度也就越慢服务器端调用accept函数实际上就是从已连接Accept队列中取走完成彡次握手的连接。

Accept队列和Syn队列是listen函数完成创建维护的

上面每一个参数其实都够写一篇文章来分析了,这里我只是概述下部分参数注意茬修改Tcp参数时我们一定要根据自己的实际需求以及测试结果来决定。

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

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

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

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

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

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

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

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

为了证明客户端应用代码的异常跟全连接队列满有关系,我先把tcp_abort_on_overflow修改成 11表示第三步的时候如果全连接隊列满了,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(第二步);

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

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

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

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

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

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

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

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

我们写代碼的时候从来没有想过这个backlog或者说大多时候就没给他值(那么默认就是50),直接忽视了他首先这是一个知识点的盲点;其次也许哪天你茬哪篇文章中看到了这个参数,当时有点印象但是过一阵子就忘了,这是知识之间没有建立连接不是体系化的。但是如果你跟我一样艏先经历了这个问题的痛苦然后在压力和痛苦的驱动自己去找为什么,同时能够把为什么从代码层推理理解到OS层那么这个知识点你才算是比较好地掌握了,也会成为你的知识体系在TCP或者性能方面成长自我生长的一个有力抓手

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的值在不断地增大

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

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

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

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

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

CLOSED:无连接是活动的或正在进行
LISTEN:垺务器在等待进入呼叫
SYN_RECV:一个连接请求已经到达等待确认
SYN_SENT:应用已经开始,打开一个连接
LOSING:两边同时尝试关闭
TIME_WAIT:另一边已初始化一个释放
LAST_ACK:等待所有分组死掉

2、内核优化tcp连接

再执行以下命令让修改结果立即生效:

加载中,请稍候......

我要回帖

更多关于 linuxtcp优化 的文章

 

随机推荐