前言
微服务架构下,每个服务都有自己的地址,客户端调用起来很麻烦
而且认证、限流、日志这些横切关注点,每个服务都要写一遍
所以需要网关来统一处理
之前用Zuul,现在SpringCloud Gateway更主流,所以学习了一下
Gateway简介
SpringCloudGateway是基于Spring5、SpringBoot2和WebFlux的网关
特点:
- 响应式编程
- 动态路由
- 内置断言和过滤器
- 集成发现客户端
基础配置
添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>
|
配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| server: port: 8080
spring: application: name: gateway-service
cloud: nacos: discovery: server-addr: 192.168.1.100:8848
gateway: discovery: locator: enabled: true lower-case-service-id: true
routes: - id: user-service uri: lb://user-service predicates: - Path=/api/user/** filters: - StripPrefix=2
- id: order-service uri: lb://order-service predicates: - Path=/api/order/** filters: - StripPrefix=2
globalcors: cors-configurations: '[/**]': allowed-origins: "*" allowed-methods: - GET - POST - PUT - DELETE - OPTIONS allowed-headers: "*" allow-credentials: true
|
路由断言
时间断言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| spring: cloud: gateway: routes: - id: after-route uri: lb://user-service predicates: - After=2025-01-01T00:00:00+08:00[Asia/Shanghai]
- id: before-route uri: lb://user-service predicates: - Before=2025-12-31T23:59:59+08:00[Asia/Shanghai]
- id: between-route uri: lb://user-service predicates: - Between=2025-01-01T00:00:00+08:00[Asia/Shanghai], 2025-12-31T23:59:59+08:00[Asia/Shanghai]
|
Cookie断言
1 2 3 4 5 6 7 8
| spring: cloud: gateway: routes: - id: cookie-route uri: lb://user-service predicates: - Cookie=token, my-token
|
1 2 3 4 5 6 7 8
| spring: cloud: gateway: routes: - id: header-route uri: lb://user-service predicates: - Header=X-Request-Id, \d+
|
Host断言
1 2 3 4 5 6 7 8
| spring: cloud: gateway: routes: - id: host-route uri: lb://user-service predicates: - Host=**.somehost.org,**.anotherhost.org
|
Method断言
1 2 3 4 5 6 7 8
| spring: cloud: gateway: routes: - id: method-route uri: lb://user-service predicates: - Method=GET,POST
|
自定义断言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Component public class CheckAuthPredicateFactory extends AbstractRoutePredicateFactory<CheckAuthPredicateFactory.Config> {
public CheckAuthPredicateFactory() { super(Config.class); }
@Override public Predicate<ServerWebExchange> apply(Config config) { return exchange -> { String token = exchange.getRequest().getHeaders().getFirst("Authorization"); return token != null && token.startsWith("Bearer "); }; }
public static class Config { } }
|
使用:
1 2 3 4 5 6 7 8
| spring: cloud: gateway: routes: - id: custom-route uri: lb://user-service predicates: - CheckAuth
|
过滤器
内置过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| spring: cloud: gateway: routes: - id: add-header-route uri: lb://user-service predicates: - Path=/api/user/** filters: - AddRequestHeader=X-Request-Id, 12345
- id: add-param-route uri: lb://user-service predicates: - Path=/api/user/** filters: - AddRequestParameter=param, value
- id: strip-prefix-route uri: lb://user-service predicates: - Path=/api/user/** filters: - StripPrefix=2
- id: rewrite-path-route uri: lb://user-service predicates: - Path=/api/user/** filters: - RewritePath=/api/user/(?<segment>.*), /$\{segment}
- id: rate-limit-route uri: lb://user-service predicates: - Path=/api/user/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20
|
全局过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| @Component public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getHeaders().getFirst("Authorization");
String path = exchange.getRequest().getPath().value(); if (isWhitelist(path)) { return chain.filter(exchange); }
if (token == null || !validateToken(token)) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); }
ServerHttpRequest request = exchange.getRequest().mutate() .header("X-User-Id", getUserIdFromToken(token)) .build();
return chain.filter(exchange.mutate().request(request).build()); }
@Override public int getOrder() { return -100; }
private boolean isWhitelist(String path) { return path.startsWith("/api/public/") || path.equals("/api/user/login"); }
private boolean validateToken(String token) { return true; }
private String getUserIdFromToken(String token) { return "123456"; } }
|
日志过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Component public class LoggingGlobalFilter implements GlobalFilter, Ordered {
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); long startTime = System.currentTimeMillis();
String requestId = UUID.randomUUID().toString(); ServerHttpRequest mutatedRequest = request.mutate() .header("X-Request-Id", requestId) .build();
return chain.filter(exchange.mutate().request(mutatedRequest).build()) .then(Mono.fromRunnable(() -> { ServerHttpResponse response = exchange.getResponse(); long duration = System.currentTimeMillis() - startTime;
log.info("请求日志 - " + "RequestID: {}, " + "Method: {}, " + "Path: {}, " + "Status: {}, " + "Duration: {}ms", requestId, request.getMethod(), request.getPath(), response.getStatusCode(), duration ); })); }
@Override public int getOrder() { return -1; } }
|
统一认证授权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| @Component public class AuthenticationFilter implements GlobalFilter, Ordered {
@Autowired private RedisTemplate<String, String> redisTemplate;
private static final String AUTHORIZATION_HEADER = "Authorization";
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path = exchange.getRequest().getPath().value();
if (isWhitelist(path)) { return chain.filter(exchange); }
String token = exchange.getRequest().getHeaders().getFirst(AUTHORIZATION_HEADER); if (StringUtils.isEmpty(token) || !token.startsWith("Bearer ")) { return unauthorized(exchange); }
token = token.substring(7);
String userId = redisTemplate.opsForValue().get("token:" + token); if (StringUtils.isEmpty(userId)) { return unauthorized(exchange); }
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate() .header("X-User-Id", userId) .build();
return chain.filter(exchange.mutate().request(mutatedRequest).build()); }
private Mono<Void> unauthorized(ServerWebExchange exchange) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = "{\"code\":401,\"msg\":\"未认证\"}"; DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return exchange.getResponse().writeWith(Mono.just(buffer)); }
private boolean isWhitelist(String path) { List<String> whitelist = Arrays.asList( "/api/user/login", "/api/user/register", "/api/public/**" );
return whitelist.stream().anyMatch(pattern -> { if (pattern.endsWith("/**")) { return path.startsWith(pattern.substring(0, pattern.length() - 3)); } return path.equals(pattern); }); }
@Override public int getOrder() { return -200; } }
|
限流
基于Redis的限流
添加依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
|
配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| spring: cloud: gateway: routes: - id: rate-limit-route uri: lb://user-service predicates: - Path=/api/user/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 key-resolver: "#{@ipKeyResolver}"
|
键解析器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Configuration public class RateLimiterConfig {
@Bean public KeyResolver ipKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() ); }
@Bean public KeyResolver userKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getPath().value() ); }
@Bean public KeyResolver apiKeyResolver() { return exchange -> { String apiKey = exchange.getRequest().getHeaders().getFirst("X-API-Key"); return Mono.just(apiKey != null ? apiKey : "anonymous"); }; } }
|
熔断降级
添加依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId> </dependency>
|
配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| spring: cloud: gateway: routes: - id: circuit-breaker-route uri: lb://user-service predicates: - Path=/api/user/** filters: - name: CircuitBreaker args: name: myCircuitBreaker fallbackUri: forward:/fallback
resilience4j: circuitbreaker: configs: default: sliding-window-size: 10 failure-rate-threshold: 50 wait-duration-in-open-state: 10000 permitted-number-of-calls-in-half-open-state: 3 instances: myCircuitBreaker: base-config: default
|
降级处理:
1 2 3 4 5 6 7 8 9 10 11
| @RestController public class FallbackController {
@GetMapping("/fallback") public Map<String, Object> fallback() { Map<String, Object> result = new HashMap<>(); result.put("code", 500); result.put("msg", "服务暂时不可用,请稍后重试"); return result; } }
|
动态路由
从数据库加载路由配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Component public class DynamicRouteService {
@Autowired private RouteDefinitionWriter routeDefinitionWriter;
@Autowired private RouteDefinitionLocator routeDefinitionLocator;
public void updateRoutes(List<RouteDefinition> definitions) { routeDefinitionLocator.getRouteDefinitions() .subscribe(route -> { routeDefinitionWriter.delete(Mono.just(route.getId())); });
definitions.forEach(definition -> { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); }); }
public void addRoute(RouteDefinition definition) { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); }
public void deleteRoute(String routeId) { routeDefinitionWriter.delete(Mono.just(routeId)).subscribe(); } }
|
监控
集成Actuator:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
|
配置:
1 2 3 4 5
| management: endpoints: web: exposure: include: gateway
|
访问路由信息:
1
| curl http://localhost:8080/actuator/gateway/routes
|
总结
SpringCloudGateway功能强大,主要优点:
关键点:
- 合理配置路由和断言
- 实现统一认证
- 做好限流和熔断
- 完善日志和监控
- 动态路由便于管理
暂时就先记录这么多