前言 #
本文主体部分摘自【深入架构原理与实践】和 Cloudflare博客【A Question of Timing】,仅作记录和整理,用于备忘录。
Https 请求 #
首先我们需要了解下 HTTPS 请求有哪些阶段,以及各个阶段延迟如何计算。
HTTPS 请求阶段 #
一个完整、未复用连接的 HTTPS 请求需要经过以下 5 个阶段:DNS 域名解析、TCP 握手、SSL 握手、服务器处理、内容传输。
如图示,这些阶段共需要 5 个 RTT(Round-Trip Time,往返时间)RTT = 1 RTT(DNS Lookup,域名解析)+ 1 RTT(TCP Handshake,TCP 握手)+ 2 RTT(SSL Handshake,SSL 握手)+ 1 RTT(Data Transfer,HTTP 内容请求传输)。
各阶段耗时分析 #
HTTPS 请求的各个阶段可以使用 curl 命令进行详细的延迟分析。
curl 命令提供了 -w 参数,该参数支持 curl 按照指定的格式打印与请求相关的信息,部分信息可以用特定的变量表示,例如 status_code、size_download、time_namelookup 等等。因为我们要进行耗时分析,所以只关注和请求延迟有关的变量(以 time_ 开头的变量)。往文本文件 curl-format.txt 写入下面的内容:
$ cat curl-format.txt
time_namelookup: %{time_namelookup}\n
time_connect: %{time_connect}\n
time_appconnect: %{time_appconnect}\n
time_redirect: %{time_redirect}\n
time_pretransfer: %{time_pretransfer}\n
time_starttransfer: %{time_starttransfer}\n
----------\n
time_total: %{time_total}\n
变量名称 | 变量释义 |
---|---|
time_namelookup | 从请求开始到域名解析完成的耗时 |
time_connect | 从请求开始到 TCP 三次握手完成的耗时 |
time_appconnect | 从请求开始到 TLS 握手完成的耗时 |
time_pretransfer | 从请求开始到向服务器发送第一个 GET/POST 请求开始之前的耗时 |
time_redirect | 重定向耗时,包括到内容传输前的重定向的 DNS 解析、TCP 连接、内容传输等时间 |
time_starttransfer | 从请求开始到内容传输前的耗时 |
time_total | 从请求开始到完成的总耗时 |
一个简单的请求:
curl -w "@curl-format.txt" -o /dev/null -s 'https://wallpaper.naifuai.com'
time_namelookup: 0.000030
time_connect: 0.000440
time_appconnect: 0.155389
time_redirect: 0.000000
time_pretransfer: 0.155608
time_starttransfer: 1.273539
----------
time_total: 1.370519
-w
: 从文件中获取要输出的信息格式。-o /dev/null
: 把响应内容丢弃。执行不关心 HTTPS 的返回内容,只关心请求状态的情况。-s
: 不输出请求的进度条。
curl 打印的各个耗时都是从请求发起的那一刻开始计算,我们得将其转换成 HTTPS 各阶段耗时,例如域名解析耗时、TCP 建立耗时、TTFB耗时等。
耗时 | 说明 |
---|---|
域名解析耗时 = time_namelookup | 域名 NS 及本地 LocalDNS 解析耗时 |
TCP 握手耗时 = time_connect - time_namelookup | 建立 TCP 连接时间 |
SSL 耗时 = time_appconnect - time_connect | TLS 握手以及加解密处理 |
服务器处理请求耗时 = time_starttransfer - time_pretransfer | 服务器响应第一个字节到全部传输完成耗时 |
TTFB = time_starttransfer - time_appconnect | 服务器从接收请求到开始到收到第一个字节的耗时 |
总耗时 = time_total | 整个 HTTPS 的请求耗时 |
使用Chrome分析 #
Chrome 和其他一些测试工具使用 W3C Resource Timing 标准进行测量。在 Chrome 开发者工具中,如下所示:
同样,以下是它如何映射到典型的 HTTP over TLS 1.2 连接上,还显示了 Resource Timing 属性名称:
优化总结 #
了解 HTTPS 请求的各个阶段以及相应的延迟计算后,我们可以针对性地采取以下优化措施:
- 域名解析优化:减少域名解析产生的延迟。例如,提前获取域名解析结果备用,那么后续的 HTTPS 连接就能减少一个 RTT。
- 对传输内容进行压缩:传输数据的大小与耗时成正比,压缩传输内容是降低请求耗时最有效的手段之一。
- SSL 层优化:升级 TLS 算法和 HTTPS 证书,例如升级 TLS 1.3 协议,可将 SSL 握手的 RTT 从 2 个减少到 1 个。
- 传输层优化:升级拥塞控制算法以提高网络吞吐量。将默认的 Cubic 升级为 BBR 对于大带宽、长链路的弱网环境尤其有效。
- 网络层优化:使用商业化的网络加速服务,通过路由优化数据包,实现动态服务加速。
- 使用更现代的 HTTP 协议:升级至 HTTP/2,进一步升级到基于 QUIC 协议的 HTTP/3。
RTT #
Round-Trip Time 一个网络数据包从起点到目的地然后再回到起点所花费的时长。 ↩︎
TTFB #
Time To First Byte,首字节时间 指从浏览器请求页面到接收来自服务器发送的信息的第一个字节的时间
域名解析使用HTTPDNS #
“域名解析器”是 DNS 查询中的第一站,它作为客户端与“域名服务器”的中间人帮我们解析“整棵 DNS 树”。
但作为一个“中间商”,“域名解析器”很容易出现域名劫持、解析时间过长、解析调度不精准等问题。这些问题的根源在于 域名解析经历了过多的中间环节,服务质量不可控。为了解决上述问题,一种新型的 DNS 解析模式 —— HTTPDNS 应运而生。
HTTP DNS 是一种通过 HTTP 或 HTTPS 协议进行 DNS 查询的方式。与传统的 DNS 查询(通过 UDP)不同,HTTP DNS 使用 HTTP 请求来发送 DNS 查询,并获得响应。这种方式的优点包括:
- 安全性:HTTP DNS 可以通过 HTTPS 加密传输,保护 DNS 查询内容不被窃听或篡改。
- 绕过 DNS 劫持:可以避免某些网络环境下的 DNS 劫持或污染,因为查询通过 HTTP 发送到可信的 DNS 服务器。
- 可兼容性:在某些受限网络环境中,HTTP 通常是开放的,而 UDP 可能被封锁。
对传输内容进行压缩 #
通常可以使用 Gzip 对内容进行压缩,但针对 HTTP 类型的文本内容还有一个更高压缩率的算法 Brotli。
Brotli 是 Google 推出的开源无损压缩算法,它内部有一个预定义的字典,涵盖了超过 1,300 个 HTTP 领域常用单词和短语。Brotli 在压缩过程中将这些常见的词汇和短语作为整体匹配,从而大幅提升文本型内容( HTML、CSS 和 JavaScript 文件)的压缩密度。
HTTPS 优化 #
未进行任何优化的情况下,HTTPS 的延迟比 HTTP 高出几百毫秒。
使用 TLS1.3 协议 #
2018 年发布的 TLS 1.3 协议优化了 SSL 握手过程,将握手时间缩短至 1 次 RTT。如果复用之前的连接,甚至可以实现 0 RTT(通过使用 early_data 扩展)。
以 Nginx 配置为例,确保 Nginx 版本为 1.13.0 或更高,OpenSSL 版本为 1.1.1 或更高。然后,在配置文件中使用 ssl_protocols 指令添加 TLSv1.3 选项即可。
server {
listen 443 ssl;
ssl_protocols TLSv1.2 TLSv1.3;
# 其他 SSL 配置...
}
使用 ECC 证书 #
从 Nginx 1.11.0 开始,支持配置 RSA/ECC 双证书。双证书的实现原理是:在 TLS 握手过程期间,分析双方协商的密码套件(Cipher Suite),如果支持 ECDSA 算法则返回 ECC 证书,否则返回 RSA 证书。
Nginx 的双证书配置示例如下:
server {
listen 443 ssl;
ssl_protocols TLSv1.2 TLSv1.3;
# RSA 证书
ssl_certificate /cert/rsa/fullchain.cer;
ssl_certificate_key /cert/rsa/thebyte.com.cn.key;
# ECDSA 证书
ssl_certificate /cert/ecc/fullchain.cer;
ssl_certificate_key /cert/ecc/thebyte.com.cn.key;
# 其他 SSL 配置...
}
调整 https 会话缓存 #
HTTPS 连接建立后,会生成一个会话(session),用于保存客户端和服务器之间的安全连接信息。如果会话未过期,后续连接可以复用之前的握手结果,从而提高连接效率。
与会话相关的配置如下:
server {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1h;
}
上述配置说明如下:
- ssl_session_cache:设置 SSL/TLS 会话缓存的类型和大小。配置为 shared:SSL:10m 表示所有 Nginx 工作进程共享一个 10MB 的 SSL 会话缓存。根据官方说明,1MB 的缓存可以存储大约 4000 个会话。
- ssl_session_timeout:设置会话缓存中 SSL 参数的过期时间,决定客户端可以在多长时间内重用缓存的会话信息。在此例中,设定为 1 小时。
开启 OCSP stapling #
Nginx配置
server {
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/xxx.pem;
resolver 8.8.8.8 valid=60s;#
resolver_timeout 2s;
}
测试是否生效
openssl s_client -connect naifuai.com:443 -servername naifuai.com -status -tlsextdebug < /dev/null 2>&1 | grep "OCSP"
若结果中存在“successful”关键字,则表示已开启 OCSP Stapling 服务。
至此,整个 HTTPS 优化手段(TLS1.3、ECC 证书、OCSP Stapling)介绍结束。
使用 https://myssl.com/ 服务确认配置是否生效
HTTPS 配置推荐使用 TLS1.3 协议 + ECC 证书方式
网络拥塞控制 #
BBR 应用 #
Linux 内核从 4.9 开始集成了 BBR 拥塞控制算法。
- 首先,查询系统所支持的拥塞控制算法。
$ sysctl net.ipv4.tcp_available_congestion_control
net.ipv4.tcp_congestion_control = bbr cubic reno
- 查询当前使用的拥塞控制算法。
$ sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = cubic
- 修改拥塞控制算法为 bbr。
echo net.ipv4.tcp_congestion_control=bbr >> /etc/sysctl.conf && sysctl -p
BBR 测试 #
我们使用 tc(Traffic Control)工具模拟弱网环境,使用网络性能测试工具 iperf3 测试弱网环境下不同的拥塞控制算法的表现。
首先,在服务端设置 eth0 网络接口上的流量丢包率为 1%、延迟为 25ms。
tc qdisc add dev eth0 root netem loss 1% latency 25ms
在服务端使用 iperf3 开启测试服务,以服务端模式运行,设置监控时间2秒,并指定端口为 8080。
iperf -s -p 8080 -i 1
在客户端节点使用 iperf3 以客户端模式运行,请求 10.0.1.188 服务端的 8080 端口。
iperf3 -c 10.0.1.188 -p 8080
BBR 在轻微丢包的网络环境下表现出色。
边缘服务器“动态加速” #
区别于静态文件缓存技术 CDN,“动态加速”并非依赖缓存数据,而是利用“网络边缘服务器”优化 IP 路由和传输层实现网络加速。
目前,主流的技术服务商,如 Akamai、Fastly、Amazon CloudFront 和 Microsoft Azure 等在全球多个地区部署了数量庞大的边缘服务器,构建了一个庞大的全球性加速网络。
使用上述服务商提供的“动态加速”操作简单,一般将域名的解析记录 CNAME 到服务商提供的域名后,整个加速过程就能自动实现。操作流程大致如下:
- 源站(Origin)将域名 CNAME 到 CDN 服务商提供的域名,例如将 <www.thebyte.com.cn> CNAME 到 thebyte.akamai.com。
- 源站提供一个 20KB 左右的用于探测网络质量的文件资源。
- 服务商在源站周边选择一批候选的转发节点(Relay Node)。
- 转发节点对测试资源进行下载测试,多个转发节点多路探索后,根据丢包率、RTT、路由的 hops 数等,选择出一条客户端(End Users)到源站之间的最佳路径。