负载均衡

AuraBoot 在应用层是无状态的——所有会话状态存储在 Redis,所有持久化数据存储在 PostgreSQL。这意味着任意后端节点都可以处理任意请求,默认情况下不需要会话粘性。本文档介绍如何使用 Nginx 或 HAProxy 正确地将集群前置,如何配置 CDN 边缘(Cloudflare),以及长连接流量的常见陷阱。

适用范围

当你运行多个后端节点,或需要在不中断流量的情况下滚动升级节点时,就需要负载均衡器。即使是单节点部署,在前面加一层 Nginx 也是推荐做法——它负责 TLS 终止、请求头清理,并为后续水平扩展提供干净的路径。

本文档仅涵盖 HTTP/HTTPS 路径。负载均衡器位于 CDN 边缘与后端 JVM 进程之间,不负责数据库或 Redis 的复制——这些内容在集群模式灾难恢复中介绍。

拓扑结构

  浏览器 / 移动端
        |
   [ Cloudflare / CDN 边缘 ]     ← TLS 终止,缓存 /assets/*、/static/*
        |
   [ 负载均衡器 ]                ← Nginx 或 HAProxy;基于健康检查的节点池
      /      \
  节点 A    节点 B              ← Spring Boot 3.x,默认监听端口 6443
      \      /
   [ Redis 集群 ]               ← 会话、分布式锁、限流计数(共享)
   [ PostgreSQL ]               ← 所有持久化数据的单一来源

各层状态说明:

层级是否有状态存储内容
CDN 边缘仅边缘缓存/assets/*/static/* 缓存;/api/* 穿透
负载均衡器仅路由表;无需会话亲和
后端节点JVM 本地 Caffeine L1 元数据缓存(启动时重建)
Redis会话、分布式锁、限流计数器
PostgreSQL所有持久化应用数据

健康检查

AuraBoot 通过 Spring Actuator 暴露三个端点:

端点返回内容适用场景
/actuator/health聚合状态:UP/DOWN + 所有指标人工仪表盘、宽泛告警
/actuator/health/livenessJVM 存活、未死锁重启决策(杀死并替换进程)
/actuator/health/readinessDB 和 Redis 可达、缓存已预热LB 节点池成员资格——DOWN 时移出

LB 健康检查使用 /actuator/health/readiness 节点可能处于 live 但 not-ready 状态(例如仍在预热元数据缓存)。将流量发送到未就绪节点会引发冷路径 SQL 风暴。liveness 仅用于进程重启决策(Kubernetes livenessProbe,而非 LB)。

默认情况下,Spring Boot 3.x 将 Actuator 端点暴露在与应用相同的端口(6443)上,不单独占用管理端口。如需将健康检查流量与应用流量隔离,可显式配置 management.server.port,但平台默认配置并不这样做:

# application.yml(平台默认)
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
      base-path: /actuator
  endpoint:
    health:
      show-details: when_authorized

接入 LB 前先验证端点可达:

curl -s http://节点A:6443/actuator/health/readiness | python3 -m json.tool
# 期望输出:{"status":"UP"}

Nginx 前端

以下是两节点集群的完整 upstream + server 块。AuraBoot 的关键配置:proxy_read_timeout 必须足够长以支持 BPM 异步命令(默认 60s 会切断长时间运行的流程);gzip 不应应用于 SSE 流;X-Forwarded-* 请求头必须传递到应用层,否则限流和审计日志无法正常工作。

upstream auraboot_backend {
    least_conn;
    server 节点A:6443;
    server 节点B:6443;
    keepalive 32;
}

server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name your-domain.com;
    server_tokens off;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;

    # 安全响应头
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options    "nosniff" always;
    add_header X-Frame-Options           "DENY" always;

    # Gzip——排除 event-stream 和 websocket
    gzip on;
    gzip_types text/plain application/json application/javascript text/css;
    gzip_proxied any;
    gzip_vary on;

    # 大文件上传(平台上限 100 MB,Nginx 设略高)
    client_max_body_size 110m;
    client_body_timeout  60s;

    # 常规 API 流量的代理默认值
    proxy_connect_timeout  10s;
    proxy_send_timeout     60s;
    proxy_read_timeout     300s;  # BPM / 自动化步骤可能需要数分钟
    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;
    proxy_hide_header X-Powered-By;
    proxy_hide_header Server;

    # API 流量——禁止缓存
    location /api/ {
        proxy_pass http://auraboot_backend;
        add_header Cache-Control "no-cache, no-store, must-revalidate" always;
    }

    # Aura Bot 流式输出 + 自动化 SSE——必须关闭缓冲
    location /api/ai/aurabot/chat/stream {
        proxy_pass            http://auraboot_backend;
        proxy_buffering       off;
        proxy_cache           off;
        proxy_read_timeout    600s;  # AI 流式输出可能持续数分钟
        add_header            X-Accel-Buffering no;
        add_header            Cache-Control "no-cache" always;
    }

    # 静态资源——长缓存,由 Vite/BFF 在端口 3000 提供
    location ~* \.(js|css|woff2?|ttf|eot|svg|ico)$ {
        proxy_pass         http://127.0.0.1:3000;
        expires            1y;
        add_header         Cache-Control "public, immutable";
        add_header         Vary Accept-Encoding;
    }

    # SPA 兜底
    location / {
        proxy_pass http://127.0.0.1:3000;
        add_header Cache-Control "no-cache";
    }
}

HAProxy 前端

适用于偏好 HAProxy 的部署。option httpchk 指向应用端口上的 /actuator/health/readiness,确保 live 但 not-ready 的节点不会被加入节点池。

global
    log /dev/log local0
    maxconn 4096
    tune.ssl.default-dh-param 2048

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect  10s
    timeout client   300s   # SSE / BPM 流需要较长超时
    timeout server   300s
    timeout tunnel   3600s  # websocket / long-poll 隧道

frontend auraboot_http
    bind *:80
    redirect scheme https code 301

frontend auraboot_https
    bind *:443 ssl crt /etc/haproxy/certs/fullchain.pem
    default_backend auraboot_nodes

    # 转发前清理内部请求头
    http-request del-header X-Forwarded-For
    http-request set-header X-Forwarded-For %[src]
    http-request set-header X-Forwarded-Proto https

backend auraboot_nodes
    balance leastconn
    option  httpchk GET /actuator/health/readiness HTTP/1.1\r\nHost:\ localhost
    http-check expect status 200
    default-server inter 5s rise 2 fall 3 check

    server node-a node-a:6443 check
    server node-b node-b:6443 check

inter 5s rise 2 fall 3 表示:每 5 秒检查一次,连续 2 次成功标记为健康,连续 3 次失败标记为故障。这给出了约 15 秒的窗口期,适合常规滚动部署的节点摘除。

Cloudflare 或 CDN 边缘

按如下方式配置页面规则(或等效的转换规则):

缓存 /assets/*/static/* 下的所有内容:

URL 模式:  your-domain.com/assets/*
缓存级别:  Cache Everything
边缘 TTL:  1 年(资源文件带内容哈希,可激进缓存)

绕过 API、actuator 和认证路径的缓存:

URL 模式:  your-domain.com/api/*
缓存级别:  Bypass

URL 模式:  your-domain.com/actuator/*
缓存级别:  Bypass

SSE 和 WebSocket 直通。 当后端发送适当的响应头时,Cloudflare 默认会直通 SSE(text/event-stream)和 WebSocket 连接,无需特殊规则。Aura Bot 流式输出(/api/ai/aurabot/chat/stream)使用 SSE 而非 WebSocket,在所有 Cloudflare 套餐上均可正常工作。

源站证书。 在后端使用 Cloudflare 源站证书,确保 Cloudflare → 源站链路也经过加密。将 SSL/TLS 模式设置为完全(严格),绝不使用灵活模式——灵活模式在边缘终止 TLS 后将明文 HTTP 转发到源站。

对认证端点启用限流:

URL:  your-domain.com/api/auth/*
速率:  每 IP 每分钟 100 次请求
动作:  拦截(429)

AuraBoot 本身对登录和密码重置接口应用了 Redis 支持的限流。Cloudflare 规则是在流量到达源站之前的粗粒度边缘防护。

流式输出与 SSE 流量

Aura Bot 对话(/api/ai/aurabot/chat/stream)、自动化运行状态流、IM @AI 回复均使用 Server-Sent Events。SSE 能够通过代理正常工作需满足三个条件:

  1. proxy_buffering off(Nginx)——如果 Nginx 对响应进行缓冲,客户端将看不到任何内容,直到缓冲区填满或流结束,完全破坏实时体验。
  2. 较长的 proxy_read_timeout——Aura Bot 的复杂推理流可能持续 30–120 秒。默认 60 秒的读超时会在回复中途切断连接。
  3. X-Accel-Buffering: no 响应头——即使 proxy_buffering 对其他 location 全局开启,该响应头也告知 Nginx 对当前连接不做缓冲。

平台对流式输出是无状态的:流锚定在 Redis key 上,而非 JVM 线程。如果连接断开后客户端重新连接(可能连到不同节点),流会从 Redis 支持的对话状态处恢复。流式输出不需要会话亲和。

会话粘性

默认情况下:不使用会话粘性。

平台是无状态的——JWT token 通过共享 Redis 会话存储进行验证,所有应用状态存储在 PostgreSQL 或 Redis 中。将同一用户路由到同一后端节点不带来任何好处,反而会降低负载均衡的有效性。

唯一需要考虑会话粘性的场景是某个第三方集成将进行中的状态存储在 JVM 内存而非共享存储中——这是集成代码的缺陷,而非全局启用粘性的理由。

如果因迁移或调试需要临时启用粘性,使用基于 Cookie 的亲和性(HAProxy 中的 SERVERID Cookie,Nginx 中的 ip_hashsticky 模块),并在完成后及时移除。

常见陷阱

配置模式为什么会出问题
/api/ai/aurabot/chat/stream 上启用 proxy_buffering onSSE 帧被 Nginx 缓冲持有,客户端什么都收不到,直到缓冲区刷新——看起来 AI 卡住了
API 路由使用 proxy_read_timeout 60s(Nginx 默认值)BPM 异步命令步骤、自动化流程和 AI 流式输出经常超过 60 秒,nginx 在中途返回 504
缺少 X-Forwarded-For 请求头AuraBoot 的 Redis 限流以 IP 为 key;没有该请求头时所有用户都被识别为 LB 的 IP,导致整个集群同时触发限流
Cloudflare SSL 模式设置为"灵活"TLS 在边缘终止,源站收到明文 HTTP;$scheme 始终为 http,破坏 X-Forwarded-Proto 检查和 HSTS 执行
未设置 proxy_hide_headerNginx 将 Server: Apache-Coyote/1.1 或 Spring 的 X-Powered-By 转发给客户端,暴露内部技术栈版本
LB 健康检查使用 /actuator/health 而非 /actuator/health/readiness缓存预热期间(约 1.5 秒),聚合端点返回 UP,但节点尚未就绪,随后产生预热 SQL 风暴
全局启用会话粘性破坏 LB 公平性;热点用户将节点钉住而其他节点空闲;由于平台无状态,没有任何功能收益

下一步

  • 可观测性 — 按节点统计请求速率、错误率和连接池指标
  • 集群模式 — 水平扩展、Redis 集群、多节点 PostgreSQL
  • 性能调优 — JVM、HikariCP 和查询层调优
  • 灾难恢复 — RTO/RPO 目标与故障转移手册

企业版说明 — 托管式多区域负载均衡(含 anycast 路由)、基于健康检查的跨可用区自动故障转移,以及支持按租户配额的边缘限流,均为企业版功能。OSS 版本完全支持本文档所述的所有配置;差异在于托管控制平面和 anycast 网络,而非应用层行为。