Zuul 网关(已进入维护模式)
导学
"白歌,前端同学快疯了。"黄俪指着屏幕说,"我们的前端要调 8 个微服务的接口,每个服务的域名都不一样。而且在浏览器里跨域请求全被 CORS 拦了。"
白歌看了一眼架构图:"我们需要一个 API 网关,作为所有微服务对外的统一入口。Netflix Zuul 是上一代方案,虽然已进入维护模式,但理解它的设计思想对掌握 Gateway 很有帮助。"
定位与问题场景
API 网关在微服务架构中的角色:
网关解决的六大问题:
| 问题 | 网关方案 |
|---|---|
| 入口分散 | 统一入口,单一域名 |
| 跨域(CORS) | 网关统一处理 CORS |
| 认证鉴权 | 网关层统一校验 Token |
| 限流 | 网关前置限流,保护后端 |
| 协议转换 | HTTP → gRPC / WebSocket |
| 日志审计 | 统一记录所有 API 调用 |
Zuul 1.x 核心原理
Zuul 1.x 基于 Servlet 2.5 阻塞模型,使用 ZuulFilter 过滤器链处理请求:
Filter 分类:
| 类型 | 执行时机 | 典型用途 |
|---|---|---|
| pre | 路由之前 | 认证鉴权、限流、参数校验、请求日志 |
| route | 路由之时 | 转发请求到后端服务 |
| post | 路由之后 | 添加响应头、记录响应日志 |
| error | 发生错误时 | 错误处理、异常格式统一 |
完整示例:飞翔科技 Zuul 网关
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类
@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class ZuulGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApplication.class, args);
}
}
路由配置
spring:
application:
name: feixiang-api-gateway
zuul:
routes:
# 方案一:服务名路由(推荐)
order-service:
path: /api/order/**
serviceId: FEIXIANG-ORDER-SERVICE
stripPrefix: true # 转发时去掉 /api/order 前缀
inventory-service:
path: /api/inventory/**
serviceId: FEIXIANG-INVENTORY-SERVICE
# 方案二:URL 路由(不走 Eureka)
payment-service:
path: /api/payment/**
url: http://localhost:9001
路由效果:
客户端请求:GET /api/inventory/1001
↓ stripPrefix=true
实际转发:GET /inventory/1001 → 库存服务
自定义 Filter——Token 认证
@Component
public class AuthFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre"; // Pre Filter
}
@Override
public int filterOrder() {
return 0; // 最高优先级
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
String path = ctx.getRequest().getRequestURI();
// 排除登录接口
return !path.startsWith("/api/auth/login");
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("{\"code\":401,\"message\":\"未授权\"}");
ctx.getResponse().setContentType("application/json");
}
// 将 Token 中的用户 ID 传递给下游
Long userId = parseUserId(token);
ctx.addZuulRequestHeader("X-User-Id", String.valueOf(userId));
return null;
}
}
自定义 Filter——API 日志审计
@Component
public class ApiLogFilter extends ZuulFilter {
@Override
public String filterType() { return "post"; }
@Override
public int filterOrder() { return 0; }
@Override
public boolean shouldFilter() { return true; }
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info("API 调用:{} {} → {},耗时:{}ms",
request.getMethod(),
request.getRequestURI(),
ctx.getResponseStatusCode(),
System.currentTimeMillis() - (Long) ctx.get("startTime")
);
return null;
}
}
Zuul 2.x 与 Zuul 1.x 的区别
| 维度 | Zuul 1.x | Zuul 2.x |
|---|---|---|
| I/O 模型 | Servlet 阻塞 | Netty 异步非阻塞 |
| 线程模型 | 每连接一线程 | Event Loop 事件驱动 |
| 推送支持 | 不支持 | 支持 WebSocket / Server-Sent Events |
| Spring 集成 | 成熟 | 不成熟 |
| 当前状态 | 维护模式 | Netflix 内部使用,未广泛推广 |
Spring Cloud 官方推荐不要投入 Zuul 2.x,直接迁移到 Spring Cloud Gateway。
易错场景
1. 路由配置中 path 和 serviceId 不匹配导致 404
# ❌ 错误:path 和实际接口不符
inventory-service:
path: /api/inventory/**
serviceId: FEIXIANG-INVENTORY-SERVICE
# 客户端请求 /api/stock/1001 → 404
2. stripPrefix 设置为 false 导致后端 404
# 若 stripPrefix=false
# 客户端请求 /api/inventory/1001
# 实际转发:/api/inventory/1001 → 库存服务
# 但库存服务只有 /inventory/{productId} 接口 → 404
3. 敏感 Header 未传递
Zuul 默认过滤 Cookie、Set-Cookie、Authorization 三个敏感 Header。若有特殊需求需配置:
zuul:
sensitive-headers: # 清空敏感头列表
routes:
auth-service:
sensitive-headers: # 仅该路由不传递 Authorization
4. Zuul 1.x 的阻塞模型导致长连接饿死线程
Zuul 1.x 基于 Servlet,每个请求占用一个 Tomcat 工作线程。如果后端服务响应慢(如 5 秒),线程被长时间占用,高并发下线程池很快耗尽。这是 Zuul 1.x 的核心缺陷,也是 Gateway 诞生的原因。
面试考点
Zuul 1.x 和 Spring Cloud Gateway 的区别?
Zuul 1.x 基于 Servlet 2.5 阻塞模型,每个请求一个线程,不支持 WebSocket。Gateway 基于 Spring WebFlux 和 Reactor 异步非阻塞模型,Event Loop + 少量线程处理大量并发,支持长连接。Gateway 的路由匹配(Predicate)和过滤器(Filter)机制比 ZuulFilter 更灵活。Zuul 1.x 已进入维护模式,新项目应使用 Gateway。
Zuul 的生命周期过滤器有哪些?
pre(路由前:认证/限流)、route(路由时:转发请求)、post(路由后:处理响应/日志)、error(异常时:统一错误格式)。四类过滤器按filterOrder排序执行,同类型内数字越小越先执行。
Zuul 与 Nginx 的分工?
Nginx 负责最前端的流量接入(静态资源、SSL 卸载、IP 黑白名单),Zuul/Gateway 负责业务层的 API 治理(认证、限流、路由、日志)。典型架构:CDN → Nginx → API 网关 → 微服务。Nginx 偏运维层面,Zuul/Gateway 偏应用层面。
小结
Zuul 开创了 Java 侧 API 网关的先河,通过过滤器链机制提供路由、认证、限流等能力。但 Servlet 阻塞模型导致它在高并发 WebSocket 场景下力不从心。Spring Cloud Gateway 以响应式架构解决了这些问题——这正是下一节的内容。