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

4.7 KiB
Raw Permalink Blame History

id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week, order
id title type domain stack tags level prerequisites est_hours related week order
devops.nginx.https-stream-limit HTTPS、SSE / WebSocket、限流和灰度 lesson devops
nginx
HTTPS
TLS
SSE
WebSocket
limit_req
灰度发布
intermediate
devops.nginx.proxy-static
3
devops.nginx.scenario.ai-gateway-sse-canary
devops.nginx.interview
7
14
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:需要协议升级头:

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)。

代码示例

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 的测试里验证了:关闭缓冲时第一条 SSE 事件在约 0.3 秒内到达,而整个响应约 0.9 秒结束;打开缓冲时第一条事件要等整个响应结束才到达。

易错点

  • SSE 没关缓冲:流式变整段。
  • proxy_read_timeout 太短:长回答被 504 中断。
  • WebSocket 没加 Upgrade / Connection 头:握手失败,前端报 400 或连接立即关闭。
  • 限流按 $remote_addr 但前面还有一层负载均衡:所有请求的地址都是负载均衡器,一起被限流。要用 real_ip 模块从可信代理的头里取真实 IP。
  • 证书过期:自动续期任务失败没人发现。加监控告警。
  • 灰度只切了后端没切前端资源(或反过来):前后端版本不匹配。灰度规则要一致,接口要向后兼容。

面试常问

见 Nginx 面试题:SSE 代理要注意什么、WebSocket 代理配置、限流原理、灰度发布怎么做、502 和 504 的区别。