大部分开发者可能不会直接使用TCP协议进行网络开发,但是在分布式系统中,无可避免的需要接触到应用层协议,或是排查网络导致的问题。因此,对于TCP协议,不是每个开发者都需要熟读《TCP/IP详解》 [1] [2] [3] ,但还是建议能够了解一些TCP协议有关的知识。
有了一些基础知识,可以帮助我们更快的排查网络问题,例如,在 《性能探索——我们如何将每个POST请求削减200ms》 这篇博客中,作者介绍了他们对POST请求延迟问题的排查,为什么每个POST请求会多消耗200ms,这里摘录一些最终排查到的核心原因:
Ruby的Net::HTTP库,会将HTTP的POST请求拆分成两个TCP数据包:POST请求头一个数据包,请求体一个数据包。而curl命令却相反,它会尽可能的将请求头和请求体塞入一个数据包中。更糟糕的是,Net::HTTP在打开TCP套接字的时候,没有设置TCP_NODELAY选项,因此该套接字会等待第一个数据包的确认包(ack)之后,才会发送第二个数据包。该行为是 Nagle算法 的结果。
到连接的另一端,HAProxy需要选择如何应答这两个包。在版本1.4.15(我们曾经使用的版本)中,它选择使用TCP延迟应答。延迟应答和Nagle算法相互影响,引起了请求中断,直到服务端触发了延迟应答超时。
重要通知:接下来InfoQ将会选择性地将部分优秀内容首发在微信公众号中,欢迎关注InfoQ微信公众号第一时间阅读精品内容。
这时连接双方(Ruby Net::HTTP和HAProxy)的数据交互是这样的:
双方都在等待对方发送数据包,应用端等待HAProxy发送应答包(Nagle算法),HAProxy在等待应用端后续的数据包(延迟应答)。这就导致了中间的200ms延迟。
找到问题之后,解决就非常方便,在应用端设置TCP_NODELAY参数或者服务端取消延迟应答(TCP_QUICKACK参数)。另一个问题又来了,设置了这两个参数之后,对于应用和服务端有什么影响呢?
应用端套接字设置了TCP_NODELAY参数之后,TCP包将不会使用缓冲区而直接发送。如果应用端会发送大量小数据,可能会遇到缓冲区刷新的瓶颈,同时可能会有大量带宽浪费在了TCP头上。
服务端使用了TCP_QUICKACK,将不会合并发送应答包,同样会增加数据包数量。但是相对来说,应答包的损耗相对于延迟应答来说可能更小。
上面这个示例说明了,虽然大部分情况下,开发者不需要了解TCP协议,但是如果遇到了诸如延迟应答/TCP_NODELAY的问题,了解一些TCP协议相关知识是非常有用的。如今,分布式计算、分布式存储、微服务等架构的兴起,越来越多的系统需要和外部系统交互,其中大部分最终是基于TCP协议,没事了解一些TCP协议,在遇到性能调优、问题诊断时,可能会有意想不到的收获。
感谢魏星对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入InfoQ读者交流群 (已满),InfoQ读者交流群(#2) )。