本内容是对知名性能评测博主 Anton Putra TCP vs UDP Performance: HUGE Improvement! 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准
在本期视频中,我们将再次对比 TCP 和 UDP 协议,重点关注延迟(latency)和吞吐量(throughput)。在我进行另一个基准测试时,我发现了几个可以改进之前测试的方法,因此我决定与大家分享这些内容。
TCP 协议是如何工作的?
首先,让我们了解一下 TCP 协议的工作方式。
客户端和服务器之间需要建立连接,这通过 三次握手 来完成。
- 首先,发送方发送一个 “同步”(SYN)数据包给接收方,表示客户端希望发起连接,并提供一个随机的序列号,这个序列号将在后续数据传输中使用。
- 然后,接收方回应一个 “同步-确认”(SYN-ACK)数据包,表示“收到!这是我的序列号,并且我确认你的序列号。”
- 最后,发送方回复一个 “确认”(ACK)数据包,序列号加 1,并确认:“很好,我们同步了,可以通信了。”
我们可以使用 Wireshark 工具进行观察。为了简化,我们可以监听本地回环地址(localhost),并设置抓包过滤器,只捕捉端口为 8080 的数据包(无论是源端口还是目标端口)。
此刻我们还没有任何数据包,所以我将先启动服务器,然后发送一个 JSON 负载。
现在我们可以看到 TCP 的三次握手过程了:包括 SYN、SYN-ACK 和最终的 ACK 数据包,表明一个 TCP 连接已经建立。
我运行的服务器监听的是 8080 端口,但当你创建客户端时,它会随机选择一个端口来创建 TCP 套接字,并连接到服务器。
你还可以看到服务器向客户端发送了一个 JSON 消息,而客户端在接收到该消息后,会返回一个仅包含头部的空数据包,表示它已经收到了消息。
测试
为了进行测试,我使用了 AWS 平台。在这次测试中,我为参与测试的每个应用程序分别使用了一台 m7a.medium 实例。
我使用 C++ 和原始套接字(raw sockets) 实现了 TCP 和 UDP 消息的发送与接收。
相关的源代码已经发布在我的公共 GitHub 仓库中。
我将从与上一次测试完全相同的设置开始,整个测试分为两个阶段:
- 第一阶段,我们只发送 16,000 条消息。你会发现,UDP 的延迟更低
---
这在意料之中,因为 UDP 不需要建立连接,也不需要确认接收。 - 第二阶段,我们尝试使用 TCP 和 UDP 各自发送尽可能多的数据。你会注意到,由于 TCP 拥有更好的拥塞控制机制,它能实现更高的吞吐量。
最终测试结果非常值得注意:
- TCP 的吞吐量可达约 620,000 条消息/秒
- UDP 的吞吐量则只有 310,000 条消息/秒
不过,在最大负载下,你也会发现延迟差异变得更明显。UDP 在需要低延迟的场景下表现更好,而 TCP 在你更关注吞吐量和可靠性时表现更优秀。
第一个改进
我原以为使用 CMake 构建项目时,默认会生成一个经过优化的、可用于生产环境的二进制文件。
但我错了。
所以在下一轮测试中,我将使用 release 模式重新编译相同的源代码。
测试仍然分为两个阶段:
- 第一阶段基本与之前相同,可能会看到 稍微降低的 CPU 使用率,但没有显著变化。
- 第二阶段中,TCP 的吞吐量提升到了 1,000,000 条消息/秒,令人惊讶的是,UDP 的吞吐量反而没有增加,不过延迟有所降低。
因此,即使是简单地将应用程序编译为 release 模式,也能同时提升两个协议的表现。
这是一个愚蠢的错误,我没有任何借口。不过它确实展示了:通过正确编译应用程序可以获得更高的性能。
第二个改进
最后一个改进其实涉及到 监控(monitoring)。
我知道给应用程序添加指标会降低性能,但我以前从未实际测量过影响到底有多大。
所以,我使用 Prometheus 的直方图(histogram) 来测量客户端发送和接收的 UDP 与 TCP 消息的延迟。
很多人在生产环境中也会这么做,但如果你测量的是类似原始 TCP 或 UDP 数据包这种层级的内容,那么这些测量行为本身就会影响性能。
因此,在下一次测试中,我继续使用 Prometheus 的计数器(counter) 来计算接收到的消息速率,但我关闭了 histogram。这样你在最终测试中将看不到延迟的统计。这是第一阶段。
在第二阶段,我们可以看到:
- TCP 达到了 1,100,000 次请求/秒,比上一个测试提升了 10%
- UDP 也有相同程度的提升,现在约为 420,000 条消息/秒
这个测试说明,仅仅是为高性能应用程序添加监控指标,就可能导致性能下降 10% 或更多。所以一定要非常小心 ---
每一次函数调用和性能测量,都会降低程序的运行效率。
这在例如 交易系统 这样的场景中尤其重要,因为每一微秒都至关重要。
在下一期视频中,我将对比 WebSockets 与 HTTP 或 REST API 的表现。但在此之前,你可以先查看我关于 数据库、缓存以及不同编程语言 的其他基准测试。