Skip to content

Nginx/Gateway 原理深度解析

面试官:Nginx 为什么能支撑高并发?

:Nginx 采用事件驱动的非阻塞 IO 模型,基于 epoll(Linux)实现 IO 多路复用。一个 Worker 进程可以同时处理数万个连接,不需要为每个连接创建线程或进程,避免了线程切换开销。

面试官:那 Nginx 的 Worker 进程数应该设置成多少?为什么?

这个问题看似简单,但能说清「Worker 数量与 CPU 核心数的关系」以及「为什么不能设置太多」的候选人,才能真正展示出对底层原理的理解。


Q1:Nginx 的 Master-Worker 架构是什么?必考

Section titled “Q1:Nginx 的 Master-Worker 架构是什么?”

进程架构

┌─────────────────────────────────────────────────┐
│ Master 进程(1个) │
│ ┌───────────────────────────────────────────┐ │
│ │ • 读取配置文件并校验 │ │
│ │ • 管理 Worker 进程(启动/停止/重启) │ │
│ │ • 处理信号(nginx -s reload/stop/quit) │ │
│ │ • 不处理实际请求 │ │
│ │ • 以 root 用户运行(绑定 80 端口) │ │
│ └───────────────────────────────────────────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Worker 1 │ │ Worker 2 │ │ Worker N │ │
│ │ │ │ │ │ │ │
│ │ • 处理请求 │ │ • 处理请求 │ │ • 处理请求 │ │
│ │ • 单线程 │ │ • 单线程 │ │ • 单线程 │ │
│ │ • epoll │ │ • epoll │ │ • epoll │ │
│ └───────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────┘

Worker 进程数配置

nginx.conf
worker_processes auto; # 自动设置为 CPU 核心数(推荐)
worker_cpu_affinity auto; # 自动绑定 CPU 亲和性
worker_connections 65535; # 每个 Worker 最大连接数

为什么 Worker 数量 = CPU 核心数

Worker 数量CPU 利用率上下文切换性能表现
< 核心数部分核心闲置未充分利用 CPU
= 核心数充分利用最少最优
> 核心数充分利用大量增加线程切换开销抵消并发优势

本质一句话:每个 Worker 是单线程事件循环,一个 Worker 充分利用一个 CPU 核心,超过核心数只会增加无意义的上下文切换。


Q2:Nginx 的一个 Worker 进程如何同时处理 10K 个连接?高频

Section titled “Q2:Nginx 的一个 Worker 进程如何同时处理 10K 个连接?”

核心机制:epoll + 非阻塞 IO + 事件循环

传统阻塞模型(每连接一线程):
连接1 → 线程1 → 阻塞等待数据
连接2 → 线程2 → 阻塞等待数据
...
连接10000 → 线程10000 → 内存爆炸 + 线程切换开销巨大
Nginx 事件驱动模型:
连接1 ─┐
连接2 ─┼─→ epoll 监控所有连接 ─→ 单线程事件循环处理就绪事件
... ─┤
连接N ─┘

Worker 事件循环伪代码

// Worker 进程的主循环
while (!shutdown) {
// 1. 等待 IO 事件(最多阻塞 1ms)
int n = epoll_wait(epfd, events, MAX_EVENTS, 1);
// 2. 处理就绪的事件
for (int i = 0; i < n; i++) {
if (events[i].events & EPOLLIN) {
// 有数据可读
int fd = events[i].data.fd;
int bytes = read(fd, buffer, BUFFER_SIZE); // 非阻塞读
if (bytes > 0) {
process_request(buffer, bytes);
} else if (bytes == 0) {
// 客户端关闭连接
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
}
// bytes == -1 && errno == EAGAIN:数据还没到,下次再处理
}
if (events[i].events & EPOLLOUT) {
// 可以发送响应
int fd = events[i].data.fd;
write(fd, response, response_len);
}
}
// 3. 处理定时器事件(超时检查等)
process_timers();
}

关键设计

  1. 非阻塞 IO:所有 IO 操作都设置 O_NONBLOCK,read/write 立即返回
  2. epoll 边缘触发(ET):只在状态变化时通知一次,减少系统调用
  3. 连接池复用:预分配连接结构体,避免频繁 malloc/free
  4. 内存池:每个请求从内存池分配,请求结束后整块回收

