开发者社区 > 博文 > 从线上的http Read timeout到TCP重传机制
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

从线上的http Read timeout到TCP重传机制

  • 京东交易提升研发团队
  • 2021-01-15
  • IP归属:未知
  • 141320浏览

    1、本文目的

    从一个线上的 http read time问题来分析以下问题

    TCP长连接

    http长连接

    TCP重传机制

    2、背景

    线上的一个调用外部http服务的接口,可用率一直在波动。



    查看日志发现:报的错误是http timeout。


    看到这个错误,大部分人可能会想,这就是网络问题,或者对方接口响应慢,出现了读超时。

    和对方确认后:他们说他们的接口响应时间都正常,没有响应慢的问题。

    让网络组的也排查了下,说网络是正常的,没有找到原因。


    3、知识点介绍

    因为一个问题的分析往往需要一些前置的知识点,如果不知道的话,看起来可能会比较费力。下面先介绍一些知识点,便于后续问题的分析。

    3.1、TCP Keep alive

    大部分人可能听过http长连接,其实TCP也有keep alive机制:

    如果一个TCP连接空闲一段时间后(默认7200秒),会发送探测报文,如果没有响应,每隔75秒就会再发一个探测报文,重试9次后如果还没有响应就认为连接已经断开,会关闭释放连接。过程如下图所示:



    这里的值可以通过如下参数调整。

    发送心跳周期(秒) 2小时
    net.ipv4.tcp_keepalive_time = 7200

    探测包发送间隔(秒)
    net.ipv4.tcp_keepalive_intvl = 75

    探测包重试次数
    net.ipv4.tcp_keepalive_probes = 9

    上面的修改会影响所有的连接,如果修改个别连接,可以通过设置单个socket的一些特性来实现。

    由于TCP默认的长连接机制,如果有问题发现的比较晚。7200秒+75*8=7800秒=130分钟。所以在实际中使用TCP keep alive来监测连接是否存活的场景基本没有。

    3.2、http长连接

    3.2.1、http短连接

    每个http请求都会创建一个TCP连接、http请求响应完成后就会关闭TCP连接。如下图所示:


    3.2.2、http长连接

    HTTP短连接需要频繁的创建销毁TCP连接,性价比不高。因为出现了http长连接:用一个TCP连接、执行多个http请求。



    http长连接对于http 1.0和1.1实现是不一样的。

    http 1.0使用keep alive机制

    请求头:
    Connection: Keep-Alive
    响应头:
    Connection: Keep-Alive
    Keep-Alive: max=5, timeout=120

    http1.1 默认就是长连接

    默认:不关闭TCP连接
    关闭连接:
    Connection: Close

    keep-alive 已经不再使用了,而且在当前的 HTTP/1.1 规范中也没有对它的说明了。但浏览器和服务器对 keep-alive 的使用仍然相当广泛.


    3.2.3、Nginx的http长连接设置

    可以设置keepalive_timeout。如下图所示,这里设置的 13s 6s。分别表示:如果服务器端超过13s空间,就会断开连接. 6s是给客户端看的,客户端维持空闲连接的最大是时长。



    6s这个值会通过http响应头返回给客户端


    4、TCP重传机制

    TCP超时重传

    发送送发送一个报文如果没有ACK,发送方会在一定时间后(RTO)重新发送这个报文。


    TCP快速重传

    接收方接收到了期望(seq=100)的以后的报文(120、135、141)。接收方会一直ack之前缺失的100。如果发送方收到3次都一个报文的ack就会重传这个报文,而不会等到超时时间了再重传。


    5、问题分析

    通过抓取线上的数据报文,如下图所示:


    分析如下:

    第7个报文:建立一个新的连接
    第10、19、23个请求都是基于这个连接

    第23个请求报文发送之后没有ACK、后面的重传都是重传这个
    第30个报文触发程序设置的10秒读超时(第23个报文和第30个报文差10秒),程序调用close关闭连接,发送TCP的FIN报文
    31、32、33达到FIN-WAIT-1的超时时间,关闭链接

    从报文可以分析出:服务器端(端口是7105)应该是断开了连接,但是没有发送FIN,导致客户端还以为连接是存活的,这时候服务器发送第23个报文就会发送不成功一直重传,直到触发10秒服务器端读超时,调用close关闭链接,然后达到FIN-WAIT-1的重试时间,链接释放。


    这只是因素之一,还有一个因素就是服务器端也没有返回之前介绍的keep-Alive头。导致客户端自己也监测不到超时。



    这里你有没有疑问呢:这里是发送失败,为什么日志里面会报:read timeout,而不是 write timeout呢?

    因为我们平时发送报文的时候,底层实现是写socket内存缓冲区成功,函数就会返回认为发送成功 。至于网卡什么时候发出去,就是协议栈和网卡的事情了。

    写完成后就是读取,因为程序设置了读超时,所以超过时间就会报read timeout。


    6、修改

    服务器端虽然没有返回timeout,通过观察报文可以看出服务器至少会保持连接存活3分钟。通过修改程序设置httpclient的timeout如果服务器不返回就认为是1分钟。


    7、结果




    大家可以回过头看一下开头的那个图,会发现,调用频繁的时候可用率是100%,隔一段时间不调用(也就是连接空闲了),再调用的时候就会出现可用率问题。也从侧面验证了,空闲后连接被关闭了,客户端没感知到,继续使用已经断开的连接,也就是会发送失败,进而出现读超时。





    共1条评论