惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
P
Proofpoint News Feed
WordPress大学
WordPress大学
P
Proofpoint News Feed
宝玉的分享
宝玉的分享
腾讯CDC
罗磊的独立博客
P
Privacy International News Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Latest news
Latest news
C
Cisco Blogs
Project Zero
Project Zero
Apple Machine Learning Research
Apple Machine Learning Research
The GitHub Blog
The GitHub Blog
博客园 - 叶小钗
Spread Privacy
Spread Privacy
博客园 - 三生石上(FineUI控件)
Blog — PlanetScale
Blog — PlanetScale
C
Check Point Blog
博客园 - 聂微东
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tenable Blog
Know Your Adversary
Know Your Adversary
Security Latest
Security Latest
T
The Blog of Author Tim Ferriss
云风的 BLOG
云风的 BLOG
L
Lohrmann on Cybersecurity
IT之家
IT之家
T
Tor Project blog
S
SegmentFault 最新的问题
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
月光博客
月光博客
L
LINUX DO - 热门话题
C
CERT Recently Published Vulnerability Notes
T
Threat Research - Cisco Blogs
A
Arctic Wolf
D
DataBreaches.Net
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - 【当耐特】
T
The Exploit Database - CXSecurity.com
T
Threatpost
Hacker News - Newest:
Hacker News - Newest: "LLM"
Scott Helme
Scott Helme
博客园 - 司徒正美
Vercel News
Vercel News
H
Help Net Security
N
News and Events Feed by Topic
MyScale Blog
MyScale Blog
Simon Willison's Weblog
Simon Willison's Weblog
P
Privacy & Cybersecurity Law Blog

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 容器中如何正确配置 GOMAXPROCS ? singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.RWMutex Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 Docker 网络原理概览 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 Go 语言内置的设计模式 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 Go Delve 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式 Go 内存模型 Kubernetes 核心概念
TCP 到底有什么性能问题?
2022-10-10 · via 蛮荆

2022-10-10 计算机网络

  • 概述
  • 拥塞控制
    • 1. 慢启动
    • 2. 拥塞发生 (丢包)
      • 顺序可靠性保证
      • 改进和优化
  • 三次握手
    • HTTP
    • 网络切换
  • 确认与重传
  • 小结
  • 扩展阅读

概述

TCP 的性能问题本质是公平与效率的取舍问题。

TCP 实现可靠传输层的核心有三点:

  1. 确认与重传 (已经可以满足 “可靠性”,但是可能存在性能问题)
  2. 滑动窗口 (也就是流量控制,为了提高吞吐量,充分利用链路带宽,避免发送方发的太慢)
  3. 拥塞控制 (防止网络链路过载造成丢包,避免发送方发的太快)

滑动窗口主要关注发送方到接收方的流量控制

拥塞控制更多地关注整个网络 (链路) 层面的流量控制

滑动窗口和拥塞控制相互制约,使发送方可以从网络链路的全局角度来自动调整发送速率,从这个角度来看,TCP 对于整个网络的意义已经超过 “传输层”。


拥塞控制

相比滑动窗口,拥塞控制的视角更为全面,会对整个网络链路中的所有主机、路由器,以及降低网络传输性能的有关因素进行综合考量。

既然拥塞控制要考虑这么多因素,那就不可避免地会在某些场景下存在所谓 “性能问题”,下面来具体分析下。

1. 慢启动

慢启动本身不会造成性能问题,因为慢启动时,cwnd (拥塞窗口大小值) 是指数级增长,所以 “慢启动” 其实并不慢,这一点我们在之前的 TCP 拥塞控制实现原理 文章中已经讲过了。

但是在特定场景下 (如 HTTP),慢启动会增加数据传输的 往返次数

这里以 Linux 为例,内核在 3.0 之后,采用了 Google 的建议,将 cwnd 初始化为 10 个 MSS,默认的 MTU 为 1500, MSS 为 1460, 那么,第一次发送的 TCP 数据 (Segment) 总量为:

$$ 1460 * 10 = 14600 ≈ 14 KB $$

默认情况下,ssthresh 值为 65 KB,也就是从 慢启动阶段 进入到 拥塞避免阶段 的阈值。

我们随便访问一个网站的主页 (例如 stackoverflow.com), 这里就以其首页 html 文本数据大小 (68.8 KB) 为例,说明一下慢启动对于服务端发送响应数据,带来了哪些性能影响。

  1. 第 1 次发送的数据总量为: 14 KB
  2. 经过 1 个 RTT, cwnd 翻倍
  3. 第 2 次发送的数据总量为: 28 KB
  4. 经过 1 个 RTT, cwnd 再次翻倍
  5. 第 3 次发送的数据总量为: 56 KB

经过 3 次发送后,14 + 28 + 56 = 98 KB, 首页 html 文本数据传输完成,一共经历了 3 个往返次数。

当网页资源加载完成后,一般很少再去加载其他资源/数据,但是此时,cwnd 也才刚刚接近 ssthresh 阈值大小。

假设现在我们消除掉慢启动阶段,直接火力全开,第 1 次就发送数据总量: 65 KB, 那么就只需要经历 2 个往返次数,就可以完成数据传输。

2. 拥塞发生 (丢包)

在基于丢包的拥塞控制算法中 (例如 Reno、Cubic、NewReno), 认为一旦发生丢包,就是网络链路发生了拥塞,所以发送方会急剧地减小发送窗口,甚至进入短暂的等待状态(超时重传)。