性能数据

指标传统阻塞模型Nginx 事件驱动
内存消耗(10K 连接)10GB+(每线程 1MB 栈)~200MB
上下文切换频繁(每秒万次)极少(单线程)
QPS(静态资源)1万10万+
CPU 利用率大量浪费在切换专注业务处理

Q3:Nginx 的平滑重启是怎么实现的?高频

Section titled “Q3:Nginx 的平滑重启是怎么实现的?”

nginx -s reload 的完整流程

┌─────────────────────────────────────────────────────────┐
│ 阶段一:发送信号 │
├─────────────────────────────────────────────────────────┤
│ $ nginx -s reload │
│ → 向 Master 发送 SIGHUP 信号 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 阶段二:Master 处理 │
├─────────────────────────────────────────────────────────┤
│ 1. 重新读取 nginx.conf 并校验语法 │
│ 2. 打开新的日志文件 │
│ 3. 启动新的 Worker 进程(使用新配置) │
│ 4. 向老 Worker 发送 SIGQUIT 信号 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 阶段三:新老 Worker 共存(关键!) │
├─────────────────────────────────────────────────────────┤
│ │
│ 新 Worker(新配置) 老 Worker(旧配置) │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 接受新连接 │ │ 不接受新连接 │ │
│ │ 处理新请求 │ │ 处理已有请求 │ │
│ │ │ │ 等待请求完成 │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ 监听同一端口(通过 SO_REUSEPORT 或共享 fd) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 阶段四:老 Worker 优雅退出 │
├─────────────────────────────────────────────────────────┤
│ 1. 老 Worker 收到 SIGQUIT │
│ 2. 停止 accept 新连接 │
│ 3. 等待当前处理的请求完成 │
│ 4. 最长等待 worker_shutdown_timeout(默认 0) │
│ 5. 超时后强制关闭连接 │
│ 6. Worker 进程退出 │
└─────────────────────────────────────────────────────────┘

配置示例

nginx.conf
worker_shutdown_timeout 30s; # 老 Worker 最长等待 30 秒
# 平滑升级二进制文件
# 1. 备份旧二进制:cp /usr/sbin/nginx /usr/sbin/nginx.old
# 2. 用新二进制替换:cp nginx_new /usr/sbin/nginx
# 3. 发送 USR2 信号:kill -USR2 $(cat /var/run/nginx.pid)
# → Master 启动新 Master + 新 Worker
# 4. 发送 WINCH 信号:kill -WINCH $(cat /var/run/nginx.pid.oldbin)
# → 老 Worker 优雅退出
# 5. 确认无问题后:kill -QUIT $(cat /var/run/nginx.pid.oldbin)
# → 老 Master 退出

链式追问二:Spring Cloud Gateway 核心原理

Section titled “链式追问二:Spring Cloud Gateway 核心原理”

Q4:Spring Cloud Gateway 的请求处理流程是什么?必考

Section titled “Q4:Spring Cloud Gateway 的请求处理流程是什么?”

完整处理链路

