Zuul
Zuul是Netflix开源的边缘服务网关,作为Spring Cloud Netflix组件的重要组成部分,为微服务架构提供动态路由、监控、弹性、安全等边缘服务功能。
核心特性
动态路由
- 路由匹配 - 基于请求路径、方法、头部等信息进行路由
- 动态配置 - 支持运行时动态修改路由配置
- 服务发现 - 与Eureka、Consul等注册中心集成
- 负载均衡 - 集成Ribbon实现客户端负载均衡
过滤器机制
- Pre过滤器 - 请求路由前执行,用于认证、记录等
- Routing过滤器 - 处理请求路由到后端服务
- Post过滤器 - 响应返回客户端前执行,用于统计、修改响应等
- Error过滤器 - 处理请求过程中发生的错误
监控与弹性
- 请求监控 - 集成Hystrix实现请求监控和统计
- 熔断机制 - 支持断路器模式防止级联故障
- 超时控制 - 配置请求超时和重试机制
- 限流控制 - 支持多种限流策略
架构设计
核心组件
点击查看完整代码实现
Zuul架构:
├── Zuul Gateway
│ ├── HTTP监听器
│ ├── 过滤器引擎
│ ├── 路由引擎
│ └── 请求分发器
├── 过滤器链
│ ├── Pre Filters
│ ├── Route Filters
│ ├── Post Filters
│ └── Error Filters
├── 路由配置
│ ├── 静态配置
│ ├── 动态配置
│ └── 服务发现
└── 集成组件
├── Ribbon(负载均衡)
├── Hystrix(熔断)
├── Eureka(服务发现)
└── Archaius(配置管理)请求处理流程
Zuul请求处理流程:
1. 接收客户端请求
2. 执行Pre过滤器
3. 路由匹配和选择
4. 执行Route过滤器
5. 调用后端服务
6. 执行Post过滤器
7. 返回响应给客户端
8. 异常时执行Error过滤器快速开始
Maven依赖
点击查看完整代码实现
xml
<dependencies>
<!-- 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>
<!-- 断路器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 配置刷新 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>基础配置
启动类
java
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@EnableHystrix
public class ZuulGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApplication.class, args);
}
}application.yml配置
点击查看完整代码实现
yaml
server:
port: 8080
spring:
application:
name: zuul-gateway
# Eureka配置
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
# Zuul路由配置
zuul:
# 路由配置
routes:
# 用户服务路由
user-service:
path: /api/users/**
service-id: user-service
strip-prefix: true
sensitive-headers: Cookie,Set-Cookie,Authorization
# 订单服务路由
order-service:
path: /api/orders/**
service-id: order-service
strip-prefix: true
# 静态路由(不使用服务发现)
external-api:
path: /external/**
url: http://external-api.example.com
strip-prefix: true
# 正则表达式路由
regex-route:
path: /api/v1/**
service-id: api-v1-service
strip-prefix: false
# 全局配置
prefix: /gateway
strip-prefix: false
# 忽略的服务
ignored-services: admin-service, internal-service
# 忽略的路径
ignored-patterns: /**/admin/**, /**/internal/**
# 敏感头部
sensitive-headers: Cookie,Set-Cookie,Authorization
# 重试配置
retryable: true
# 超时配置
host:
socket-timeout-millis: 60000
connect-timeout-millis: 60000
# Ribbon配置
ribbon:
ReadTimeout: 60000
ConnectTimeout: 60000
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 1
# Hystrix配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 60000过滤器开发
Pre过滤器
认证过滤器
点击查看完整代码实现
java
@Component
public class AuthFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// 排除不需要认证的路径
String path = request.getRequestURI();
return !path.startsWith("/api/auth/") && !path.startsWith("/api/public/");
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
logger.warn("Authorization header is missing");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("{\"error\":\"Authorization header required\"}");
return null;
}
// 验证token
if (!validateToken(token)) {
logger.warn("Invalid authorization token");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("{\"error\":\"Invalid authorization token\"}");
return null;
}
// 添加用户信息到请求头
String userId = extractUserId(token);
ctx.addZuulRequestHeader("X-User-Id", userId);
ctx.addZuulRequestHeader("X-User-Role", extractUserRole(token));
logger.info("Authentication successful for user: {}", userId);
return null;
}
private boolean validateToken(String token) {
// 实现token验证逻辑
return token.startsWith("Bearer ") && token.length() > 20;
}
private String extractUserId(String token) {
// 从token中提取用户ID
return "user123";
}
private String extractUserRole(String token) {
// 从token中提取用户角色
return "USER";
}
}请求日志过滤器
点击查看完整代码实现
java
@Component
public class AccessLogFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(AccessLogFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0; // 最先执行
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String requestId = UUID.randomUUID().toString();
ctx.set("requestId", requestId);
ctx.set("startTime", System.currentTimeMillis());
logger.info("Gateway Request [{}]: {} {} from {} {}",
requestId,
request.getMethod(),
request.getRequestURI(),
request.getRemoteAddr(),
request.getHeader("User-Agent"));
return null;
}
}Post过滤器
响应日志过滤器
点击查看完整代码实现
java
@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 1000; // 最后执行
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String requestId = (String) ctx.get("requestId");
Long startTime = (Long) ctx.get("startTime");
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
logger.info("Gateway Response [{}]: {} {} - {} in {}ms",
requestId,
request.getMethod(),
request.getRequestURI(),
ctx.getResponseStatusCode(),
duration);
}
return null;
}
}响应头过滤器
点击查看完整代码实现
java
@Component
public class ResponseHeaderFilter extends ZuulFilter {
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 999;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
// 添加CORS头部
ctx.getResponse().addHeader("Access-Control-Allow-Origin", "*");
ctx.getResponse().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
ctx.getResponse().addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
// 添加安全头部
ctx.getResponse().addHeader("X-Frame-Options", "DENY");
ctx.getResponse().addHeader("X-Content-Type-Options", "nosniff");
ctx.getResponse().addHeader("X-XSS-Protection", "1; mode=block");
// 添加自定义头部
ctx.getResponse().addHeader("X-Gateway", "Zuul");
ctx.getResponse().addHeader("X-Response-Time", String.valueOf(System.currentTimeMillis()));
return null;
}
}Route过滤器
自定义路由过滤器
点击查看完整代码实现
java
@Component
public class CustomRoutingFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(CustomRoutingFilter.class);
@Override
public String filterType() {
return "route";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return "/api/custom".equals(ctx.getRequest().getRequestURI());
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
try {
// 自定义路由逻辑
String response = handleCustomRequest(ctx.getRequest());
ctx.setResponseStatusCode(200);
ctx.setResponseBody(response);
ctx.setSendZuulResponse(false);
} catch (Exception e) {
logger.error("Custom routing error", e);
ctx.setResponseStatusCode(500);
ctx.setResponseBody("{\"error\":\"Internal server error\"}");
ctx.setSendZuulResponse(false);
}
return null;
}
private String handleCustomRequest(HttpServletRequest request) {
// 实现自定义处理逻辑
return "{\"message\":\"Custom response\", \"timestamp\":\"" + Instant.now() + "\"}";
}
}Error过滤器
错误处理过滤器
点击查看完整代码实现
java
@Component
public class ErrorFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return -1; // 最先执行的error过滤器
}
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().containsKey("error.status_code");
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable throwable = ctx.getThrowable();
logger.error("Gateway error: ", throwable);
try {
int statusCode = (Integer) ctx.get("error.status_code");
String errorMessage = getErrorMessage(statusCode, throwable);
ctx.setResponseStatusCode(statusCode);
ctx.setResponseBody(errorMessage);
ctx.getResponse().setContentType("application/json;charset=UTF-8");
} catch (Exception e) {
logger.error("Error in error filter", e);
}
return null;
}
private String getErrorMessage(int statusCode, Throwable throwable) {
Map<String, Object> errorInfo = new HashMap<>();
errorInfo.put("timestamp", Instant.now().toString());
errorInfo.put("status", statusCode);
errorInfo.put("error", getErrorType(statusCode));
errorInfo.put("message", getErrorDescription(statusCode, throwable));
try {
return new ObjectMapper().writeValueAsString(errorInfo);
} catch (Exception e) {
return "{\"error\":\"Internal server error\"}";
}
}
private String getErrorType(int statusCode) {
switch (statusCode) {
case 404: return "Not Found";
case 500: return "Internal Server Error";
case 503: return "Service Unavailable";
default: return "Unknown Error";
}
}
private String getErrorDescription(int statusCode, Throwable throwable) {
if (throwable != null) {
return throwable.getMessage();
}
switch (statusCode) {
case 404: return "The requested resource was not found";
case 500: return "An internal server error occurred";
case 503: return "Service is temporarily unavailable";
default: return "An unknown error occurred";
}
}
}高级功能
限流控制
点击查看完整代码实现
java
@Component
public class RateLimitFilter extends ZuulFilter {
private final RedisTemplate<String, String> redisTemplate;
private final Map<String, Integer> rateLimits = new HashMap<>();
public RateLimitFilter(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
// 配置不同路径的限流策略
rateLimits.put("/api/users", 100); // 每分钟100次
rateLimits.put("/api/orders", 50); // 每分钟50次
}
@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);
String path = getMatchingPath(request.getRequestURI());
if (path != null && isRateLimited(clientId, path)) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(429);
ctx.setResponseBody("{\"error\":\"Rate limit exceeded\"}");
return null;
}
return null;
}
private String getClientId(HttpServletRequest request) {
String userId = request.getHeader("X-User-Id");
return userId != null ? userId : request.getRemoteAddr();
}
private String getMatchingPath(String requestURI) {
for (String path : rateLimits.keySet()) {
if (requestURI.startsWith(path)) {
return path;
}
}
return null;
}
private boolean isRateLimited(String clientId, String path) {
String key = "rate_limit:" + path + ":" + clientId;
String windowKey = key + ":" + (System.currentTimeMillis() / 60000); // 1分钟窗口
try {
Long requests = redisTemplate.opsForValue().increment(windowKey);
if (requests == 1) {
redisTemplate.expire(windowKey, Duration.ofMinutes(1));
}
return requests > rateLimits.get(path);
} catch (Exception e) {
// Redis故障时不限流
return false;
}
}
}灰度发布
点击查看完整代码实现
java
@Component
public class GrayReleaseFilter extends ZuulFilter {
@Value("${gray.release.ratio:0.1}")
private double grayRatio;
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 5;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// 检查是否强制指定版本
String forceVersion = request.getHeader("X-Force-Version");
if (forceVersion != null) {
ctx.set("serviceVersion", forceVersion);
return null;
}
// 基于用户ID进行灰度
String userId = request.getHeader("X-User-Id");
if (userId != null && shouldUseGrayVersion(userId)) {
ctx.set("serviceVersion", "v2");
} else {
ctx.set("serviceVersion", "v1");
}
return null;
}
private boolean shouldUseGrayVersion(String userId) {
int hash = Math.abs(userId.hashCode());
return (hash % 100) < (grayRatio * 100);
}
}自定义路由规则
点击查看完整代码实现
java
@Component
public class CustomRouteLocator implements RefreshableRouteLocator {
private final DiscoveryClient discoveryClient;
private final ZuulProperties properties;
public CustomRouteLocator(DiscoveryClient discoveryClient, ZuulProperties properties) {
this.discoveryClient = discoveryClient;
this.properties = properties;
}
@Override
public Collection<String> getIgnoredPaths() {
return properties.getIgnoredPatterns();
}
@Override
public List<Route> getRoutes() {
List<Route> routes = new ArrayList<>();
// 从配置文件加载静态路由
if (properties.getRoutes() != null) {
for (Map.Entry<String, ZuulRoute> entry : properties.getRoutes().entrySet()) {
ZuulRoute zuulRoute = entry.getValue();
String path = zuulRoute.getPath();
String serviceId = zuulRoute.getServiceId();
String url = zuulRoute.getUrl();
Route route = new Route(entry.getKey(), path,
url != null ? url : serviceId,
zuulRoute.getStripPrefix(),
zuulRoute.getRetryable(),
zuulRoute.getSensitiveHeaders());
routes.add(route);
}
}
// 动态发现服务并生成路由
List<String> services = discoveryClient.getServices();
for (String serviceId : services) {
if (!properties.getIgnoredServices().contains(serviceId)) {
String path = "/" + serviceId + "/**";
Route route = new Route(serviceId, path, serviceId, true, null, null);
routes.add(route);
}
}
return routes;
}
@Override
public void refresh() {
// 刷新路由配置
}
}监控与管理
Actuator端点
yaml
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
routes:
enabled: true
filters:
enabled: truebash
# 查看路由信息
GET /actuator/routes
# 查看过滤器信息
GET /actuator/filters
# 刷新路由配置
POST /actuator/refresh自定义健康检查
点击查看完整代码实现
java
@Component
public class ZuulHealthIndicator implements HealthIndicator {
private final DiscoveryClient discoveryClient;
public ZuulHealthIndicator(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
@Override
public Health health() {
try {
List<String> services = discoveryClient.getServices();
Map<String, Object> details = new HashMap<>();
details.put("availableServices", services.size());
details.put("services", services);
return Health.up()
.withDetails(details)
.build();
} catch (Exception e) {
return Health.down()
.withException(e)
.build();
}
}
}指标收集
点击查看完整代码实现
java
@Component
public class MetricsFilter extends ZuulFilter {
private final MeterRegistry meterRegistry;
public MetricsFilter(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@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();
// 记录请求计数
Counter.builder("gateway.requests")
.tag("method", request.getMethod())
.tag("uri", request.getRequestURI())
.tag("status", String.valueOf(ctx.getResponseStatusCode()))
.register(meterRegistry)
.increment();
// 记录响应时间
Long startTime = (Long) ctx.get("startTime");
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
Timer.builder("gateway.response.time")
.tag("uri", request.getRequestURI())
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
}
return null;
}
}最佳实践
性能优化
- 合理配置Hystrix超时时间
- 使用Ribbon配置连接池
- 启用响应压缩
- 监控内存和CPU使用情况
安全配置
- 实现统一的认证授权
- 过滤敏感请求头
- 配置CORS策略
- 实现请求限流
高可用部署
- 部署多个Zuul实例
- 使用负载均衡器
- 配置健康检查
- 实现优雅关闭
运维管理
- 建立完善的监控体系
- 实现日志集中管理
- 配置告警机制
- 制定故障处理流程
Zuul作为Netflix OSS的重要组件,在Spring Cloud生态中发挥着重要作用,虽然性能相比新一代网关有所不足,但其成熟稳定的特性和丰富的功能仍使其在许多企业级应用中得到广泛使用。
