Files
zhuangang/topics/devops/nginx/02-https-stream-limit.md

114 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: devops.nginx.https-stream-limit
title: HTTPS、SSE / WebSocket、限流和灰度
type: lesson
domain: devops
stack: [nginx]
tags: [HTTPS, TLS, SSE, WebSocket, limit_req, 灰度发布]
level: intermediate
prerequisites: [devops.nginx.proxy-static]
est_hours: 3
related: [devops.nginx.scenario.ai-gateway-sse-canary, devops.nginx.interview]
week: [7, 14]
order: 2
---
# HTTPS、SSE / WebSocket、限流和灰度
## 解释说明
**HTTPS**
- 证书可以用 Let's Encrypt(免费,certbot 或 acme.sh 自动续期)或云厂商证书。
- 配置 `ssl_certificate`、`ssl_certificate_key`,只开启 TLS 1.2 / 1.3;80 端口 301 跳转到 443。
- TLS 在 Nginx 终止,Nginx 到内网后端用 HTTP(`X-Forwarded-Proto: https` 告诉后端原始协议)。
**SSE(大模型流式输出)**
- SSE 是普通 HTTP 响应(`Content-Type: text/event-stream`),服务端不断写入 `data: ...\n\n`。
- Nginx 默认开启 `proxy_buffering`,会把后端响应先攒进缓冲区,导致用户看到的“打字机效果”变成一大段一起出现。**关闭缓冲**:`proxy_buffering off;`,或者后端返回响应头 `X-Accel-Buffering: no`。
- 生成时间长,`proxy_read_timeout` 要调大(它是两次读之间的间隔,不是总时长);也可以让后端定期发心跳注释 `: ping`。
- 如果开启了 gzip,确认流式响应不被压缩缓冲(`text/event-stream` 不在 `gzip_types` 中即可)。
**WebSocket**:需要协议升级头:
```nginx
map $http_upgrade $connection_upgrade { default upgrade; "" close; }
location /ws/ {
proxy_pass http://ws_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
}
```
**限流**:`limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;` 定义按 IP 的漏桶;`limit_req zone=api burst=20 nodelay;` 允许突发 20 个且不排队延迟;超出返回 `limit_req_status`(默认 503,建议改成 429)。`limit_conn` 限制并发连接数。
**灰度发布**:
- 按比例:`split_clients "${remote_addr}" $variant { 10% canary; * stable; }`;
- 按用户:`map $cookie_canary $backend { default stable; "1" canary; }`,内部员工或白名单用户先体验;
- 按权重:`upstream` 里新旧实例设置不同 `weight`。
## 案例
AI 问答页面上线后用户反馈“回答要等很久才一下子全部出来”:浏览器直连后端是逐字输出,经过 Nginx 就变成整段。原因是 `proxy_buffering` 默认开启。关闭后恢复逐字输出;同时把 `proxy_read_timeout` 从 60 秒调到 300 秒,避免长回答被中途断开(504)。
## 代码示例
```nginx
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=5r/s;
map $cookie_canary $api_backend {
default api_stable;
"1" api_canary;
}
server {
listen 443 ssl;
http2 on; # nginx 1.25.1+ 的写法;旧版本写在 listen 后面
server_name example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location /api/ {
limit_req zone=api_limit burst=5 nodelay;
limit_req_status 429;
proxy_pass http://$api_backend;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ai/stream {
proxy_pass http://api_stable;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off; # 流式输出必须关闭缓冲
proxy_cache off;
proxy_read_timeout 300s;
}
}
```
[examples/gateway](examples/gateway/) 的测试里验证了:关闭缓冲时第一条 SSE 事件在约 0.3 秒内到达,而整个响应约 0.9 秒结束;打开缓冲时第一条事件要等整个响应结束才到达。
## 易错点
- **SSE 没关缓冲**:流式变整段。
- **`proxy_read_timeout` 太短**:长回答被 504 中断。
- **WebSocket 没加 Upgrade / Connection 头**:握手失败,前端报 400 或连接立即关闭。
- **限流按 `$remote_addr` 但前面还有一层负载均衡**:所有请求的地址都是负载均衡器,一起被限流。要用 `real_ip` 模块从可信代理的头里取真实 IP。
- **证书过期**:自动续期任务失败没人发现。加监控告警。
- **灰度只切了后端没切前端资源**(或反过来):前后端版本不匹配。灰度规则要一致,接口要向后兼容。
## 面试常问
见 [Nginx 面试题](interview.md):SSE 代理要注意什么、WebSocket 代理配置、限流原理、灰度发布怎么做、502 和 504 的区别。