客户端请求
┌─────────────────────────────────────────────────────────┐
│ DispatcherHandler(Spring WebFlux 核心) │
│ • 遍历 HandlerMapping 找到处理器 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ RoutePredicateHandlerMapping │
│ • 遍历所有 RouteDefinition │
│ • 对每个 Route 应用所有 Predicate │
│ • 找到第一个完全匹配的路由 │
│ │
│ 示例匹配过程: │
│ Route 1: Path=/api/users/** AND Method=GET │
│ Route 2: Path=/api/users/** AND Header=X-Version, v2 │
│ → 按定义顺序匹配,先匹配成功的生效 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ FilteringWebHandler │
│ • 收集所有 GlobalFilter │
│ • 收集当前路由的 GatewayFilter │
│ • 按 Order 排序,构建过滤器链 │
│ • DefaultFilter + GlobalFilter + RouteFilter │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 过滤器链 - Pre 阶段(按 Order 顺序执行) │
├─────────────────────────────────────────────────────────┤
│ │
│ Order=-100 AdaptiveReactiveFilter(自适应) │
│ Order=-90 JwtAuthFilter(认证) │
│ Order=-80 RateLimitFilter(限流) │
│ Order=-70 TraceFilter(链路追踪) │
│ Order=0 RouteSpecificFilter(路由特定过滤器) │
│ Order=10 StripPrefix(路径处理) │
│ │
│ 执行顺序:从小到大 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ NettyRoutingFilter(核心转发过滤器) │
│ • 解析 URI:lb://order-service │
│ • 从注册中心获取实例列表:[10.0.0.1:8080, 10.0.0.2:8080]│
│ • 负载均衡选择实例 │
│ • 使用 Netty HttpClient 发送请求(非阻塞) │
│ • 将响应传递给过滤器链 │
└─────────────────────────────────────────────────────────┘
后端服务处理请求,返回响应
┌─────────────────────────────────────────────────────────┐
│ 过滤器链 - Post 阶段(按 Order 逆序执行) │
├─────────────────────────────────────────────────────────┤
│ │
│ Order=10 ModifyResponseFilter │
│ Order=0 RouteSpecificFilter │
│ Order=-70 TraceFilter(记录耗时) │
│ Order=-80 RateLimitFilter(释放令牌) │
│ Order=-90 JwtAuthFilter(无操作) │
│ │
│ 执行顺序:从大到小(逆序) │
└─────────────────────────────────────────────────────────┘
返回响应给客户端

核心源码解析

// FilteringWebHandler.handle() 核心逻辑
public Mono<Void> handle(ServerWebExchange exchange) {
// 1. 获取路由
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
// 2. 收集所有过滤器
List<GatewayFilter> gatewayFilters = route.getFilters();
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
combined.addAll(gatewayFilters);
// 3. 按 Order 排序
AnnotationAwareOrderComparator.sort(combined);
// 4. 构建过滤器链(装饰器模式)
return new DefaultGatewayFilterChain(combined).filter(exchange);
}
// 过滤器链的递归调用
class DefaultGatewayFilterChain {
private int index = 0;
public Mono<Void> filter(ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < filters.size()) {
GatewayFilter filter = filters.get(this.index++);
return filter.filter(exchange, this); // 递归调用下一个过滤器
}
return Mono.empty(); // 链条结束
});
}
}

Q5:Predicate 和 Filter 的区别?如何自定义?高频

Section titled “Q5:Predicate 和 Filter 的区别?如何自定义?”

对比表格

维度Predicate(断言)Filter(过滤器)
作用路由匹配条件(是否匹配该路由)请求/响应增强(匹配后做什么)
执行时机路由选择阶段请求转发前后
返回值boolean(true/false)Mono<Void>(异步处理)
典型用途路径、方法、Header、时间等条件判断认证、限流、日志、熔断等
配置位置predicates 列表filters 列表

配置示例

spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/** # 路径匹配
- Method=GET,POST # 方法匹配
- Header=X-Request-Id, \d+ # Header 正则匹配
- Query=version, v2 # 查询参数匹配
- After=2024-01-01T00:00:00+08:00 # 时间匹配
- Weight=group1, 80 # 权重匹配(80% 流量)
filters:
- StripPrefix=1 # 去掉 /api 前缀
- AddRequestHeader=X-Source, gateway
- AddRequestParameter=source, gateway
- RequestRateLimiter=10,20 # 限流
- CircuitBreaker=myCircuit # 熔断

自定义 Predicate 示例

// 自定义:根据用户等级路由
@Component
public class UserLevelRoutePredicateFactory
extends AbstractRoutePredicateFactory<UserLevelRoutePredicateFactory.Config> {
public UserLevelRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// 从请求中获取用户等级(Header 或 JWT)
String level = exchange.getRequest().getHeaders().getFirst("X-User-Level");
// 判断是否符合配置的等级
return config.getLevel().equals(level);
};
}
@Data
public static class Config {
private String level; // 配置参数:level=VIP
}
}
// 配置使用
// predicates:
// - UserLevel=VIP

自定义 GlobalFilter 示例

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Autowired
private JwtUtil jwtUtil;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().value();
// 1. 白名单检查
if (isWhitePath(path)) {
return chain.filter(exchange);
}
// 2. 提取 Token
String token = request.getHeaders().getFirst("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
return unauthorized(exchange);
}
token = token.substring(7);
// 3. 验证 Token
try {
Claims claims = jwtUtil.validateToken(token);
// 4. 透传用户信息到下游服务
ServerHttpRequest newRequest = request.mutate()
.header("X-User-Id", claims.getSubject())
.header("X-User-Roles", String.join(",", claims.get("roles", List.class)))
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
} catch (JwtException e) {
return unauthorized(exchange);
}
}
private boolean isWhitePath(String path) {
return path.startsWith("/api/auth/")
|| path.startsWith("/api/public/")
|| path.equals("/health");
}
private Mono<Void> unauthorized(ServerWebExchange exchange) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = "{\"code\":401,\"message\":\"Unauthorized\"}";
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(body.getBytes());
return exchange.getResponse().writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -100; // 高优先级,尽早拦截
}
}

Q6:Gateway 如何实现动态路由(无需重启)?中频

Section titled “Q6:Gateway 如何实现动态路由(无需重启)?”

方案对比

方案实时性持久化适用场景
配置文件需重启文件小规模,路由少变
Nacos 配置中心实时Nacos生产环境推荐
数据库 + 定时刷新延迟数据库需要审计、版本管理
数据库 + 事件推送实时数据库复杂业务场景

Nacos 动态路由实现

@Component
@RefreshScope // 支持动态刷新
public class DynamicRouteService {
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private ApplicationEventPublisher publisher;
/**
* 监听 Nacos 配置变更
*/
@NacosConfigListener(dataId = "gateway-routes.json", groupId = "DEFAULT_GROUP")
public void onRoutesChanged(String config) {
List<RouteDefinition> definitions = JSON.parseArray(config, RouteDefinition.class);
for (RouteDefinition definition : definitions) {
updateRoute(definition);
}
// 刷新路由缓存
publisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* 更新单个路由
*/
public void updateRoute(RouteDefinition definition) {
try {
// 先删除旧路由
routeDefinitionWriter.delete(Mono.just(definition.getId())).block();
// 添加新路由
routeDefinitionWriter.save(Mono.just(definition)).block();
log.info("路由更新成功: {}", definition.getId());
} catch (Exception e) {
log.error("路由更新失败: {}", definition.getId(), e);
}
}
/**
* 删除路由
*/
public void deleteRoute(String routeId) {
routeDefinitionWriter.delete(Mono.just(routeId)).block();
publisher.publishEvent(new RefreshRoutesEvent(this));
}
}

