一、客户端 connect 异常
-
出现的原因:端口号不足
-
客户端端口选取的方案:
- 首先选定一个随机数,然后从随机的位置开始向后遍历,直到找到一个可用的端口,就进行返回
- 如果端口很充足,那么循环只需要执行少数几次就可以退出。但假设说端口消耗掉很多已经不充足,或者干脆就没有可用的了。那么这个循环就得执行很多遍。
这是线上截取到的正常时的 connect 系统调用耗时,是 22 us(微秒)。
这个是我们一台服务器在端口不足情况下 connect 开销,是 2581 us(微秒)。\
从上两张图中可以看出,异常情况下的 connect 耗时是正常情况下的 100 多倍。虽然换算成毫秒只有 2 ms 多一点,但是要知道这消耗的全是 CPU 时间。
二、第一次握手丢包
2.1 半连接队列满了
- 半连接队列底层是一个 HashMap
- 如果半连接队列满了,可用通过设定
ipv4.tcp_syncookies
的参数进行处理- 1:通过
SYN Cookie
进行处理 - 0:直接丢弃
- 1:通过
SYN Cookie :在TCP服务器接收到TCP SYN包并返回TCP SYN + ACK包时,不分配一个专门的数据区(半连接队列),而是根据这个SYN包计算出一个cookie值。这个cookie作为将要返回的SYN ACK包的初始序列号。当客户端返回一个ACK包时,根据包头信息计算cookie,与返回的确认序列号(初始序列号 + 1)进行对比,如果相同,则是一个正常连接,然后,分配资源,建立连接。
2.2 全连接队列满了
- 当第一次握手的收获,如果半连接队列没有慢,当时全连接队列已经满了,也会自动将包进行对其
2.3 客户端重试
- 当全连接队列或者半连接队列满了,服务器如果选择将包自动丢弃,那么当超过时间限制的收获,客户端将会发起重试
- 需要注意的是,重试 下一次的超时时间是上一次超时时间的两倍
- 客户端在 1 s 以后进行了第一次握手重试。重试仍然没有响应,那么接下来依次又分别在 3 s、7 s 15 s,31 s,63 s 等时间共重试了 6 次(我的 tcp_syn_retries 当时设置是 6)。
第三次握手丢包
-
第三次握手时,如果服务器全连接队列满了,来自客户端的 ack 握手包又被直接丢弃了
-
服务器等到半连接定时器到时后,向客户端重新发起 synack ,客户端收到后再重新回复第三次握手 ack。如果这期间服务器端全连接队列一直都是满的,那么服务器重试 5 次(受内核参数 net.ipv4.tcp_synack_retries 控制)后就放弃了。