引子:一次突发流量事故
大促开始前 5 分钟,运维群里突然炸了:
"网关 502 了!" "连不上后端!" "监控看 CPU 才 40% 啊,怎么就打满了?"
你的第一反应是加机器。于是你给网关集群又起了 5 个实例,然后......打开 Nginx 配置文件,把新实例的 IP 一行行加进 upstream,执行 nginx -s reload。
流量恢复了,但你的内心是崩溃的:这次是 5 台,下次 10 台呢?每次都手动改 Nginx?而且这 5 分钟里,用户已经流失了。
这篇文章要回答的,正是这三个问题:
- Q1:为什么说网关必须"无状态"才能扩?有状态会怎样?
- Q2:扩容后,前面的 Nginx 怎么"自动"知道新实例的 IP?现代企业到底怎么做的?
- Q3:就算流量能自动过去,新实例 30 秒才启动完,这 30 秒的窗口期怎么办?
三个问题是一条完整的链路:扩得动 → 路由得到 → 起得来。任何一个环节断了,突发流量照样会把你打趴。
Q1:无状态是扩容的前提
先说结论:如果你的网关是有状态的,加多少台机器都没用。
什么叫"有状态"?典型场景:
- 会话(Session)存在本地内存里,用户 A 第一次请求落在实例 1,第二次请求被路由到实例 2,登录态直接丢了;
- 限流计数器是 JVM 本地变量,每台机器各限各的,扩容后限流阈值反而被"放大"了 N 倍;
- 路由规则硬编码在
application.yml里,新实例起来是旧配置。
所以无状态化有三个硬性要求:
| 要素 | 做法 | 为什么 |
|---|---|---|
| 鉴权/会话下沉 | JWT 非对称密钥(RSA/ECDSA)本地验签免查询;Session/Token 存 Redis | 任意实例都能验证任意请求 |
| 限流配额集中化 | 分布式限流状态放 Redis(Sentinel Redis 数据源 / Lua 脚本) | 全局阈值不随实例数漂移 |
| 路由配置动态化 | 路由存 Nacos/Consul/K8s ConfigMap,变更实时推送 | 新实例拉起即最新配置 |
无状态化之后,网关就变成了"任意实例可以接任意请求"的等价无差别节点------这是水平扩容的数学前提。
别只看 CPU:WebFlux 的伸缩指标陷阱
Spring Cloud Gateway 底层是 Reactor Netty(WebFlux 事件循环模型),少量 CPU 线程就能扛住数万并发连接。突发流量下最典型的监控画面是:
连接数暴涨、RT 飙升,但 CPU 只有 30%-40%。
如果按传统思路"CPU > 80% 才扩容",等 CPU 真涨上去的时候,网关早就因为堆外内存(DirectMemory)耗尽 或队列积压挂了。
所以网关的伸缩指标必须是业务指标 + 资源指标的组合:
| 指标维度 | 监控项 | 扩容阈值(参考) |
|---|---|---|
| 吞吐量 | http_server_requests_seconds_count(QPS) |
单 Pod QPS > 3000 |
| 延迟 | http_server_requests_seconds_sum/count(P95 RT) |
P95 > 200ms |
| 并发连接 | reactor_netty_http_server_connections |
单实例活跃连接 > 5000 |
| 堆外内存 | jvm_buffer_memory_used_bytes{id="direct"} |
使用率 > 75% |
⚠️ 注意:Reactor Netty 不同版本的 Micrometer 指标名有差异(
reactor_netty_http_server_connections是最常用的 HTTP Server 连接 gauge,reactor_netty_connection_provider_*系列是 ConnectionProvider 连接池指标,语义不同)。落地前先curl <pod>:9090/actuator/prometheus | grep reactor_netty核对实际指标名,不要照抄网上配置。
Q2:扩容后,流量怎么自动到新实例?
这是本文的核心,也是大多数人的疑惑点:Nginx 在前面,我总不可能每次扩容都去改它吧?
先立一个心智模型
现代架构的答案非常反直觉但极其简单:
Nginx 的配置是一次性的。它从不直接写"网关实例的 IP 列表",只指向一个"稳定的逻辑入口"------K8s Service 的 DNS/VIP、云 SLB 的 VIP、或者一个域名。网关扩到几台、IP 是多少,由这个逻辑入口背后的机制自动消化。
换句话说,你要解决的不是"Nginx 怎么发现新实例",而是把"实例列表"这个变量,从 Nginx 的配置里彻底移除。移除的方式有三种,对应三种部署形态。
arduino
┌──────────────────────────────────────────┐
│ Nginx / Tengine(7层入口) │
│ upstream 永远指向"稳定逻辑入口" │
└───────────────────┬──────────────────────┘
│
┌─────────────────────────────┼─────────────────────────────┐
▼ ▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
│ K8s Service VIP │ │ 云 SLB / ALB VIP │ │ APISIX + Nacos │
│ kube-proxy / Cilium │ │ 后端服务器组 │ │ 服务发现动态路由 │
│ EndpointSlice 自动刷新 │ │ 弹性伸缩组自动增删 │ │ 注册中心感知节点 │
└────────────┬────────────┘ └────────────┬────────────┘ └────────────┬────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
│ SCG Pod × N │ │ SCG 实例 × N │ │ SCG 实例 × N │
│ HPA/KEDA 自动扩缩 │ │ 云上弹性伸缩 │ │ Nacos 注册/下线感知 │
└─────────────────────────┘ └─────────────────────────┘ └─────────────────────────┘
机制一:K8s 云原生(当前企业主流)
在 K8s 里,答案几乎是无感的:
scss
① 采集 ② 决策 ③ 扩容 ④ 感知 ⑤ 路由
Prometheus ───▶ KEDA/HPA ───▶ Deployment ───▶ K8s Service ───▶ Ingress-Nginx
(QPS / P95) (阈值判断) (副本 N→N+M) (EndpointSlice (watch 动态更新
自动纳入新 Pod) upstream, 零 reload)
│
▼
新 Pod 开始接收流量
三个组件各司其职:
- KEDA/原生 HPA:按 QPS、P95 RT 决定扩不扩、扩多少;
- K8s Service:label selector 匹配网关 Pod,Pod 增删时 endpoint 列表由 kube-proxy(IPVS)/Cilium(eBPF)自动刷新;
- Ingress Controller (ingress-nginx、Higress、APISIX Ingress 等):本质是 OpenResty 进程,通过 watch K8s API(EndpointSlice)在内存中动态更新 upstream ,请求进来时用 Lua balancer 实时选节点------Pod 扩缩容完全不触发 nginx reload。
KEDA 配置实战
KEDA(Kubernetes Event-driven Autoscaling)相比原生 HPA 的优势:直接以 PromQL 查 Prometheus,多触发器、扩缩容策略更灵活:
yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: scg-gateway-scaler
namespace: microservices
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: spring-cloud-gateway
minReplicaCount: 4
maxReplicaCount: 50
cooldownPeriod: 300 # 缩容冷却 5 分钟,防震荡
pollingInterval: 15 # 15 秒轮询一次指标
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleUp:
stabilizationWindowSeconds: 0 # 扩容零等待
policies:
- type: Percent
value: 100 # 单次最多翻倍
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300 # 缩容观察 5 分钟
policies:
- type: Percent
value: 10
periodSeconds: 60
triggers:
# 触发器 1:网关集群平均 QPS > 3000
- type: prometheus
metadata:
serverAddress: http://prometheus-k8s.monitoring.svc.cluster.local:9090
metricName: gateway_qps_avg
query: |
sum(rate(http_server_requests_seconds_count{
namespace="microservices", app="spring-cloud-gateway",
uri!="/actuator/health"}[1m]))
/ count(kube_pod_info{namespace="microservices", pod=~"spring-cloud-gateway-.*"})
threshold: '3000'
# 触发器 2:P95 响应时间 > 200ms
- type: prometheus
metadata:
serverAddress: http://prometheus-k8s.monitoring.svc.cluster.local:9090
metricName: gateway_p95_rt_seconds
query: |
histogram_quantile(0.95,
sum(rate(http_server_requests_seconds_bucket{
namespace="microservices", app="spring-cloud-gateway"}[1m])) by (le))
threshold: '0.2'
几个细节值得展开:
- 扩容策略 :
stabilizationWindowSeconds: 0+ 单次翻倍(Percent 100),让扩容秒级响应;缩容侧留 300 秒观察窗,避免 QPS 波动导致副本数上下跳(震荡); - QPS 触发器用的是"总 QPS / 副本数" (平均值语义),这样扩容后新 Pod 加入会自然拉低均值,不会立刻又触发;
cooldownPeriod是 KEDA 自己的冷却期,与 HPA behavior 的 stabilization 叠加使用。
Ingress 配置:零 Reload 的关键
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: scg-ingress
namespace: microservices
annotations:
kubernetes.io/ingress.class: "nginx"
# 走 EndpointSlice(Pod IP)而不是 Service ClusterIP,让 Lua balancer 感知每个 Pod
nginx.ingress.kubernetes.io/service-upstream: "false"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "2"
nginx.ingress.kubernetes.io/proxy-read-timeout: "10"
spec:
rules:
- host: api.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: spring-cloud-gateway-service
port:
number: 8080
⚠️ 两个容易踩的坑:
- 不要配
upstream-hash-by: "$remote_addr"。IP 哈希会把同一来源的请求粘到固定 Pod,扩容出来的新 Pod 分不到流量(哈希偏斜),反而削弱扩容效果。无状态网关不需要粘性。- "零 Reload"有边界:Pod 增删(EndpointSlice 变化)确实不触发 nginx reload;但 Ingress 配置/注解变化(路由规则、TLS、超时)仍会触发 reload。不要把"零 Reload"推广成"Ingress 永远不 reload"。
如果你不想引入 KEDA,用原生 HPA + Prometheus-Adapter(把 Prometheus 指标导出为 Custom Metrics API)也可以,两者二选一,不要同时部署,否则两个控制器会抢着改副本数:
yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: scg-hpa
namespace: microservices
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: spring-cloud-gateway
minReplicas: 4
maxReplicas: 50
metrics:
- type: Pods # per-Pod 平均值
pods:
metric:
name: gateway_qps_per_pod
target:
type: AverageValue
averageValue: "3000"
机制二:云厂商(AHPA 预测伸缩 + ECI 秒级容器 + ALB)
K8s 原生 HPA 有个先天缺陷:它永远是"事后反应" ------流量先上来,指标再升高,然后才扩容,整个过程 30 秒到 1 分钟。对"大促 0 点准时爆发"这种可预测的突发,反应式扩容不够用。
云厂商的答案是 AHPA(Advanced HPA,阿里云) :
- 预测算法 :基于 Holt-Winters / LSTM 分析历史流量周期,在峰值到来前 5-10 分钟提前预扩;
- ECI 弹性容器:Serverless 容器秒级拉起,不依赖底层 ECS 机器是否够;
- ALB 自动挂载:ALB Ingress Controller 监听 Pod 变化,把新 Pod IP 自动注入后端服务器组。
yaml
apiVersion: autoscaling.alibabacloud.com/v1beta1 # ⚠️ v1beta1,v1alpha1 已过时
kind: AdvancedHorizontalPodAutoscaler
metadata:
name: scg-ahpa
namespace: microservices
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: spring-cloud-gateway
minReplicas: 5
maxReplicas: 100
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
prediction:
quantiles:
- 0.95
historical:
duration: "7d" # 学习过去 7 天流量周期
granularity: "1m"
forecast:
duration: "2h" # 预测未来 2 小时走势
instanceBounds:
- startTime: "2026-08-08T08:00:00Z"
endTime: "2026-08-08T12:00:00Z"
minReplicas: 30 # 大促窗口保底 30 副本
配套 ECI ImageCache 把 Pod 启动从 30s 压到 5s 内------镜像缓存到节点侧,省去拉镜像时间:
yaml
apiVersion: eci.alibabacloud.com/v1
kind: ImageCache
metadata:
name: scg-image-cache
spec:
images:
- registry.cn-hangzhou.aliyuncs.com/your-org/spring-cloud-gateway:v2.5.0
imageCacheSize: 20
retentionDays: 7
AHPA 使用要点 :与原生 HPA 二选一(会互相覆盖副本数);
instanceBounds的起止时间是 UTC,注意时区;AHPA 需要 ACK 安装ack-advanced-autoscaler组件,纯自建 K8s 上没有这个能力,只能回退到 KEDA/原生 HPA。
机制三:传统 ECS 自建(注册中心感知网关 / 动态渲染)
没有 K8s、没有云 SLB 怎么办?裸 Nginx 是"睁眼瞎"------它不认识 Nacos。两个出路:
方案 A(推荐):入口换成 APISIX,直接对接 Nacos
APISIX 内置 Nacos 服务发现,SCG 扩容后向 Nacos 注册,APISIX 实时感知并同步更新 upstream 列表,全程零 reload:
yaml
# APISIX config.yaml
discovery:
nacos:
host:
- "http://192.168.10.11:8848"
- "http://192.168.10.12:8848"
prefix: "/nacos/v1/ns/instance"
weight: 100
arduino
# 创建路由,upstream 直接绑定 Nacos 服务名
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/*",
"name": "scg-upstream-route",
"upstream": {
"type": "roundrobin",
"discovery_type": "nacos",
"service_name": "spring-cloud-gateway-service",
"discovery_args": {
"group_name": "DEFAULT_GROUP",
"namespace_id": "prod"
}
}
}'
方案 B(传统):Nginx + Consul-Template 动态渲染
如果必须保留裸 Nginx,那就让模板工具替你改配置------监听注册中心变化,自动渲染 nginx.conf 并 reload:
ini
# nginx.conf.tpl
upstream spring_cloud_gateway_cluster {
zone scg_dynamic_zone 64m;
{{ range service "prod.spring-cloud-gateway" }}
server {{ .Address }}:{{ .Port }} max_fails=3 fail_timeout=10s weight=10;
{{ else }}
server 127.0.0.1:8080 down; # 兜底:无节点时全部下线
{{ end }}
keepalive 256;
}
ini
consul-template \
-consul-addr=192.168.10.20:8500 \
-template="/etc/nginx/templates/nginx.conf.tpl:/etc/nginx/conf.d/gateway.conf:nginx -s reload" \
-log-level=info \
-retry=3s
方案 B 是"能做但不够现代":每次扩容都会 reload nginx(长连接被断开、瞬时错误率上升)。它适合过渡期,长期建议往 APISIX/K8s 演进。
三种机制怎么选?
| 维度 | 机制一:K8s | 机制二:云厂商 | 机制三:自建 ECS |
|---|---|---|---|
| 扩容触发 | 反应式(QPS/RT 指标) | 预测 + 反应式 | 反应式 |
| 新实例感知 | EndpointSlice 自动 | SLB/ALB 自动挂载 | APISIX Nacos 感知 / reload |
| 冷启动 | 秒级(取决于镜像) | 秒级(ECI + ImageCache) | 较慢(ECS 起机) |
| 成本 | 自建 K8s 运维成本 | 云产品按量付费 | 裸机最低 |
| 适用场景 | 标准 K8s 集群 | 云上大促、有历史流量规律 | 存量 ECS 环境过渡 |
选型决策可以简化为一句:
有 K8s 用机制一,上云且有可预测大促用机制二,存量 ECS 且不想动架构先用机制三过渡,但演进方向一定是前两者。
Q3:流量能过去了,冷启动的 30 秒怎么办?
扩容决策再快,Pod 起不来也是白搭。网关的启动时间由"拉镜像 + JVM 启动 + Spring 初始化"组成,传统 JVM 应用 30-60 秒是常态。四个手段叠加,可以压到毫秒级:
| 优化手段 | 原理 | 效果 |
|---|---|---|
| Spring Boot 懒加载 | spring.main.lazy-initialization=true,Bean 按需初始化 |
启动时间缩短 30%-50% |
| JRE 镜像瘦身 | jlink/jdeps 裁剪 JRE 模块 |
镜像 350MB → 70MB,拉取加速 80% |
| CRaC 快照恢复 | 预热后打 Checkpoint,恢复时秒级还原 JVM 状态 | 15s → 500ms 内 |
| GraalVM Native Image | AOT 编译为原生可执行文件 | 冷启动 <50ms,内存降 70% |
重点说 CRaC(低成本、高收益)
CRaC(Coordinated Restore at Checkpoint)的思路:提前把 JVM 跑起来、完成预热,然后冻结整个进程状态成快照;生产拉起的不是"新启动的应用",而是"恢复"一份现成的状态。
ruby
# 1. 构建期:启动并预热后生成 Checkpoint
java -XX:CRaCCheckpointTo=/opt/crac-files -jar gateway-service.jar
# 2. 生产:从快照恢复(毫秒级)
java -XX:CRaCRestoreFrom=/opt/crac-files
两个必须知道的坑:
- JDK 版本 :
org.crac是 OpenJDK 21 起 才内置;标准 OpenJDK 17 没有 CRaC 能力,必须用 Azul Zulu Prime(CRaC 分支)镜像azul/zulu-openjdk-crac:17。生产镜像选型时不要踩"17-jre-slim + org.crac"这个坑。 - 资源重建 :快照会冻结当时的文件句柄、网络连接、线程状态。恢复后 Redis/Nacos 连接不会自动重建,需要实现
org.crac.Resource接口,在beforeCheckpoint/afterRestore回调里主动重建连接;Checkpoint 前还要配合优雅下线停止接收新流量。
至于 GraalVM Native Image,提醒一句:Spring Cloud Gateway 走 AOT 需要 Spring Boot 3.3+/Spring Cloud 2023.0.x,且对反射/动态代理(Security、自定义 GlobalFilter、Dubbo 泛化调用、Nacos 动态类加载)限制严格 。网关一旦有"从配置中心下发动态 Filter"这类需求,Native 基本不可行。CRaC 是比 Native 更低风险的秒起方案。
配合无损优雅上线:探针是最后一道闸
秒起之后还要保证"新实例就绪了才接流量"。Spring Boot 默认没有 /actuator/health/liveness 端点,必须先开启探针:
yaml
# application.yml
management:
endpoint:
health:
probes:
enabled: true # 暴露 /actuator/health/liveness 与 /readiness
yaml
# Deployment 容器配置
startupProbe: # 启动完成前不算 Ready
httpGet: { path: /actuator/health/readiness, port: 8080 }
periodSeconds: 5
failureThreshold: 60 # 最多等 5 分钟
readinessProbe: # 就绪才接流量(ALB/K8s 都认这个)
httpGet: { path: /actuator/health/readiness, port: 8080 }
periodSeconds: 5
livenessProbe: # 探活失败重启 Pod
httpGet: { path: /actuator/health/liveness, port: 8080 }
periodSeconds: 15
别忘了边缘削峰:限流是扩容的缓冲区
扩容需要时间,而限流可以在流量到达网关之前 就把它削平。记住一个原则:全局限流放最前面的 7 层入口(Tengine/APISIX),网关内的 Sentinel 只是兜底。
而且限流要分层------"全局闸门"和"单 IP 防护"是两回事:
ini
http {
# ① 全局 QPS 闸门:key 为空字符串,所有请求共享一个计数桶(真正的削峰)
limit_req_zone "" zone=global_qps_limit:20m rate=100000r/s;
# ② per-IP 限流:防单点攻击/爬虫
limit_req_zone $binary_remote_addr zone=per_ip_limit:20m rate=1000r/s;
# ③ 全局并发连接兜底
limit_conn_zone "" zone=global_conn_limit:20m;
server {
listen 80;
location / {
limit_req zone=global_qps_limit burst=20000 nodelay; # 全局削峰
limit_req zone=per_ip_limit burst=100 nodelay; # 单 IP 防护
limit_conn global_conn_limit 50000; # 连接兜底
proxy_pass http://spring_cloud_gateway_cluster;
}
}
}
⚠️ 全局限流的阈值要留足余量:应大于"当前网关集群能扛住的 QPS"(建议按集群容量 × 1.5 起步,压测校准)。否则限流会先于扩容生效,把本可服务的流量拒掉------你看到的现象就是"503 一片,但副本数纹丝不动"。
验证闭环:扩容到底生效了没有?
配置是配置,实测才是真相。用 k6 模拟"爬坡 → 尖峰 → 回落"三段式流量,验证从触发扩容到流量分发到新 Pod 的完整闭环:
javascript
import http from "k6/http";
import { sleep } from "k6";
export const options = {
scenarios: {
spike: {
executor: "ramping-vus",
startVUs: 0,
stages: [
{ duration: "10s", target: 1000 }, // 爬坡
{ duration: "30s", target: 5000 }, // 尖峰:触发扩容
{ duration: "10s", target: 0 }, // 回落:触发缩容观察
],
gracefulStop: "30s",
},
},
thresholds: {
http_req_failed: ["rate<0.01"], // 错误率 < 1%
http_req_duration: ["p(95)<200"], // P95 < 200ms
},
};
export default function () {
http.get("https://api.yourdomain.com/actuator/health");
sleep(0.1);
}
压测期间按下面的清单逐项观察,缺一不可:
| # | 观察项 | 判定标准 |
|---|--------------------------------------------------------------|--------------------------------|------------------------------|
| 1 | HPA/KEDA 是否真的扩了(kubectl get hpa -w / kubectl get events) | 出现 ScaleUp 事件,副本数 N→N+M |
| 2 | 新 Pod 是否纳入 Service(kubectl get endpoints -w) | Endpoints 列表出现新 Pod IP |
| 3 | Ingress 是否感知新 Pod(`kubectl logs ingress-nginx | grep endpoint`) | 有新 endpoint 同步日志,无 reload 记录 |
| 4 | 流量是否分散到新 Pod(Prometheus 按 pod 聚合 QPS) | 各 Pod QPS 曲线均衡,新 Pod 从 0 爬升到均值 |
| 5 | 扩容窗口是否丢包/超时 | 错误率 < 1%,无 504 潮 |
五个最常见的翻车点,压测时逐个排查:
- 扩容了但流量没过去 → label selector 与 Pod label 不匹配;或
service-upstream: "true"导致走 ClusterIP 而非 endpoint; - 新 Pod 反复 Ready/NotReady → readiness 探针路径配错(
/actuator/health/liveness没开启 probes),JVM 预热期被摘除; - 副本数上下跳(震荡) → 缩容侧没留稳定窗口,
stabilizationWindowSeconds建议 ≥ 300; - 503 一片但副本数没动 → 入口全局限流阈值低于集群容量,限流先于扩容生效;
- 指标在 Prometheus 有、HPA 拿不到 → namespace/label 选择器写错,用
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1验证指标是否已导出。
总结与演进路线
把整条链路串起来,突发流量下的完整自愈体系长这样:
markdown
突发流量
│
▼
┌──────────────────────────────────────────────────────┐
│ 入口 7 层:Tengine / APISIX │
│ · 全局 QPS 削峰限流(空 key 全局闸门) │
│ · per-IP 攻击防护 · 全局连接兜底 │
└──────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ 稳定逻辑入口:SLB VIP / K8s Service │
│ (upstream 配置一次,永远不变) │
└──────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ SCG Pod × N(无状态) │
│ · Redis 鉴权/限流 · Nacos 动态路由 │
│ · KEDA/AHPA 自动扩缩 · CRaC/ECI 秒级启动 │
└──────────────────────────┬───────────────────────────┘
▼
业务服务
一句话总结:
突发流量下的网关自愈 = 无状态化(扩得动) + 稳定逻辑入口(路由得到) + 秒级启动与边缘削峰(起得来) + 压测验证(证明它真的生效)。
最后给一条务实的演进路线,按你的现状对号入座:
arduino
阶段 1:裸 Nginx + 手动加 IP(现状,不推荐)
↓ 引入 consul-template / APISIX 动态发现
阶段 2:Nginx 自动渲染或 APISIX + Nacos(存量 ECS 过渡)
↓ 容器化上 K8s
阶段 3:K8s + KEDA/原生 HPA + Ingress 零 Reload(标准形态)
↓ 业务有可预测大促
阶段 4:云厂商 AHPA 预测伸缩 + ECI + CRaC 秒起(极致形态)
记住那个心智模型:Nginx 的配置是一次性的,扩容永远只是"加机器/加 Pod"这一个动作。 如果你发现自己还在扩容时改 Nginx 配置,说明你的架构还停留在阶段 1------这篇文章就是你的路线图。