Nacos 配置示例(gateway-routes.json)

[
{
"id": "user-service",
"uri": "lb://user-service",
"predicates": [
{"name": "Path", "args": {"pattern": "/api/users/**"}}
],
"filters": [
{"name": "StripPrefix", "args": {"parts": "1"}},
{"name": "RequestRateLimiter", "args": {
"redis-rate-limiter.replenishRate": "100",
"redis-rate-limiter.burstCapacity": "200",
"key-resolver": "#{@userKeyResolver}"
}}
]
},
{
"id": "order-service",
"uri": "lb://order-service",
"predicates": [
{"name": "Path", "args": {"pattern": "/api/orders/**"}}
]
}
]

Q7:Nginx 和 Spring Cloud Gateway 的区别?如何选择?高频

Section titled “Q7:Nginx 和 Spring Cloud Gateway 的区别?如何选择?”
维度NginxSpring Cloud Gateway
语言CJava(Kotlin DSL)
IO 模型epoll 事件循环Reactor + Netty
性能极高(10万+ QPS)较高(1-3万 QPS)
功能静态资源、反向代理、负载均衡动态路由、限流、熔断、鉴权
配置方式nginx.conf 文件YAML/Java DSL
动态路由需要 Lua 模块原生支持
与微服务集成需要手动配置自动服务发现
学习曲线低(配置简单)高(需要理解 Reactor)
内存占用极低(~10MB)较高(~200MB+)

