尊敬的 KV,
我一直在调试一个网络问题,这个问题应该出现在一段简单的网络代码中。我们有一个小型服务器进程,它监听来自我们数据中心中所有其他系统的命令,然后将命令分发到其他服务器上运行。对于发出的每个命令,客户端都会建立一个新的 TCP 连接,发送命令,然后在我们的服务器确认命令后关闭连接。
在我们的原始数据中心,控制器没有任何问题,但是当我们迁移到更大的数据中心,拥有更多的客户端机器后,现在我们在尝试执行命令时经常无法建立连接,这减慢了整个系统的速度。这是一个如此简单的设计,我不知道哪里可能出了问题。控制器本身只有一页代码!我怀疑是新数据中心的网络设备有问题,它无法处理传入连接的负载。
连接被拒绝
尊敬的被拒绝者,
只有一页代码,但你却找不到错误?也许代码没有错误,问题确实出在交换机上。当然,这是开发人员通常的甩锅方式:“我的代码以前运行良好!你们改了什么!” 责怪变更。
问题是,你必须责怪正确的变更。没有什么比向某人发泄脾气,然后被证明是错的,不得不承认自己错了更糟糕的了。我希望你没有向网络管理员发泄脾气,因为在这种情况下,你绝对是错的。
现在我不会攻击你的代码;它很可能像你声称的那样正确。这里的责任在于未能理解 TCP 以及如何有效地使用它。
当你的客户端建立与服务器的连接时,它使用四个信息片段来唯一标识这两个主机之间发生的所有其他连接中的这个连接。这些信息片段是源 IP 地址和目标 IP 地址,以及源端口和目标端口。现在,我相信你的代码选择了一个单一的目标端口来发送;例如,Web 使用端口 80,电子邮件使用端口 25。无论你选择了什么,这可能都不是问题。
你为剩下的部分,即源端口选择了什么?大多数代码,尤其是简单到只有一页的代码,都让操作系统来决定。当你让操作系统决定源端口时,它会选择所谓的临时端口。请注意,临时在字典中的定义是“持续时间很短”,但当然,短是观察者的看法,或者在这种情况下,是 TCP 的计时器代码的看法。每当 TCP 关闭连接时,连接状态会在操作系统中保持活动状态,即所谓的 TIME_WAIT 状态。TIME_WAIT 状态至少持续连接的最大段长度的两倍,但在数据中心中,你永远不会让此状态持续少于 60 秒,这是系统定义的 2MSL(最大段生存时间的两倍)的最小值。
好的,现在你知道 短 在你的情况下意味着一分钟,这就是一个关闭的套接字会停留的时间。当关闭的套接字存在时,操作系统选择的临时端口号不能被重用。你可能会认为这应该没问题,因为必须有很多可用的端口号。不幸的是,TCP 是在机器之间拥有数百万个连接的想法实际上不切实际,甚至不可能的时代定义的,因此可能的端口数量可能会让你感到惊讶:它是 65,536。临时端口甚至没有获得完整的集合,而只是一个子集,并且在大多数现代操作系统上,范围约为 16,384。
这个限制意味着你只能打开 16,384 个使用临时端口的连接,除非你修改操作系统中的一些调整变量。即使你确实增加了范围,比如增加到 32,768,现代计算机也很容易在一分钟内用完这么多连接——请记住,任何你关闭的连接都会在关闭后停留一分钟。这个错误到现在才出现的原因是,你已经扩展了你的系统,使它们集体能够在在一分钟内用完它们的临时端口。
虽然你现在可能有很多关于扩展 TCP 或更改 2MSL 最小值的疯狂想法,但我可以告诉你,你应该忘记所有这些想法。TCP 的设计目的是维护互联网中的长流,而不是作为某种短消息服务。设置和拆除 TCP 连接的开销应该清楚地表明,要有效地使用它,需要在一次传输超过几个字节,这是早期 HTTP 设计者没有学到的教训,但这又是另一个故事了。
在你的情况下,使用 TCP 的正确方法是在每个客户端上都有一个本地进程,该进程维护与中央服务器的持久连接。如果没有不断地打开和关闭套接字,你就不会循环使用并耗尽你宝贵的临时端口。
KV
KODE VICIOUS,凡人称之为 George V. Neville-Neil,为了乐趣和利润而从事网络和操作系统代码工作。他还教授各种与编程相关的课程。他的兴趣领域是代码探险、操作系统以及重写你的糟糕代码(好吧,也许不是最后一个)。他获得了马萨诸塞州波士顿东北大学的计算机科学学士学位,并且是 、Usenix 协会和 IEEE 的成员。他是一位狂热的自行车爱好者和旅行者,目前居住在纽约市。
© 2010 1542-7730/10/0800 $10.00
最初发表于 Queue 第 8 卷,第 8 期—
在 数字图书馆 中评论本文
Charisma Chan, Beth Cooper - 调试 Google 分布式系统中的事件
本文介绍了 2019 年对 Google 工程师如何调试生产问题进行的研究结果,包括工程师在不同组合中有效调试所使用的工具类型、高级策略和低级任务。它考察了用于捕获数据的研究方法,总结了生产调查的常见工程过程,并分享了专家如何调试复杂分布式系统的示例。最后,本文将这项研究的 Google 特性扩展到提供一些可以在你的组织中应用的实用策略。
Devon H. O'Dell - 调试心态
软件开发人员花费 35-50% 的时间来验证和调试软件。调试、测试和验证的成本估计占软件开发项目总预算的 50-75%,每年超过 1000 亿美元。虽然工具、语言和环境减少了花在单个调试任务上的时间,但它们并没有显着减少花在调试上的总时间,也没有减少调试的成本。因此,过度关注在开发过程中消除错误是适得其反的;程序员应该拥抱调试作为一种解决问题的练习。
Peter Phillips - 使用跟踪增强调试
创建模拟器来运行旧程序是一项困难的任务。你需要彻底了解目标硬件以及模拟器要执行的原始程序的正确功能。除了功能正确之外,模拟器还必须达到以原始实时速度运行程序的性能目标。实现这些目标不可避免地需要大量的调试。错误通常是模拟器本身中的细微错误,但也可能是对目标硬件的误解或原始程序中实际已知的错误。(原始程序的二进制数据也可能已微妙地损坏或不是预期的版本。)
Queue Readers - 又一天,又一个错误
作为本期关于程序员工具的一部分,我们在 Queue 决定就调试主题进行一项非正式的网络调查。我们请你告诉我们你使用的工具以及你如何使用它们。我们还收集了关于那些难以追踪的错误的故事,这些错误有时会让我们想到改行。