Table of Contents

Why You Should Understand (a Little) About TCP

为什么你应该了解(一点)TCP的知识?

我想说服你,了解一点TCP的知识(比如数据包是如何工作的,什么是ACK)是很重要的,即使你的系统只是在进行常规无聊的HTTP请求。让我们从我在工作中遇到的一个谜团开始:多出40毫秒的情况。

有一天,有人在Slack中提到:“嘿,我在向NSQ发布消息,每次都要花40毫秒。” 说一下背景。NSQ是一个队列。你发布消息的方式是在localhost上进行HTTP请求。向localhost发送一个HTTP请求确实不应该花40毫秒。有些地方出了大问题。NSQ 守护进程并没有处于高 CPU 负载下,也没有使用大量的内存,它似乎没有处于垃圾收集暂停状态。

然后,我想起了一周前读过的一篇文章,叫做 “In Search of Performance: 我们是如何将每个POST请求减少200毫秒的。”那篇文章描述了两个TCP特性(延迟ACKs和Nagle算法)的组合如何合谋给每个POST请求增加了很多额外的时间。

以下是延迟的ACK加上Nagle的算法是如何让你的HTTP请求变慢的。我将在我读到的blog中告诉你发生了什么。首先,介绍一下他们的设置背景。

  • 他们有一个应用程序在向HAProxy发送请求。
  • 他们的HTTP库(Ruby的Net::HTTP)以两个小包(一个是头包,一个是主体包)发送POST请求。 下面是TCP交换的情况。
  1. 客户端:嗨!这是数据包1。
  2. 服务器:<沉默>. (“我最终会ACK的,但我们还是等第二个数据包吧。”)
  3. 客户端:<沉默>. (“我还有更多的数据要发送,但让我们等待ACK。”)
  4. 服务器:好吧,我很无聊,这里有一个ACK。
  5. 客户端:太好了,这是第二个数据包!!!

客户端和服务器都在被动地等待对方发送信息的那段时间?这就是多出来的200毫秒! 客户端是因为Nagle的算法在等待,而服务器是因为延迟ACKs在等待。

延迟ACKs和Nagle的算法在Linux上都是默认启用的,所以这并不是什么不寻常的事情。如果你用多个TCP包发送数据,就会发生在你身上。

解决方法是TCP_NODELAY。当我看到这篇文章的时候,我想:“这不会是我的问题吧?是吗?问题不可能出在TCP上!” 但是我读到可以通过在客户端启用TCP_NODELAY来解决这个问题,这个套接字选项可以禁用Nagle的算法,这似乎很容易测试,所以我提交了一个更改,为我们的应用程序打开TCP_NODELAY,然后BOOM。所有的40毫秒的延迟瞬间消失了。一切都搞定了。我是个魔法师。

如果不了解TCP,就不能解决TCP问题。我曾经认为TCP是非常低级的,我不需要理解它——这在很大程度上是事实!但在现实生活中,有时会有一个错误,而这个错误是由TCP算法中的某些东西引起的。我发现,在运维工作中,这些错误的数量惊人,它们是由我的系统的一个低级组件引起的,我以前认为这些组件很模糊,突然间需要非常迅速地了解更多。

我之所以能够理解并修复这个bug,是因为在两年前,我花了一周时间用Python写了一个玩具TCP协议栈,学习TCP的工作原理。对TCP协议以及数据包是如何被ACK的有了基本的了解,确实帮助我解决了这个问题。

我们如何构建本书的结构

SRE虽然涉及复杂的技术系统,但归根结底是一种文化实践。文化是人的产物,这启发我们根据你在组织中的SRE数量来组织本书的各个部分–你具体处理什么,你的一天是怎样的,取决于有多少个SRE工程师。我们将本书的文章分为 “SRE新手” 、0-1个SRE、1-10个SRE、10-100个SRE和 “SRE的未来 ”。

读者如果想找寻先从哪里开始的指导,可以直接跳到最适用于自己的部分;但是,你仍然会发现阅读那些目前并不适用于你日常的部分的文章的价值。

在0到1个SRE时,还没有人被指定为SRE,或者你已经找到了你的第一个SRE,这个角色看起来几乎是孤独的。

在1到10名SRE时,你正在组建一个团队,有知识共享和分工的能力。

在10到100个SRE时,你已经成为一个组织,你需要思考的不仅仅是你所从事的系统,还需要思考如何组织这么多SRE。

“SRE新手” 涵盖了基础性的话题(尽管并不详尽!),对于那些刚刚开始SRE之旅的人来说是很有帮助的,即使是最有经验的SRE,也是一种复习。 “SRE的未来” 包含了一些文章,这些文章探讨了SRE潜在的发展方向,或者是(目前)坐拥时代潮流。

没有必要按照任何特定的顺序阅读本书。你可以从头到尾读一遍。或者,如果你对某个特定的主题感到好奇,可以翻到索引,在那里你可以找到关于该主题的所有文章。把它作为参考指南,或者是灵感的来源–可以在需要的时候提供一个震撼。或者,也许可以建立一个阅读俱乐部,每周一次挑选一篇文章与同事讨论。这就是散文集的魅力所在。我们希望你和我们一样喜欢阅读它们。

结语

SRE系列的文章,有时间我就会翻译一些,希望大家能学到对自己有用的东西。谢谢

延伸阅读

TCP连接中启用和禁用TCP_NODELAY有什么影响?

30年前,阳澄湖的蟹农老王,每抓一只大闸蟹,就雇佣机器人开着卡车送到上海,然后再带着卖蟹的钱回来,老王想,这样好处是回款快(延迟小)。

但是,30年前的乡间小路,很快被一辆辆的卡车所堵塞,结果三天三夜也没有到达上海,老王的如意算盘泡汤了。

痛定思痛,老王觉得,没有必要一个螃蟹一辆车,可以将上午的所有螃蟹装在一辆卡车,上午发车;下午抓的螃蟹装在另一辆卡车里,下午发车。这样即使一天抓1000个螃蟹,也只需要两卡车,而不需要1000辆卡车

这样,乡间的小路也不会造成拥堵,上午发货,不一会儿,卖蟹的钱会被卡车运回来。

以上就是Nagle算法的通俗解释。

70-80年代,一些远程交互式软件,如Rlogin,客户端将用户输入的每一个字符独立传输到服务器端,服务器端再将这一个字符发回来,rlogin再显示到用户屏幕上。这样一个字节的字符却需要20字节的IP+ 20字节的TCP头,这样的传输效率非常低下,只有 141 = 2.43%

更要命的是,那时网络带宽特别窄,这样的传输模式很容易将窄窄的带宽挤满而丢包,再重传、再丢包的恶性循环。

于是Nagle发明了一个算法,针对交互式应用,将用户敲入的字符缓存一下,聚集了几个字符放在一起发送,这样传输效率则高得多,唯一的不足是,可能会有一些延迟。

为了避免延迟过大,等待用户时间由定时器控制,比如100-200毫秒,定时器到了,立马将缓冲区的数据发送出去。

但记住一点,Nagle算法是时代的产物,因为当时网络带宽有限。而当前的局域网、广域网的带宽则宽裕得多,所以目前的TCP/IP协议栈默认将Nagle算法关闭,即通过tcp_nodelay on; (nginx的配置) 禁用nagle算法,也即不缓存数据。

这就好比,现在老王用一辆卡车运一只螃蟹,走沿海高速也不会堵,尽管这听起来很荒诞…

参考文档

  1. https://www.zhihu.com/question/42308970