微服务网关Gateway实战笔记

前言

微服务架构下,每个服务都有自己的地址,客户端调用起来很麻烦

而且认证、限流、日志这些横切关注点,每个服务都要写一遍

所以需要网关来统一处理

之前用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

# 全局CORS配置
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

Header断言

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);
}

// 验证token
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) {
// token验证逻辑
return true;
}

private String getUserIdFromToken(String token) {
// 从token获取用户ID
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);
}

// 获取token
String token = exchange.getRequest().getHeaders().getFirst(AUTHORIZATION_HEADER);
if (StringUtils.isEmpty(token) || !token.startsWith("Bearer ")) {
return unauthorized(exchange);
}

token = token.substring(7);

// 验证token
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功能强大,主要优点:

  • 响应式编程,性能好
  • 配置灵活
  • 易于扩展

关键点:

  1. 合理配置路由和断言
  2. 实现统一认证
  3. 做好限流和熔断
  4. 完善日志和监控
  5. 动态路由便于管理

暂时就先记录这么多