引言
在现代微服务架构中,API网关作为系统入口层扮演着至关重要的角色。它不仅负责请求路由,还承担着流量控制、服务熔断、安全认证等核心功能。Spring Cloud Gateway作为Spring Cloud生态中的新一代API网关,凭借其基于Netty的异步非阻塞特性,为构建高性能、高可用的微服务网关提供了强大的技术支持。
本文将深入探讨如何基于Spring Cloud Gateway构建一个集限流、熔断、认证于一体的微服务网关架构,通过详细的代码示例和最佳实践,帮助开发者打造稳定可靠的微服务入口层。
Spring Cloud Gateway核心概念
什么是Spring Cloud Gateway
Spring Cloud Gateway是Spring Cloud生态中的API网关组件,它基于Spring Boot 2.x和Spring WebFlux构建,采用响应式编程模型。与传统的Zuul相比,Gateway具有更高的性能和更好的可扩展性。
Gateway的核心特性包括:
- 基于Netty的异步非阻塞IO
- 支持路由匹配、过滤器链处理
- 内置负载均衡、熔断机制
- 灵活的路由配置和动态刷新
核心组件架构
# Gateway核心组件关系图
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Route │───▶│ Predicate │───▶│ Filter │
└─────────────┘ └─────────────┘ └─────────────┘
▲ ▲ ▲
│ │ │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Gateway │◀───│ Router │◀───│ Gateway │
│ Server │ │ Handler │ │ Filter │
└─────────────┘ └─────────────┘ └─────────────┘
基础环境搭建
项目依赖配置
<?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.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>gateway-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway-service</name>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Cloud LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Spring Cloud Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT处理 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- Redis用于限流 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- Actuator监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</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>
</project>
配置文件设置
# application.yml
server:
port: 8080
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
# 用户服务路由
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- name: RateLimiter
args:
key-resolver: "#{@userKeyResolver}"
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burst: 20
# 订单服务路由
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- name: CircuitBreaker
args:
name: order-service-circuit-breaker
# 商品服务路由
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/product/**
filters:
- name: RateLimiter
args:
key-resolver: "#{@productKeyResolver}"
redis-rate-limiter.replenishRate: 5
redis-rate-limiter.burst: 10
# 全局过滤器
global-filters:
- name: AuthFilter
args:
skip-urls: /api/auth/login,/api/auth/register
- name: CircuitBreaker
args:
name: global-circuit-breaker
# 默认超时时间
httpclient:
connect-timeout: 1000
response-timeout: 5000
redis:
host: localhost
port: 6379
timeout: 2000ms
# 安全配置
jwt:
secret: mySecretKey1234567890
expiration: 86400000
management:
endpoints:
web:
exposure:
include: health,info,route,metrics
endpoint:
health:
show-details: always
路由配置与管理
基于YAML的路由配置
spring:
cloud:
gateway:
routes:
# 用户服务路由 - 包含认证和限流
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
- Method=GET,POST,PUT,DELETE
filters:
- name: AuthFilter
- name: RateLimiter
args:
key-resolver: "#{@userKeyResolver}"
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burst: 20
- name: CircuitBreaker
args:
name: user-service-circuit-breaker
fallbackUri: forward:/fallback/user
# 订单服务路由 - 包含熔断和认证
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- name: AuthFilter
- name: CircuitBreaker
args:
name: order-service-circuit-breaker
fallbackUri: forward:/fallback/order
# 商品服务路由 - 包含限流和熔断
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/product/**
filters:
- name: RateLimiter
args:
key-resolver: "#{@productKeyResolver}"
redis-rate-limiter.replenishRate: 5
redis-rate-limiter.burst: 10
- name: CircuitBreaker
args:
name: product-service-circuit-breaker
动态路由配置
@Component
public class DynamicRouteService {
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private RouteRefreshable routeRefreshable;
/**
* 添加动态路由
*/
public void addRoute(RouteDefinition routeDefinition) {
try {
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
routeRefreshable.refresh();
} catch (Exception e) {
log.error("添加路由失败", e);
}
}
/**
* 删除动态路由
*/
public void deleteRoute(String id) {
try {
routeDefinitionWriter.delete(Mono.just(id)).subscribe();
routeRefreshable.refresh();
} catch (Exception e) {
log.error("删除路由失败", e);
}
}
}
限流机制实现
Redis限流器核心实现
@Component
public class RateLimiterConfig {
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
ServerHttpRequest request = exchange.getRequest();
String userId = getUserIdFromRequest(request);
if (userId == null) {
userId = "anonymous";
}
return Mono.just(userId);
};
}
@Bean
public KeyResolver productKeyResolver() {
return exchange -> {
ServerHttpRequest request = exchange.getRequest();
String productId = getProductFromRequest(request);
if (productId == null) {
productId = "default";
}
return Mono.just(productId);
};
}
private String getUserIdFromRequest(ServerHttpRequest request) {
// 从请求头或参数中提取用户ID
String userId = request.getHeaders().getFirst("X-User-ID");
if (userId == null) {
userId = request.getQueryParams().getFirst("userId");
}
return userId;
}
private String getProductFromRequest(ServerHttpRequest request) {
// 从路径参数中提取商品ID
String path = request.getPath().toString();
if (path.contains("/api/product/")) {
String[] parts = path.split("/");
if (parts.length >= 5) {
return parts[4];
}
}
return null;
}
}
自定义限流过滤器
@Component
public class RateLimitFilter implements GatewayFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(RateLimitFilter.class);
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Value("${rate-limiter.redis.prefix:rate_limit}")
private String prefix;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 获取限流配置
RateLimitConfig config = getRateLimitConfig(request);
if (config == null) {
return chain.filter(exchange);
}
String key = generateKey(request, config);
return checkAndApplyRateLimit(key, config, exchange, chain);
}
private Mono<Void> checkAndApplyRateLimit(String key, RateLimitConfig config,
ServerWebExchange exchange, GatewayFilterChain chain) {
String redisKey = prefix + ":" + key;
// 使用Redis Lua脚本实现原子性限流
String script = "local key = KEYS[1] " +
"local limit = tonumber(ARGV[1]) " +
"local replenishRate = tonumber(ARGV[2]) " +
"local burst = tonumber(ARGV[3]) " +
"local now = tonumber(ARGV[4]) " +
"local lastRefillTime = redis.call('HGET', key, 'lastRefillTime') " +
"local tokens = redis.call('HGET', key, 'tokens') " +
"if not lastRefillTime then " +
" redis.call('HSET', key, 'lastRefillTime', now) " +
" redis.call('HSET', key, 'tokens', replenishRate) " +
" tokens = replenishRate " +
"else " +
" local timePassed = now - lastRefillTime " +
" local newTokens = math.min(replenishRate, tokens + (timePassed * replenishRate)) " +
" if newTokens > burst then " +
" newTokens = burst " +
" end " +
" redis.call('HSET', key, 'tokens', newTokens) " +
" redis.call('HSET', key, 'lastRefillTime', now) " +
" tokens = newTokens " +
"end " +
"if tokens >= 1 then " +
" redis.call('HSET', key, 'tokens', tokens - 1) " +
" return 1 " +
"else " +
" return 0 " +
"end";
try {
List<Object> result = (List<Object>) redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Arrays.asList(redisKey),
String.valueOf(config.getLimit()),
String.valueOf(config.getReplenishRate()),
String.valueOf(config.getBurst()),
String.valueOf(System.currentTimeMillis())
);
if (result != null && result.get(0).equals(1L)) {
return chain.filter(exchange);
} else {
// 限流拒绝
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().add("Retry-After", "1");
return response.setComplete();
}
} catch (Exception e) {
log.error("限流检查失败", e);
return chain.filter(exchange);
}
}
private RateLimitConfig getRateLimitConfig(ServerHttpRequest request) {
// 根据请求路径获取对应的限流配置
String path = request.getPath().toString();
if (path.startsWith("/api/user")) {
return new RateLimitConfig(10, 20, 5);
} else if (path.startsWith("/api/product")) {
return new RateLimitConfig(5, 10, 3);
}
return null;
}
private String generateKey(ServerHttpRequest request, RateLimitConfig config) {
String path = request.getPath().toString();
String method = request.getMethodValue();
String userAgent = request.getHeaders().getFirst("User-Agent");
return DigestUtils.md5DigestAsHex((path + method + userAgent).getBytes());
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100;
}
}
限流配置类
public class RateLimitConfig {
private int replenishRate; // 每秒补充令牌数
private int burst; // 桶容量
private int limit; // 单位时间内的请求限制
public RateLimitConfig() {}
public RateLimitConfig(int replenishRate, int burst, int limit) {
this.replenishRate = replenishRate;
this.burst = burst;
this.limit = limit;
}
// getter和setter方法
public int getReplenishRate() { return replenishRate; }
public void setReplenishRate(int replenishRate) { this.replenishRate = replenishRate; }
public int getBurst() { return burst; }
public void setBurst(int burst) { this.burst = burst; }
public int getLimit() { return limit; }
public void setLimit(int limit) { this.limit = limit; }
}
服务熔断机制
Hystrix熔断器集成
@Component
public class CircuitBreakerConfig {
@Bean
public ReactorLoadBalancer<ReactiveServiceInstance> reactorLoadBalancer(
Environment environment,
ServiceInstanceListSupplier serviceInstanceListSupplier) {
String name = environment.getProperty("spring.cloud.loadbalancer.configurations", "zone-aware");
return new RoundRobinLoadBalancer(serviceInstanceListSupplier, name);
}
@Bean
public Customizer<ReactiveResilience4JCircuitBreakerFactory> circuitBreakerCustomizer() {
return factory -> {
factory.configureDefault(id -> new CircuitBreakerConfigBuilder()
.circuitBreakerConfig(CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30))
.slidingWindowSize(100)
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
.permittedNumberOfCallsInHalfOpenState(10)
.build())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(10))
.build())
.build());
};
}
}
自定义熔断过滤器
@Component
public class CircuitBreakerFilter implements GatewayFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(CircuitBreakerFilter.class);
@Autowired
private ReactiveResilience4JCircuitBreakerFactory circuitBreakerFactory;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String routeId = getRouteId(exchange);
if (routeId == null || routeId.isEmpty()) {
return chain.filter(exchange);
}
CircuitBreaker circuitBreaker = circuitBreakerFactory.create(routeId);
// 创建熔断器包装的响应
return Mono.fromCallable(() -> {
try {
return chain.filter(exchange).then();
} catch (Exception e) {
log.error("服务调用异常", e);
throw new RuntimeException(e);
}
})
.transformDeferred(circuitBreaker::run)
.onErrorResume(throwable -> {
log.warn("熔断器触发: {}", throwable.getMessage());
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
return response.setComplete();
});
}
private String getRouteId(ServerWebExchange exchange) {
Route route = exchange.getAttribute(GatewayFilterChain.class.getName() + ".route");
return route != null ? route.getId() : null;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 50;
}
}
熔断降级处理
@RestController
@RequestMapping("/fallback")
public class FallbackController {
private static final Logger log = LoggerFactory.getLogger(FallbackController.class);
@GetMapping("/user")
public ResponseEntity<String> userFallback() {
log.warn("用户服务熔断降级");
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("用户服务暂时不可用,请稍后重试");
}
@GetMapping("/order")
public ResponseEntity<String> orderFallback() {
log.warn("订单服务熔断降级");
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("订单服务暂时不可用,请稍后重试");
}
@GetMapping("/product")
public ResponseEntity<String> productFallback() {
log.warn("商品服务熔断降级");
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("商品服务暂时不可用,请稍后重试");
}
}
统一认证与授权
JWT认证过滤器实现
@Component
public class AuthFilter implements GatewayFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
@Value("${auth.skip-urls:}")
private List<String> skipUrls;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 跳过认证的路径
if (shouldSkipAuthentication(request)) {
return chain.filter(exchange);
}
// 获取JWT Token
String token = getTokenFromRequest(request);
if (token == null || token.isEmpty()) {
return unauthorizedResponse(response);
}
// 验证Token
try {
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
// 检查Token是否过期
if (claims.getExpiration().before(new Date())) {
return unauthorizedResponse(response);
}
// 验证Token是否被撤销
String redisKey = "jwt:blacklist:" + token;
Boolean isBlacklisted = redisTemplate.hasKey(redisKey);
if (Boolean.TRUE.equals(isBlacklisted)) {
return unauthorizedResponse(response);
}
// 将用户信息添加到请求头
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-User-ID", claims.getSubject())
.header("X-User-Roles", (String) claims.get("roles"))
.build();
ServerWebExchange mutatedExchange = exchange.mutate()
.request(mutatedRequest)
.build();
return chain.filter(mutatedExchange);
} catch (Exception e) {
log.error("Token验证失败", e);
return unauthorizedResponse(response);
}
}
private String getTokenFromRequest(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
private boolean shouldSkipAuthentication(ServerHttpRequest request) {
String path = request.getPath().toString();
return skipUrls.stream()
.anyMatch(skipUrl -> path.startsWith(skipUrl));
}
private Mono<Void> unauthorizedResponse(ServerHttpResponse response) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String body = "{\"code\":401,\"message\":\"未授权访问\"}";
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 200;
}
}
JWT工具类
@Component
public class JwtUtil {
private static final Logger log = LoggerFactory.getLogger(JwtUtil.class);
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
/**
* 生成JWT Token
*/
public String generateToken(UserDetails userDetails) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.claim("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 验证JWT Token
*/
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
} catch (Exception e) {
log.error("Token验证失败", e);
return false;
}
}
/**
* 从Token中获取用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* 获取Token过期时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
/**
* 撤销Token(加入黑名单)
*/
public void revokeToken(String token, long timeout) {
String redisKey = "jwt:blacklist:" + token;
redisTemplate.opsForValue().set(redisKey, "revoked", timeout, TimeUnit.MILLISECONDS);
}
}
认证服务接口
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private static final Logger log = LoggerFactory.getLogger(AuthController.class);
@Autowired
private UserService userService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
try {
User user = userService.authenticate(request.getUsername(), request.getPassword());
if (user != null) {
String token = jwtUtil.generateToken(new UserPrincipal(user));
return ResponseEntity.ok(new AuthResponse(token, "Bearer"));
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body("用户名或密码错误");
}
} catch (Exception e) {
log.error("登录失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("登录异常");
}
}
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest request) {
try {
User user = userService.register(request.getUsername(), request.getPassword());
String token = jwtUtil.generateToken(new UserPrincipal(user));
return ResponseEntity.ok(new AuthResponse(token, "Bearer"));
} catch (Exception e) {
log.error("注册失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("注册异常");
}
}
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletRequest request) {
String token = getTokenFromRequest(request);
if (token != null) {
jwtUtil.revokeToken(token, 3600000); // 1小时
}
return ResponseEntity.ok("退出成功");
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
// 请求参数类
public class LoginRequest {
private String username;
private String password;
// getter和setter方法
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
public class AuthResponse {
private String
评论 (0)