一次真实故障:回源HTTPS导致全站超时
2023年双11大促前夜,我们CDN节点突然大面积502。排查发现:回源HTTPS连接数暴涨,源站Nginx worker进程全部卡在SSL握手阶段。当时源站配置是PHP8.3 + Laravel11,CDN用阿里云DCDN,回源协议强制HTTPS。压测数据:1000并发下,回源HTTPS耗时平均2.3s,其中TLS握手占1.8s。这就是典型的回源HTTPS性能瓶颈。
问题拆解:回源HTTPS到底慢在哪
回源HTTPS慢在三个环节:
- TLS握手:每次回源都要完成TCP三次握手 + TLS 1.3握手(至少2-RTT),加上证书链验证
- OCSP查询:浏览器端有OCSP Stapling,但CDN回源时源站默认没有,每次握手都要去CA查证书状态
- Session复用:CDN节点到源站的连接池管理不当,导致频繁新建TLS会话
我们用Wireshark抓包确认:一次完整回源HTTPS请求,从CDN到源站耗时2.1s,其中TLS握手1.6s,数据传输仅0.5s。这意味着80%的时间浪费在握手。
三种回源策略对比
方案A:直连HTTPS回源(原始方案)
CDN直接发起HTTPS请求到源站。配置简单,但性能最差。
# 源站Nginx HTTPS配置(原始)
server {
listen 443 ssl http2;
server_name origin.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# 没有OCSP Stapling
# 没有Session Cache优化
}
压测数据(wrk -t4 -c100 -d30s):
| 指标 | 直连HTTPS |
|---|---|
| 平均延迟 | 2.3s |
| P99延迟 | 3.8s |
| 吞吐量 | 43 req/s |
| 错误率 | 12% |
方案B:私有协议回源(HTTP + 加密隧道)
CDN和源站之间走自定义协议,比如用gRPC over HTTP/2,或者WebSocket隧道。优点是握手次数少,但需要改应用代码。
// PHP端gRPC客户端示例(需要grpc扩展)
$client = new OriginClient('10.0.0.1:50051', [
'credentials' => Grpc\ChannelCredentials::createInsecure(),
'timeout' => 1000, // 1秒超时
]);
$request = new Request();
$request->setPath('/api/data');
$request->setMethod('GET');
list($response, $status) = $client->Fetch($request)->wait();
压测数据:
| 指标 | 私有协议 |
|---|---|
| 平均延迟 | 0.8s |
| P99延迟 | 1.2s |
| 吞吐量 | 210 req/s |
| 错误率 | 0.5% |
但问题:需要CDN厂商支持私有协议,大部分公有云CDN不支持。我们测试了阿里云DCDN和腾讯云ECDN,都不支持自定义回源协议。所以这个方案只适合自建CDN。
方案C:HTTPS卸载回源(最终方案)
CDN到源站走HTTP,源站前面加一层Nginx做TLS卸载。CDN只负责边缘节点HTTPS,回源用HTTP。这是最实用的方案。
# 源站Nginx配置:TLS卸载层
upstream backend {
server 127.0.0.1:8080;
keepalive 64; # 连接池
}
server {
listen 443 ssl http2;
server_name origin.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/certs/ca-chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Session Cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets on;
# 回源到内部HTTP服务
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
压测数据:
| 指标 | HTTPS卸载 |
|---|---|
| 平均延迟 | 0.3s |
| P99延迟 | 0.6s |
| 吞吐量 | 580 req/s |
| 错误率 | 0% |
HTTPS优化细节:从握手到传输
1. TLS 1.3 + 0-RTT
TLS 1.3将握手从2-RTT降到1-RTT,0-RTT模式甚至可以复用之前会话直接发数据。但0-RTT有重放攻击风险,需要幂等性保证。
# 启用TLS 1.3和0-RTT
ssl_protocols TLSv1.2 TLSv1.3;
ssl_early_data on;
# 注意:0-RTT只对幂等请求安全
# 可以在location中限制
location /api/read-only {
ssl_early_data on;
}
location /api/write {
ssl_early_data off;
}
2. OCSP Stapling
默认浏览器会去CA查证书状态,增加延迟。OCSP Stapling让服务器自己缓存证书状态,发给客户端。
# 检查OCSP Stapling是否生效
openssl s_client -connect origin.example.com:443 -status 2>&1 | grep -A 10 "OCSP response"
# 输出示例:
# OCSP response:
# OCSP Response Data:
# OCSP Response Status: successful (0x0)
# Response Type: Basic OCSP Response
如果没生效,检查证书链是否完整:
# 查看证书链
openssl s_client -connect origin.example.com:443 -showcerts
# 确保返回了完整的chain,包括中间证书
3. Session Resumption
Session Cache和Session Ticket两种方式。Cache需要共享内存,适合单机;Ticket适合分布式。
# Session Cache配置
ssl_session_cache shared:SSL:50m; # 50MB共享内存,大约能存5万个session
ssl_session_timeout 4h; # 4小时过期
# Session Ticket配置
ssl_session_tickets on;
ssl_session_ticket_key /etc/nginx/ticket.key; # 多台机器共享同一个key
生成ticket key:
openssl rand 80 > /etc/nginx/ticket.key
# 多台机器同步这个文件
4. 连接池优化
CDN到源站的HTTP连接复用,减少TCP握手。
# 源站Nginx upstream配置
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
keepalive 128; # 每个worker保持128个空闲连接
keepalive_requests 1000; # 每个连接最多处理1000个请求
keepalive_timeout 60s; # 空闲连接超时
}
避坑指南
坑1:CDN回源HTTPS证书过期
我们曾遇到CDN节点缓存了过期证书,导致回源失败。解决方案:在CDN控制台设置证书自动更新,同时源站Nginx配置证书热加载。
# 证书更新后reload Nginx
nginx -s reload
# 或者用systemd timer自动检查
systemctl enable certbot.timer
坑2:OCSP Stapling导致源站DNS查询超时
OCSP Stapling需要Nginx去CA查询证书状态,如果resolver配置不当,会导致worker阻塞。我们遇到过resolver timeout设置太长,导致所有worker卡住。
# 正确配置:使用本地DNS缓存
resolver 127.0.0.1:5353 valid=300s; # 本地dnsmasq
resolver_timeout 2s; # 超时短一点
坑3:Session Ticket密钥泄露
Session Ticket密钥如果泄露,攻击者可以伪造会话。一定要定期轮换密钥。
# 每周轮换一次
openssl rand 80 > /etc/nginx/ticket.key
nginx -s reload
坑4:CDN回源HTTP导致安全问题
HTTPS卸载后,回源走HTTP,内网可能被ARP欺骗。解决方案:回源走专线或VPN,或者用mTLS。
# 源站Nginx配置mTLS
server {
listen 443 ssl http2;
ssl_client_certificate /etc/nginx/certs/ca.crt;
ssl_verify_client on;
# 只允许CDN的证书
if ($ssl_client_s_dn !~ "CN=cdn.example.com") {
return 403;
}
}
坑5:压测数据不准确
我们第一次压测用wrk,结果发现CDN节点有缓存,导致数据虚高。正确做法:压测时禁用CDN缓存,或者直接压源站。
# 压测源站,不走CDN
wrk -t4 -c100 -d30s --latency https://origin.example.com/api/test
# 或者用curl测试单次请求
curl -w "TCP handshake: %{time_connect}s, TLS handshake: %{time_appconnect}s, Total: %{time_total}s\n" -o /dev/null -s https://origin.example.com/api/test
最终效果数据
优化后,全站平均延迟从2.3s降到0.3s,P99从3.8s降到0.6s。双11当天峰值QPS 12万,源站CPU使用率仅35%。
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 平均延迟 | 2.3s | 0.3s | 86.9% |
| P99延迟 | 3.8s | 0.6s | 84.2% |
| 吞吐量 | 43 req/s | 580 req/s | 1248% |
| 错误率 | 12% | 0% | 100% |
| 源站CPU | 85% | 35% | 58.8% |
所有配置已上线运行6个月,零故障。