性能比拼: TCP vs UDP(延迟和吞吐量)

本内容是对知名性能评测博主 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 传输过程

  1. 服务器端

    • 服务器创建一个 套接字(socket) ,该套接字将返回一个 文件描述符(file descriptor)
    • 然后,它会将该套接字 绑定到服务器上的某个端口 ,例如 8080 端口
    • 之后,服务器开始 监听(listen) 传入的连接请求。
    • 当有客户端连接时,服务器调用 accept() 函数,该函数会 阻塞并等待客户端请求
  2. 客户端

    • 客户端也需要创建一个 套接字(socket)
    • 然后,它调用 connect() 函数 连接服务器
    • 这时,服务器端的 accept() 函数会返回 客户端的文件描述符 ,服务器可以使用它来 读写数据

例如,服务器可以向客户端发送 "Hello" 消息,客户端则使用 recv() 函数来接收所有消息。

如果客户端 没有收到完整的消息 (例如缺少一个字符),它会 请求服务器重新发送

这正是 TCP 被认为可靠的原因 ---客户端和服务器都会验证数据是否被完整接收 ,并且 保证数据的顺序不变


UDP 传输过程

UDP 的工作方式与 TCP 不同:

  1. 服务器端

    • 服务器创建一个 套接字(socket) ,并将其 绑定到端口
    • 但它 不需要与客户端建立连接 ,它可以 直接使用 recvfrom() 接收消息
  2. 客户端

    • 客户端 不需要调用 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 小时 ,但我将其 压缩为几分钟

该测试包含 两个阶段

  1. 第一阶段
    • 发送 每秒 16,000 条消息 ,测量 延迟CPU 使用率
    • 例如,你可以看到 TCP 服务器的 CPU 使用率很高 ,因为 TCP 需要执行数据验证
    • 结果表明,UDP 的消息发送延迟 显著低于 TCP,这正是预期的。

(此处用到的json和之前测试过的其他http框架时是一致的,可以比较一下使用http框架的延迟)

  1. 第二阶段
    • 发送 尽可能多的消息 ,测量 吞吐量
    • 结果显示,TCP 由于内置拥塞控制(congestion control),吞吐量更高
    • 你可以同时运行 1,000 个 TCP 连接 ,它们会 自动感知可用带宽,保持高效。
    • UDP 在高负载下可能反而降低吞吐量 ,并且 TCP 需要更多 CPU 资源处理消息

结论

UDP 不总是最快的协议 ,它在 小规模场景下表现优秀

但如果你关心 吞吐量 ,或者想要处理 大量消息或请求TCP 可能是更好的选择

当然,还有许多 基于 UDP 的协议 实现了 数据包级别的验证 ,提高了可靠性。

但无论选择哪种协议,你都应该 自己进行测试 ,并 测量所有关键指标


最后

感谢观看!

如果你对更多基准测试感兴趣,比如 数据库对比(PostgreSQL vs. MySQL)、缓存系统对比(Redis vs. Memcached),请继续关注我的频道!

相关推荐
huan9916 分钟前
Obsidian 插件篇 - 插件汇总简介
后端
周Echo周18 分钟前
5、vim编辑和shell编程【超详细】
java·linux·c++·后端·编辑器·vim
AronTing24 分钟前
03-深入解析 Spring AOP 原理及源码
后端
逻辑重构鬼才25 分钟前
AES+RSA实现前后端加密通信:全方位安全解决方案
后端
卤蛋七号32 分钟前
JavaSE高级(一)
后端
Java中文社群1 小时前
SpringAI用嵌入模型操作向量数据库!
后端·aigc·openai
暴力袋鼠哥1 小时前
基于Flask的跨境电商头程预警分析系统
后端·python·flask
一只爱撸猫的程序猿2 小时前
防止外部API服务不可用拖垮系统的解决方案
spring boot·后端·程序员
白露与泡影2 小时前
SpringBoot 最大连接数及最大并发数是多少?
spring boot·后端·firefox
radient2 小时前
线上死锁问题排查思路
后端