写点什么

NGINX 应用性能优化指南(第五部分):吞吐量

2016 年 5 月 05 日

【编者的话】本文是“NGINX 应用性能优化指南”系列文章的第五篇,主要介绍了如何从吞吐量方面实现 NGINX 应用性能优化。

注:本文最初发布于 MaxCDN 博客,InfoQ 中文站在获得作者授权的基础上对文章进行了翻译。

正文

NGINX 反向代理配置设置了两个网络 _ 路径 _:客户端到代理和代理到服务器。这两个路径不仅“HTTP 跨度(HTTP spans)”不同,TCP 网络传输域也不同。

尤其是提供大资源时,我们的目标是确保 TCP 充分利用了端到端连接。理论上讲,如果 TCP 流同 HTTP 数据紧密打包在一起,而且数据尽可能快地发送出去,那么我们就会获得最大的吞吐量。

但是,我时常在想,为什么我没有看到更快的下载速度。知道这种感觉吗?所以让我们深入底层,了解下基本原理。

网络传输入门

TCP 采用两个基本原则决定何时发送以及发送多少数据:

  1. 流量控制是为了确保接收者可以接收数据;
  2. 拥塞控制是为了管理网络带宽。

流量控制是通过接收者指定的接收窗口实现的,后者会规定接收者一次希望接收和存储的最大数据量。这个窗口可能会变大——从几个 KB 到若干 MB——这取决于测得的连接带宽延迟乘积(BDP,对此下文会有更多介绍)。

拥塞控制是由发送者实现为 RWND 大小的一个约束函数。发送者会将它传输的数据量限制为 CWND 和 RWND 的最小值。可以将此看作是遵从“网络公平性”的一种自我约束。窗口大小(CWND)会随着时间增长,或者随着发送者接收到先前传输的数据的确认而增长。如果检测到网络拥塞,那么它也会缩小窗口。

同时,发送者和接收者在决定最大可用 TCP 吞吐量时各自扮演一个重要的角色。如果接收者的 RWND 太小,或者发送者对网络拥塞过于敏感或者对网络拥塞减退反应太慢,那么 TCP 吞吐量都不会是最理想的。

管道填充

网络连接通常以管道为模型。发送者在一端泵入数据,接收者在另一端抽取数据。

BDP(以 KB 或 MB 为单位)是比特率同 RTT 的乘积,是一个表示需要多少数据填充管道的指标。例如,如果你在使用一个 100Mbps 的端到端连接,而 RTT 为 80 毫秒,那么 BDP 就为 1MB(100 Mbps * 0.080 sec = 1 MB)。

TCP 会设法填充管道,并保证没有管道泄露或破裂,因此,BDP 是 RWND 的理想值:TCP 可以发出的最大动态(还没有收到接收者的确认)数据量。

假设有足够的数据待发送(大文件),而且没有什么阻止发送应用程序(NGINX)以管道能够接受的速度向管道泵入数据,RWND 和 CWND 可能会成为实现最大吞吐量的限制因素。

大多数现代 TCP 栈会使用 TCP 时间戳以及窗口缩放选项自动优化这些参数。但是旧系统不会,有些应用程序会出现异常行为。因此,有两个明显的问题:

  1. 我如何检查?
  2. 我如何修复?

我们下面会处理第一个问题,但是修复 TCP 涉及学习如何优化 TCP 栈——这本身就是一项全职工作。更可行的方案是 TCP 加速软件或硬件。而且,这类供应商非常多,包括我每天都在研究的产品 SuperTCP

检查 RWND 和 CWND

设法确认 RWND 或 CWND 是否是限制因素,包括将它们同 BDP 进行比较。

为此,我们会嗅探在(无头)NGINX 代理上使用tcpdump工具进行大资源 HTTP(S) 传输的数据包,并将捕获的文件加载到带有图形界面的机器上的 Wireshark 中。然后,我们可以绘制一个有意义的图形,从而对这些基本变量是否得到了正确设置有一些了解。

复制代码
# tcpdump -w http_get.pcap -n -s 100 -i eth0 port <80|443>

如果你使用了一个不同的捕获过滤器,那么只要确保它捕获了 TCP HTTP 对话的双向数据。另外,还要确保是在发送设备上进行捕获,因为我们需要使用 Wireshark 正确地计算动态数据量。(在接收者一端进行捕获会使 Wireshark 相信 RTT 接近为 0,因为接收者 ACK 可能会在数据进来后立即发送出去)。

http_get.pcap文件加载到 Wireshark 中,找到感兴趣的 HTTP 流,然后仔细看下它的tcp.stream索引:

打开 Statistics->IO Graph,并进行如下配置:

  • Y-axis -> Unit: Advanced
  • Scale: Auto
  • Graph 5 (pink)
    • Filter:tcp.dstport==<80|443> && tcp.stream==< index>
    • Calc: MAX and tcp.window_size
    • Style: Impulse
  • Graph 4 (blue)
    • Filter:tcp.srcport==<80|443> && tcp.stream==< index>
    • Calc: MAX and tcp.analysis.bytes_in_flight
    • Style: FBar

接下来,务必按下(启用)Graph 4 和 Graph 5 按钮,根据那些结果进行绘图。下面的例子可能是你期望看到的:

我使用一个 100Mbps 的连接从一个 80 毫秒远的 NGINX 代理上 GET 一个 128MB 的文件(从 AWS 俄勒冈州到我们在加拿大渥太华的办公室)。相应的 BDP 为 1MB。