1% 的丢包率并不只是降低 1% 的传输性能,而是可能降低 50% 甚至更多 (取决于具体的 TCP 实现),此时就可能出现极端情况: 网络花在重传被丢掉的数据包的时间比发送新的数据包的时间还要多,所以这是造成 所谓 TCP 性能问题 的最大元凶。

丢包同时会加重网络链路拥塞,假设 1 个 TCP 数据段转发到第 N 个路由器,前 N-1 个路由器已经完成转发,但是第 N 个路由器转发时丢失了数据段,最终导致丢失的数据段浪费了前面所有路由器的带宽。

TCP Reno 算法发生丢包时,性能直接腰斩

TCP Tahoe 算法发生丢包时,直接重置,进入慢启动过程

这里以 HTTP 场景为例,丢包带来的影响在 HTTP/2 中表现更为严重,因为 HTTP/2 只使用 1 个 TCP 连接进行传输。所以 1 次丢包会导致所有的资源下载速度变慢。而 HTTP/1.1 可能有多个独立的 TCP 连接,1 次丢包也只会影响其中 1 个 TCP 连接,所以这种场景下 HTTP/1.1 反而会获得更好的性能。

顺序可靠性保证

虽然 TCP 确保所有数据包有序到达,但是这个顺序语义保证可能会引发类似 HTTP 队列头部请求阻塞 (head of line blocking) 问题。

TCP 在传输时使用序列号 (Seq) 标识数据的顺序,一旦某个数据丢失,后续的数据需要保存在接收方的 TCP 缓冲区中,等待丢失的数据重传完成后,才能进行下一步处理 (传递到应用层)。

应用层无法得知 TCP 接收缓冲区的情况,所以必须等待序列 (Seq) 完整之后才可以获取应用数据。但是实际上,已经接收到的数据包中,很可能就有应用层可以直接处理的数据,所以,这也可以称之为 TCP 队列头部请求阻塞 (head of line blocking) 问题。

改进和优化

针对基于丢包的拥塞控制算法,最明显的改进就是使用更为合理的拥塞控制算法,例如可以更好地适应高带宽、高时延、且容忍一定丢包率的 BBR 算法。

如果保证 TCP 可以在 0 丢包 的前提下传输数据,那么自然而然可以最大化利用带宽。

🤔: 思考: 如果高丢包的情况下,使用 UDP 会获得更好的性能吗?


三次握手

除了拥塞控制引起的 “性能问题” 外,TCP 建立连接时的三次握手机制,在某些场景下也会引起性能问题。

对于大多数 TCP 的使用场景 (长连接 + 频繁数据传输),三次握手几乎可以忽略不计。真正会造成性能影响的是 长时间 + 大量短连接 场景,针对这个问题, 可以考虑将短连接改造为长连接,或者使用 TFO 技术 来进行优化。

此外,还有 2 个会引发性能问题的场景是 HTTP 和网络切换。

HTTP

HTTP/1.1 版本中,访问不同的的资源时 (CSS, Javascript, images …) 会使用多个 TCP 连接会产生大量的延迟,如下图所示。

使用 HTTP/1.1 访问时的瀑布图

解决方案也很简单:直接升级使用 HTTP/2, 在整个通信过程中,只会有 1 个 TCP 连接。

使用 HTTP/2 访问时的瀑布图

除此之外,有的读者可能会想到 “弱网络” 这个使用场景 (例如人群密集的地铁车厢),但是,既然都是 “弱网络” 了,那么使用其他的传输协议也很难规避这个问题。

网络切换

TCP 连接迁移:受限于 TCP 四元组 的限制,如果源 IP 发生变化,则需要重新建立 TCP 连接,从而导致延迟暂停 (例如当前设备从 Wifi 切换到蜂窝网络)。

类似的场景,还有多个物理场所使用不同的出口公网 IP 地址,例如学校的图书馆和宿舍,公司的会议室和办公区,当使用者切换物理空间时,也会发送重新建立 TCP 连接。

当然,该问题同样可以通过使用 TFO 技术 来进行优化。


确认与重传

这块 (可能) 会引发 TCP 性能问题的原因,主要涉及到 3 点:

  1. TCP 超时重传带来的性能影响
  2. TCP 快速重传的局限性
  3. TCP 选择性重传解决了哪些问题

细节部分在 之前的文章中 已经详细讲过了,本文不再赘述。


小结

现代 TCP 在理想传输条件下,性能只受限于光速和接收方缓冲区 (内存) 大小,也就是硬件和物理。

硬件方面,有例如 TOE, NIC 的各种助攻和加速。

所以最终 TCP 在理想情况下的硬件性能受限于:

  1. 链路中最小的带宽
  2. 链路中最慢的硬件处理
  3. 链路中最小的接收缓冲区大小

三者结合起来,也就是通信过程中的所谓 “瓶颈链路”。

如果不存在硬件性能限制,也就是在足够的带宽、足够的内存、足够的处理速度的前提下,TCP 的性能理论上只受限于物理,也就是光速

最后,再次搬上大佬语录:

网络编程中,开发者遇到的实际问题,大约有 90% 都和开发者对于 TCP/IP 的理解有关。不要对 TCP 和 UDP 的相对性能做任何先验假设,即使是很小的擦不参数改变,都可能对性能产生严重影响。

扩展阅读