生产环境典型架构

外网流量
┌─────────────────────────────────────┐
│ Nginx 接入层 │
│ • SSL 证书终止 │
│ • 静态资源缓存 │
│ • DDoS 防护(IP 黑名单、连接限制) │
│ • 负载均衡到 Gateway 集群 │
│ • Gzip 压缩 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Spring Cloud Gateway │
│ • JWT 认证鉴权 │
│ • 动态路由(Nacos 配置中心) │
│ • 业务限流(用户/接口级别) │
│ • 灰度发布 │
│ • 熔断降级 │
│ • 链路追踪(Sleuth) │
└─────────────────────────────────────┘
├── 用户服务
├── 订单服务
└── 支付服务

选择建议

  • 只用 Nginx:小型项目、静态网站、简单的反向代理
  • Nginx + Gateway:微服务架构、需要动态路由、深度集成 Spring Cloud
  • Gateway 单独使用:内网服务网关、不需要 SSL 处理

Q8:Spring Cloud Gateway vs Zuul 1.x vs Zuul 2.x?加分

Section titled “Q8:Spring Cloud Gateway vs Zuul 1.x vs Zuul 2.x?”

架构对比

维度Zuul 1.xZuul 2.xSpring Cloud Gateway
IO 模型阻塞 Servlet非阻塞 Netty非阻塞 Reactor
线程模型每请求一线程事件循环事件循环
吞吐量2-3万 QPS8-10万 QPS1-3万 QPS
延迟较高(线程切换)
Spring 集成官方支持社区支持官方支持
社区活跃度维护模式
学习曲线

为什么 Gateway 更流行

  1. Spring 官方支持:与 Spring Boot、Spring Cloud 深度集成
  2. 响应式生态:基于 Reactor,支持背压、流式处理
  3. 社区活跃:持续迭代,bug 修复快
  4. 功能丰富:内置限流、熔断、重试等

Zuul 1.x 的问题

每个请求一个线程:
请求1 → Tomcat 线程1 → 阻塞等待后端响应
请求2 → Tomcat 线程2 → 阻塞等待后端响应
...
问题:
• 线程数有限(默认 200)
• 大量请求排队或被拒绝
• 线程切换开销大
• 后端慢拖垮整个网关

Gateway 的优势

单线程事件循环:
请求1 ─┐
请求2 ─┼→ Netty EventLoop → 非阻塞转发 → 后端响应回调
... ─┘
优势:
• 线程数少(= CPU 核心数)
• 无阻塞等待
• 内存占用低
• 后端慢不影响其他请求

现象:每分钟出现几次 504 Gateway Timeout,后端服务响应正常。

排查步骤

// 1. 开启 Gateway 详细日志
logging:
level:
org.springframework.cloud.gateway: DEBUG
org.springframework.web: DEBUG
// 2. 添加耗时统计 Filter
@Component
public class TimingFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long start = System.currentTimeMillis();
exchange.getAttributes().put("startTime", start);
return chain.filter(exchange).doFinally(signalType -> {
Long startTime = exchange.getAttribute("startTime");
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
log.info("请求 {} 耗时 {}ms, 信号: {}",
exchange.getRequest().getPath(), duration, signalType);
}
});
}
@Override
public int getOrder() { return Integer.MIN_VALUE; }
}
// 3. 发现问题:有些请求耗时 5000ms(默认超时)

根因分析

# 问题配置
spring:
cloud:
gateway:
httpclient:
connect-timeout: 3000
response-timeout: 5s # ← 默认值
# 后端服务 GC 停顿偶尔超过 5 秒
# 导致 Gateway 超时,但后端服务最终处理成功

解决方案

spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000 # 连接超时 1 秒
response-timeout: 30s # 响应超时 30 秒
pool:
type: FIXED # 固定连接池
max-connections: 500 # 最大连接数
max-idle-time: 60s # 空闲连接保留时间
acquire-timeout: 5000 # 获取连接超时

本质一句话:Gateway 超时配置要略大于后端服务最长可能处理时间,同时配合熔断降级,避免雪崩。