本内容是对知名性能评测博主 Anton Putra TCP vs UDP Performance (Latency & Throughput) 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准
介绍
最近,我正在开发一个 高性能项目 ,所以我决定尝试使用 原始套接字(raw sockets) 来通过 TCP 和 UDP 发送和接收 JSON 消息。
我想要比较 两种协议的延迟(latency)和吞吐量(throughput),并且不依赖任何框架。
我使用的是 C++ 语言 ,并参考了我认为目前 互联网上最好的网络编程指南。
什么是性能?
在衡量 任何事物的性能 时,我们需要关注 延迟(latency) 和 吞吐量(throughput)。
-
延迟(Latency)
在这个测试中,延迟指的是 从一台计算机发送 JSON 消息到另一台计算机接收它所花费的时间 。
我们通常使用 百分位(percentiles) 来测量延迟,例如 p50、p90 和 p99。
- p90 表示 90% 的消息 在此时间内被成功发送并接收。
- p99 表示 99% 的消息 在此时间范围内完成传输,依此类推。
-
吞吐量(Throughput)
吞吐量衡量的是 单位时间内可以同时发送和接收的消息数量 。
我们通常以 每秒消息数(messages per second)、每秒请求数(requests per second)或每秒查询数(queries per second) 来测量它。
通常情况下,如果一个系统被优化为 低延迟 ,那么它的 吞吐量可能较低 ,反之亦然。
在本次测试中,你将看到这一点的具体表现。
TCP vs UDP
你可能已经知道,TCP 是一个 更可靠的协议 ,但它需要先 建立连接。
TCP 传输过程
-
服务器端
- 服务器创建一个 套接字(socket) ,该套接字将返回一个 文件描述符(file descriptor)。
- 然后,它会将该套接字 绑定到服务器上的某个端口 ,例如 8080 端口。
- 之后,服务器开始 监听(listen) 传入的连接请求。
- 当有客户端连接时,服务器调用 accept() 函数,该函数会 阻塞并等待客户端请求。
-
客户端
- 客户端也需要创建一个 套接字(socket)。
- 然后,它调用 connect() 函数 连接服务器。
- 这时,服务器端的 accept() 函数会返回 客户端的文件描述符 ,服务器可以使用它来 读写数据。
例如,服务器可以向客户端发送 "Hello" 消息,客户端则使用 recv() 函数来接收所有消息。
如果客户端 没有收到完整的消息 (例如缺少一个字符),它会 请求服务器重新发送 。
这正是 TCP 被认为可靠的原因 ---
客户端和服务器都会验证数据是否被完整接收 ,并且 保证数据的顺序不变。
UDP 传输过程
UDP 的工作方式与 TCP 不同:
-
服务器端
- 服务器创建一个 套接字(socket) ,并将其 绑定到端口。
- 但它 不需要与客户端建立连接 ,它可以 直接使用 recvfrom() 接收消息。
-
客户端
- 客户端 不需要调用 connect() ,也 不需要建立连接 ,它可以直接向服务器 发送消息。
- 但客户端 不知道服务器的状态 ,它只是不断发送消息,假设服务器正在监听。
由于 UDP 不需要建立连接 ,它通常具有 更低的延迟 。
然而,因为服务器不会 确认数据是否完整接收 ,UDP 消息可能会 缺失或顺序混乱。
尽管如此,UDP 在 视频和音频流(streaming)、金融交易系统(trading systems) 等对 低延迟要求极高 的场景中仍然非常有用。
TCP vs UDP 的方向性
在 TCP 中,每个 客户端与服务器之间 都是 一对一的连接 。
例如,如果有 多个客户端 连接到 同一台服务器 ,那么每个客户端都必须 单独建立 TCP 连接,才能开始发送和接收数据。
但在 UDP 中,客户端可以 同时向所有服务器发送消息 ,这被称为 广播(broadcasting)。
例如,在 AWS VPC(虚拟私有云) 内,UDP 消息可以被 所有服务器接收和处理 。
但如果 只有部分服务器需要接收消息 ,广播就 不够高效 ,这时可以使用 多播(multicasting) ,只向 特定的服务器组 发送数据。
在这种情况下,我们可以有一个 单个生产者(producer) 和 多个消费者(consumers)。
代码概述
我使用 C++ 编写了 TCP 和 UDP 的服务器与客户端 ,你可以在我的 GitHub 公开仓库 中找到完整的 源代码。
此外,我还在每个客户端上 集成了 Prometheus 监控,并使用了:
- 直方图(histogram) 来测量 延迟,
- 计数器(counter metric) 来测量 吞吐量(由于某些异常情况)。
在接下来的视频中,我会提供一个 从零开始的 C++ 或 C 语言套接字编程指南。
测试
我在 AWS 上运行了基准测试,并为 TCP 和 UDP 服务器及客户端 分配了 专用虚拟机(VM) 。
由于我的应用是 单线程(single-threaded) ,只能使用 一个 CPU 核心 ,所以我选择了 m7a.medium EC2 实例 (1 个 CPU,4GB 内存 )。
当然,我还使用 EKS(Elastic Kubernetes Service) 运行 Prometheus 及其他监控组件。
测试过程
整个测试 大约持续 1 小时 ,但我将其 压缩为几分钟。
该测试包含 两个阶段:
- 第一阶段 :
- 发送 每秒 16,000 条消息 ,测量 延迟 和 CPU 使用率。
- 例如,你可以看到 TCP 服务器的 CPU 使用率很高 ,因为 TCP 需要执行数据验证。
- 结果表明,UDP 的消息发送延迟 显著低于 TCP,这正是预期的。
(此处用到的json和之前测试过的其他http框架时是一致的,可以比较一下使用http框架的延迟)
- 第二阶段 :
- 发送 尽可能多的消息 ,测量 吞吐量。
- 结果显示,TCP 由于内置拥塞控制(congestion control),吞吐量更高。
- 你可以同时运行 1,000 个 TCP 连接 ,它们会 自动感知可用带宽,保持高效。
- 而 UDP 在高负载下可能反而降低吞吐量 ,并且 TCP 需要更多 CPU 资源处理消息。
结论
UDP 不总是最快的协议 ,它在 小规模场景下表现优秀 。
但如果你关心 吞吐量 ,或者想要处理 大量消息或请求 ,TCP 可能是更好的选择。
当然,还有许多 基于 UDP 的协议 实现了 数据包级别的验证 ,提高了可靠性。
但无论选择哪种协议,你都应该 自己进行测试 ,并 测量所有关键指标。
最后
感谢观看!
如果你对更多基准测试感兴趣,比如 数据库对比(PostgreSQL vs. MySQL)、缓存系统对比(Redis vs. Memcached),请继续关注我的频道!