注意看下 RWND(粉色)的变化,开始时很小,数个往返后增长到稍稍超过 1MB。这证明接收者能够调整 RWND,并且可以察觉 BDP(好极了)。或者,如果我们看到 RWND 变小了(又称为关闭中),那表明接收应用程序读取数据的速度不够快——也许没有获得足够的 CPU 时间。

对于发送者的性能——CWND(蓝色)——我们想要一个指征,就是动态数据量会受到 RWND 限制。我们看到,在3 秒到 6 秒这个时间段里,NGINX 代理能够发出的动态数据量是 RWND 允许的最大数据量。这证明发送者能够推送足够的数据以满足 BDP。

不过,在快到6 秒时,似乎有东西出现了 _ 大问题 _。发送者发出的动态数据量显著减少。自我约束行为通常是由发送者检测到拥塞引起的。回想一下,GET 响应从西海岸传送到东海岸,遇到网络拥塞还是很可能的。

识别网络拥塞

当发送者检测到拥塞,它会缩小 CWND,以便减少它对网络拥塞的贡献。但是,我们如何才能知道?

通常,TCP 栈可以使用两类指示器检测或度量网络拥塞:数据包丢失和延迟变化(bufferbloat)。

  • 数据包丢失:任何网络都会出现数据包丢失,Wi-Fi 网络尤为突出,或者在网络元素积极管理它们的队列(比如随机早期丢弃)的时候,或者在它们根本没有管理它们的队列(尾部丢弃)的时候。TCP 会将丢失 ACK 或者从接收者那里收到了非递增 ACK(又称重复 ACK)当作数据包丢失。

  • Bufferbloat是由数据包积压越来越多导致的端点间延迟(RTT)增加。

    TIP:使用简单的工具ping或者更加复杂的工具mtr,有时候可以检测到有意义的 bufferbloat——尤其是当发送者推送数据速率较高的时候。那会给你深刻的印象,让你对更深层次的网络上可能正在发生什么有个真实的感受。

在同一个 Wireshark IO Graph 窗口中加入下列内容:

  • Graph 2 (red)
    • Filter: tcp.dstport==<80|443> && tcp.stream==< index>
    • Calc: COUNT FIELDS and tcp.analysis.duplicate_ack
    • Style: FBar
  • 另外将 Scale 改为Logarithmic

瞧!证据有了,接收者发送了重复 ACK,表明实际上有数据包丢失。这解释了为什么发送者缩小了 CWND,即动态数据量。

你可能还想寻找tcp.analysis.retransmission出现的证据,当出现数据包丢失报告,数据包必须重发时,或者当发送者等待来自接收者的 ACK 超时时(假设数据包丢失或者 ACK 丢失)。在后一种情况下,查下tcp.analysis.rto

对于上述两种情况,务必将 Filter 设置为tcp.srcport=*<80|443>*,因为重传源于发送者。

查看英文原文: NGINX Application Performance Optimization:Throughput


感谢郭蕾对本文的策划和审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016 年 5 月 05 日 18:063787
用户头像

发布了 1008 篇内容, 共 308.1 次阅读, 收获喜欢 272 次。

关注

评论

发布
暂无评论
发现更多内容

第1周作业

Rocky·Chen

【Java】变量声明在循环体内还是循环体外你选哪一个咧?

java金融

Java 变量声明

LeetCode题解:78. 子集,迭代,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

怎么样让自己的博客被谷歌和百度收录!

java金融

百度 SEO 博客收录 谷歌收录

第六周作业

alpha

极客大学架构师训练营

数据库JDBC:Statement查询

大规模数据处理学习者

JDBC sql查询 SQL光标

【分布式事务】面试官问我:MySQL中的XA事务崩溃了如何恢复??

冰河

MySQL 分布式事务 一致性 XA

前端不得不懂的架构知识(上)

执鸢者

架构 前端

架构师训练营第 1 期 - 第 6 周课后练习

Anyou Liu

极客大学架构师训练营

第六周总结

fmouse

极客大学架构师训练营

第二周作业

Griffenliu

架构作业 -- CAP原理

Nick~毓

第六周总结

alpha

极客大学架构师训练营

训练营第六周作业 1

仲夏

极客大学架构师训练营

第五周作业

icydolphin

极客大学架构师训练营

架构师训练营第二期 - 第二周作业

john_zhang

极客大学架构师训练营

week2 框架设计 作业和学习总结

杨斌

架构师系列之1:UML 系统设计用例图

桃花原记

Double Kill!! 数据联邦修炼之路

脑极体

第二周总结

Griffenliu

架构师训练营 - 作业 - 第六周

Max2@12

训练营第六周作业 2

仲夏

极客大学架构师训练营

第六周

等燕归

软件设计原则

猴子胖胖

软件设计原则

一个90后码农的真实经历,希望大家可以不留遗憾;

Java架构师迁哥

盘点 Mac 上好用的七款软件

彭宏豪95

效率 效率工具 软件 Mac

架构师系列之2:依赖倒置设计原则

桃花原记

如何抽取实体关系?——基于依存句法分析的事实三元组抽取

Guanngxu

自然语言处理

架构师训练营第二周学习总结

邢永春

Week2 - 框架设计

evildracula

学习 架构

第六周作业

fmouse

极客大学架构师训练营

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

NGINX应用性能优化指南(第五部分):吞吐量-InfoQ