Zuul入门教程 - Spring Cloud微服务网关完整指南
大约 16 分钟
Zuul入门教程 - Spring Cloud微服务网关完整指南
目录
1. Zuul简介
Zuul是Netflix开源的微服务API网关,在微服务架构中充当统一入口,负责路由转发、请求过滤、负载均衡、安全认证等功能。
- ✅ 动态路由:根据请求路径动态路由到不同的微服务
- ✅ 请求过滤:在请求的不同阶段执行过滤逻辑
- ✅ 负载均衡:与Ribbon集成实现负载均衡
- ✅ 服务发现:与Eureka集成自动发现服务
- ✅ 安全认证:统一处理认证和授权
- ✅ 限流熔断:与Hystrix集成实现限流和熔断
- ✅ 监控日志:记录请求日志和监控信息
客户端请求
↓
Zuul API网关 (统一入口)
↓
├──→ 用户服务 (User Service)
├──→ 订单服务 (Order Service)
├──→ 商品服务 (Product Service)
└──→ 支付服务 (Payment Service)重要提示: Netflix Zuul已经进入维护模式,Spring官方推荐使用Spring Cloud Gateway作为替代方案。但Zuul仍然被广泛使用,本教程将详细介绍Zuul的使用。
| 特性 | Zuul | Spring Cloud Gateway |
|---|---|---|
| 技术栈 | Servlet 2.5 | WebFlux (Reactive) |
| 性能 | 阻塞IO | 非阻塞IO,性能更好 |
| 维护状态 | 维护模式 | 积极维护 |
| 学习曲线 | 相对简单 | 需要了解响应式编程 |
| 适用场景 | 传统Spring应用 | 新项目推荐 |
2. 环境搭建
- JDK版本:JDK 8或更高版本(推荐JDK 11+)
- Maven版本:Maven 3.6+ 或 Gradle 6+
- Spring Boot版本:2.x(Zuul在Spring Boot 3.x中已移除)
- Spring Cloud版本:Hoxton.SR12或更高版本
- IDE:IntelliJ IDEA、Eclipse等
方式1:使用Spring Initializr
- 访问 https://start.spring.io/
- 选择以下配置:
- Project: Maven
- Language: Java
- Spring Boot: 2.7.x
- Group: com.example
- Artifact: zuul-gateway
- 添加依赖:
- Spring Cloud Routing → Zuul
- Spring Cloud Discovery → Eureka Client(可选)
- 点击Generate下载项目
方式2:手动创建
创建标准的Maven项目结构:
zuul-gateway/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── gateway/
│ │ │ ├── ZuulGatewayApplication.java
│ │ │ └── filter/
│ │ │ └── CustomFilter.java
│ │ └── resources/
│ │ └── application.yml
│ └── test/
└── pom.xml在pom.xml中添加必要的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>zuul-gateway</artifactId>
<version>1.0.0</version>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.9</spring-cloud.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Zuul网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- Eureka客户端(可选,用于服务发现) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Actuator(用于监控) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>3. 创建Zuul网关服务
创建ZuulGatewayApplication.java:
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* Zuul网关启动类
* @EnableZuulProxy 启用Zuul代理功能
*/
@SpringBootApplication
@EnableZuulProxy
public class ZuulGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApplication.class, args);
}
}创建application.yml:
server:
port: 8080
spring:
application:
name: zuul-gateway
# Zuul配置
zuul:
routes:
# 用户服务路由
user-service:
path: /user/**
url: http://localhost:8081
# 订单服务路由
order-service:
path: /order/**
url: http://localhost:8082# 编译项目
mvn clean compile
# 运行服务
mvn spring-boot:run
# 或者打包后运行
mvn clean package
java -jar target/zuul-gateway-1.0.0.jar启动后访问:http://localhost:8080/actuator/routes
应该能看到配置的路由信息。
4. 路由配置详解
简单路由
zuul:
routes:
user-service:
path: /user/**
url: http://localhost:8081说明:
path:匹配的请求路径,支持Ant风格通配符url:目标服务的URL
访问示例:
- 请求:
http://localhost:8080/user/api/users - 转发到:
http://localhost:8081/api/users
服务ID路由(配合Eureka)
zuul:
routes:
user-service:
path: /user/**
serviceId: user-service # 服务注册名称stripPrefix - 去除前缀
zuul:
routes:
user-service:
path: /api/user/**
url: http://localhost:8081
strip-prefix: true # 去除 /api/user 前缀示例:
- 请求:
http://localhost:8080/api/user/users - 转发到:
http://localhost:8081/users(去除了/api/user前缀)
sensitiveHeaders - 敏感头信息
zuul:
routes:
user-service:
path: /user/**
url: http://localhost:8081
sensitive-headers: Cookie,Set-Cookie,Authorizationretryable - 是否重试
zuul:
routes:
user-service:
path: /user/**
url: http://localhost:8081
retryable: truezuul:
prefix: /api # 统一前缀
strip-prefix: true # 去除前缀
routes:
user-service:
path: /user/**
url: http://localhost:8081访问示例:
- 请求:
http://localhost:8080/api/user/users - 实际路由:
/user/users - 转发到:
http://localhost:8081/users
zuul:
ignored-patterns:
- /admin/**
- /actuator/**
routes:
user-service:
path: /user/**
url: http://localhost:8081zuul:
routes:
# 更具体的路由放在前面
user-detail:
path: /user/detail/**
url: http://localhost:8081
order: 1
user-service:
path: /user/**
url: http://localhost:8081
order: 2server:
port: 8080
spring:
application:
name: zuul-gateway
zuul:
# 统一前缀
prefix: /api
strip-prefix: true
# 忽略的路由
ignored-patterns:
- /admin/**
# 路由配置
routes:
user-service:
path: /user/**
serviceId: user-service
strip-prefix: false
sensitive-headers: Cookie,Set-Cookie
retryable: true
order-service:
path: /order/**
url: http://localhost:8082
strip-prefix: true
product-service:
path: /product/**
serviceId: product-service
# 默认路由(如果启用Eureka,会自动创建)
ignored-services: "*" # 禁用默认路由5. 过滤器详解
Zuul提供了4种过滤器类型:
- pre:路由前执行,用于身份验证、参数校验等
- routing:路由时执行,用于构建发送给微服务的请求
- post:路由后执行,用于处理响应、添加响应头等
- error:发生错误时执行,用于错误处理
请求进入
↓
pre过滤器
↓
routing过滤器
↓
调用微服务
↓
post过滤器
↓
响应返回
↓
(如果出错)error过滤器Pre过滤器示例
package com.example.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* 请求日志过滤器(Pre类型)
*/
@Component
public class PreLogFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(PreLogFilter.class);
/**
* 过滤器类型:pre、routing、post、error
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器执行顺序,数字越小优先级越高
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 是否执行该过滤器
* true:执行
* false:不执行
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器具体逻辑
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info("========== Pre Log Filter ==========");
logger.info("Request Method: {}", request.getMethod());
logger.info("Request URL: {}", request.getRequestURL().toString());
logger.info("Request URI: {}", request.getRequestURI());
logger.info("Remote Address: {}", request.getRemoteAddr());
return null;
}
}Post过滤器示例
package com.example.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
/**
* 响应头过滤器(Post类型)
*/
@Component
public class PostResponseFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(PostResponseFilter.class);
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse response = ctx.getResponse();
// 添加自定义响应头
response.setHeader("X-Gateway-Version", "1.0.0");
response.setHeader("X-Response-Time", String.valueOf(System.currentTimeMillis()));
logger.info("Response Status: {}", response.getStatus());
return null;
}
}认证过滤器示例
package com.example.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* 认证过滤器(Pre类型)
*/
@Component
public class AuthFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0; // 优先级最高
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String uri = request.getRequestURI();
// 排除登录接口
return !uri.contains("/login") && !uri.contains("/public");
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getHeader("Authorization");
if (StringUtils.isEmpty(token)) {
// 阻止请求继续
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("{\"code\":401,\"message\":\"未授权,请先登录\"}");
return null;
}
// 验证token(这里简化处理,实际应该调用认证服务)
if (!isValidToken(token)) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("{\"code\":401,\"message\":\"Token无效\"}");
return null;
}
// 将用户信息添加到请求头,传递给下游服务
ctx.addZuulRequestHeader("X-User-Id", getUserIdFromToken(token));
return null;
}
private boolean isValidToken(String token) {
// TODO: 实现token验证逻辑
return token != null && token.startsWith("Bearer ");
}
private String getUserIdFromToken(String token) {
// TODO: 从token中解析用户ID
return "12345";
}
}限流过滤器示例
package com.example.gateway.filter;
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
/**
* 限流过滤器(Pre类型)
*/
@Component
public class RateLimitFilter extends ZuulFilter {
// 每秒允许10个请求
private static final RateLimiter rateLimiter = RateLimiter.create(10.0);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 2;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
if (!rateLimiter.tryAcquire()) {
// 限流,拒绝请求
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(429);
ctx.setResponseBody("{\"code\":429,\"message\":\"请求过于频繁,请稍后再试\"}");
}
return null;
}
}// Pre过滤器执行顺序
AuthFilter: order = 0 // 最先执行认证
PreLogFilter: order = 1 // 然后记录日志
RateLimitFilter: order = 2 // 最后限流检查
// Post过滤器执行顺序
PostResponseFilter: order = 1 // 添加响应头zuul:
SendErrorFilter:
error:
disable: true # 禁用SendErrorFilter
PreDecorationFilter:
pre:
disable: true # 禁用PreDecorationFilter6. 与Eureka服务发现集成
确保pom.xml中包含Eureka客户端依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient // 启用Eureka客户端
public class ZuulGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApplication.class, args);
}
}server:
port: 8080
spring:
application:
name: zuul-gateway
# Eureka配置
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
fetch-registry: true
register-with-eureka: true
instance:
prefer-ip-address: true
# Zuul配置
zuul:
routes:
# 使用serviceId而不是url
user-service:
path: /user/**
serviceId: user-service # 服务注册名称
order-service:
path: /order/**
serviceId: order-service当启用Eureka后,Zuul会自动为所有注册的服务创建路由:
zuul:
# 禁用自动路由
ignored-services: "*"
# 或者只禁用特定服务
ignored-services:
- service-name-1
- service-name-27. 负载均衡配置
Zuul默认集成了Ribbon实现负载均衡:
# Ribbon配置
user-service:
ribbon:
# 连接超时时间(毫秒)
ConnectTimeout: 3000
# 读取超时时间(毫秒)
ReadTimeout: 5000
# 最大重试次数
MaxAutoRetries: 1
# 最大重试下一个服务的次数
MaxAutoRetriesNextServer: 2
# 是否对所有操作都进行重试
OkToRetryOnAllOperations: false
# 负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRulepackage com.example.gateway.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RibbonConfig {
/**
* 随机负载均衡策略
*/
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}package com.example.gateway.config;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RibbonClient(name = "user-service", configuration = UserServiceRibbonConfig.class)
public class UserServiceRibbonConfig {
@Bean
public IRule ribbonRule() {
// 自定义负载均衡规则
return new CustomRule();
}
@Bean
public IPing ribbonPing() {
// 自定义健康检查
return new CustomPing();
}
}8. 请求和响应处理
@Component
public class RequestModifyFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
// 添加请求参数
ctx.addZuulRequestHeader("X-Gateway-Request", "true");
ctx.addZuulRequestHeader("X-Request-Time", String.valueOf(System.currentTimeMillis()));
// 修改请求URI
// ctx.set("requestURI", "/new/path");
return null;
}
}@Component
public class ResponseModifyFilter extends ZuulFilter {
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
InputStream responseDataStream = ctx.getResponseDataStream();
if (responseDataStream != null) {
try {
// 读取响应内容
String responseData = StreamUtils.copyToString(
responseDataStream,
Charset.forName("UTF-8")
);
// 修改响应内容
responseData = modifyResponse(responseData);
// 设置新的响应内容
ctx.setResponseBody(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
private String modifyResponse(String responseData) {
// 修改响应数据的逻辑
return responseData.replace("old", "new");
}
}zuul:
routes:
user-service:
path: /user/**
serviceId: user-service
retryable: true
# Ribbon重试配置
user-service:
ribbon:
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 2
OkToRetryOnAllOperations: false9. 安全认证和授权
package com.example.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* JWT认证过滤器
*/
@Component
public class JwtAuthFilter extends ZuulFilter {
@Value("${jwt.secret}")
private String jwtSecret;
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String uri = request.getRequestURI();
// 排除公开接口
return !uri.startsWith("/public") && !uri.startsWith("/login");
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = extractToken(request);
if (StringUtils.isEmpty(token)) {
rejectRequest(ctx, "缺少认证token");
return null;
}
try {
Claims claims = parseToken(token);
// 将用户信息添加到请求头
ctx.addZuulRequestHeader("X-User-Id", claims.getSubject());
ctx.addZuulRequestHeader("X-User-Role", claims.get("role", String.class));
} catch (Exception e) {
rejectRequest(ctx, "Token无效或已过期");
return null;
}
return null;
}
private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
private Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
}
private void rejectRequest(RequestContext ctx, String message) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("{\"code\":401,\"message\":\"" + message + "\"}");
}
}@Component
public class RoleAuthFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1; // 在JWT认证之后执行
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String uri = request.getRequestURI();
// 只对/admin路径进行角色检查
return uri.startsWith("/admin");
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
String userRole = ctx.getZuulRequestHeaders().get("X-User-Role");
if (!"ADMIN".equals(userRole)) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(403);
ctx.setResponseBody("{\"code\":403,\"message\":\"权限不足\"}");
}
return null;
}
}10. 限流和熔断
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>启用Hystrix
@SpringBootApplication
@EnableZuulProxy
@EnableHystrix // 启用Hystrix
public class ZuulGatewayApplication {
// ...
}配置熔断
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000 # 超时时间5秒
user-service:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 # 用户服务超时3秒
# 熔断器配置
zuul:
routes:
user-service:
path: /user/**
serviceId: user-service
# 熔断后的降级处理
fallback:
enabled: true实现降级处理
package com.example.gateway.fallback;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* 用户服务降级处理
*/
@Component
public class UserServiceFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "user-service"; // 指定服务名称
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() {
return 200;
}
@Override
public String getStatusText() {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() {
String message = "{\"code\":500,\"message\":\"服务暂时不可用,请稍后重试\"}";
return new ByteArrayInputStream(message.getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}使用Guava RateLimiter
@Component
public class RateLimitFilter extends ZuulFilter {
// 每个IP每秒10个请求
private final Map<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 2;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String clientId = getClientId(request);
RateLimiter rateLimiter = rateLimiters.computeIfAbsent(
clientId,
k -> RateLimiter.create(10.0) // 每秒10个请求
);
if (!rateLimiter.tryAcquire()) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(429);
ctx.setResponseBody("{\"code\":429,\"message\":\"请求过于频繁\"}");
}
return null;
}
private String getClientId(HttpServletRequest request) {
// 可以根据IP、用户ID等标识
return request.getRemoteAddr();
}
}11. 监控和日志
配置Actuator
management:
endpoints:
web:
exposure:
include: "*" # 暴露所有端点
endpoint:
health:
show-details: always访问监控端点
- 路由信息:
http://localhost:8080/actuator/routes - 路由详情:
http://localhost:8080/actuator/routes/details - 过滤器信息:
http://localhost:8080/actuator/filters - 健康检查:
http://localhost:8080/actuator/health
@Component
public class RequestLogFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(RequestLogFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 100; // 最后执行
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
long startTime = System.currentTimeMillis();
ctx.set("startTime", startTime);
logger.info("========== Request Start ==========");
logger.info("Request URL: {}", request.getRequestURL());
logger.info("Request Method: {}", request.getMethod());
logger.info("Remote Address: {}", request.getRemoteAddr());
return null;
}
}
@Component
public class ResponseLogFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(ResponseLogFilter.class);
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 100;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response = ctx.getResponse();
Long startTime = (Long) ctx.get("startTime");
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
logger.info("Request Duration: {}ms", duration);
}
logger.info("Response Status: {}", response.getStatus());
logger.info("========== Request End ==========");
return null;
}
}<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>配置后会自动生成traceId和spanId,方便追踪请求链路。
12. 实际应用案例
server:
port: 8080
spring:
application:
name: zuul-gateway
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
zuul:
prefix: /api
strip-prefix: true
routes:
user-service:
path: /user/**
serviceId: user-service
strip-prefix: false
order-service:
path: /order/**
serviceId: order-service
ignored-services: "*"
sensitive-headers: Cookie,Set-Cookie
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000@Configuration
public class SecurityConfig {
@Bean
public JwtAuthFilter jwtAuthFilter() {
return new JwtAuthFilter();
}
@Bean
public RoleAuthFilter roleAuthFilter() {
return new RoleAuthFilter();
}
}zuul:
routes:
user-service-v1:
path: /v1/user/**
serviceId: user-service
strip-prefix: true
user-service-v2:
path: /v2/user/**
serviceId: user-service-v2
strip-prefix: true13. 常见问题解决
问题: 配置了路由但请求无法转发
解决方案:
- 检查路由配置是否正确
- 确认目标服务是否可访问
- 检查过滤器是否阻止了请求(
ctx.setSendZuulResponse(false)) - 查看日志排查问题
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}# Ribbon超时配置
user-service:
ribbon:
ConnectTimeout: 3000
ReadTimeout: 10000
# Hystrix超时配置
hystrix:
command:
user-service:
execution:
isolation:
thread:
timeoutInMilliseconds: 15000zuul:
sensitive-headers: # 设置为空,不过滤任何请求头
routes:
user-service:
path: /user/**
serviceId: user-service
custom-sensitive-headers: true # 不过滤敏感头14. 总结与最佳实践
- ✅ 路由转发:灵活的路由配置
- ✅ 请求过滤:强大的过滤器机制
- ✅ 服务发现:与Eureka无缝集成
- ✅ 负载均衡:集成Ribbon
- ✅ 熔断降级:集成Hystrix
- ✅ 安全认证:统一认证入口
路由配置
- 使用serviceId而不是硬编码URL
- 合理使用strip-prefix
- 配置路由顺序避免冲突
过滤器设计
- Pre过滤器用于认证、参数校验
- Post过滤器用于日志、响应处理
- 合理设置filterOrder
- 避免在过滤器中执行耗时操作
性能优化
- 合理配置超时时间
- 使用连接池
- 避免在过滤器中同步调用外部服务
安全考虑
- 统一认证入口
- 敏感信息过滤
- 限流保护
- 请求日志记录
监控告警
- 集成Actuator监控
- 记录关键指标
- 设置告警规则
- ⚠️ Zuul已进入维护模式,新项目建议使用Spring Cloud Gateway
- ⚠️ Spring Boot 3.x不支持Zuul,需要使用Gateway
- ⚠️ 过滤器执行顺序很重要
- ⚠️ 注意线程安全和性能问题
如果考虑迁移到Spring Cloud Gateway:
- 功能对比:Gateway功能更强大,性能更好
- 迁移步骤:
- 评估现有Zuul配置
- 转换为Gateway配置
- 重写自定义过滤器
- 测试验证
- 学习资源:参考Spring Cloud Gateway官方文档
结语
Zuul作为微服务架构中的重要组件,提供了强大的API网关功能。通过本教程的学习,相信你已经掌握了Zuul的核心功能和使用方法。
记住:
- 多实践:理论结合实践,多写代码
- 理解原理:理解过滤器机制和路由原理
- 关注性能:注意超时和性能优化
- 考虑迁移:新项目建议使用Spring Cloud Gateway
祝你学习愉快,编程顺利! 🚀
本教程由Java突击队学习社区编写,如有问题欢迎反馈。