一、Gateway 介绍
1. 为什么要使用 Gateway?(重点)
vue
|
gateway------->问题:①ip和port硬编码 ②无法负载均衡
|
|-微服务1-| |-微服务2-| |-微服务3-|... ...
|-微服务1-|
|-微服务1-|
.
.
.
解决的问题:
-
解耦前端与后端:前端无需知道后端服务的具体地址
-
动态路由:通过服务名实现路由,无需硬编码 IP 和端口
-
负载均衡:内置 Ribbon 实现客户端负载均衡
-
统一入口:为所有微服务提供单一入口点
-
安全控制:统一认证、授权、限流等
2. 什么是 Gateway?
Gateway 是 Spring Cloud 官方基于 WebFlux(Reactor 响应式编程模型)开发的网关组件,用于替代 Zuul。
主要功能:
-
路由:将请求路由到对应的微服务
-
过滤:对请求和响应进行预处理和后处理
-
限流:防止系统被过多请求压垮
-
熔断:服务降级和熔断保护
3. Gateway 的启动器
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
注意: 不要添加 spring-boot-starter-web依赖,因为 Gateway 基于 WebFlux 而非 Servlet。
二、Gateway 工程搭建
1. 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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
</properties>
<dependencies>
<!-- Gateway 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 配置管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 健康检查 -->
<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>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2. application.yml 配置
server:
port: 8888 # Gateway 服务端口
spring:
application:
name: api-gateway # 服务名称
cloud:
nacos:
discovery:
server-addr: 192.168.61.132:8848 # Nacos 服务地址
namespace: public
group: DEFAULT_GROUP
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yaml
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
lower-case-service-id: true # 服务名小写
# 全局跨域配置
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-headers: "*"
allowed-methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allow-credentials: true
max-age: 3600
# 路由配置
routes:
- id: sentinel-consumer
uri: lb://sentinel-consumer
predicates:
- Path=/consumer/**
filters:
- StripPrefix=1
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- StripPrefix=1
# 日志配置
logging:
level:
org.springframework.cloud.gateway: DEBUG
reactor.netty.http.client: DEBUG
# 健康检查端点
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
3. 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApp {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApp.class, args);
}
}
三、路由配置
1. 内置断言工厂
Gateway 提供了多种内置的断言工厂,用于匹配 HTTP 请求的不同属性:
spring:
cloud:
gateway:
routes:
- id: sentinel-consumer
uri: lb://sentinel-consumer # 从 Nacos 获取服务列表
predicates: # 断言:只有所有条件都为 true 时才进行路由
- Path=/consumer/** # 路径匹配
- After=2022-04-09T13:20:54.957+08:00[Asia/Shanghai] # 时间之后
- Before=2023-12-31T23:59:59.999+08:00[Asia/Shanghai] # 时间之前
- Between=2022-01-01T00:00:00.000+08:00[Asia/Shanghai],2022-12-31T23:59:59.999+08:00[Asia/Shanghai]
- Cookie=sessionId, test # Cookie 匹配
- Header=X-Request-Id, \d+ # 请求头匹配
- Host=**.example.com # 主机名匹配
- Method=GET,POST # 请求方法匹配
- Query=name, zhangsan # 查询参数匹配
- RemoteAddr=192.168.1.1/24 # 远程地址匹配
2. 自定义断言工厂
2.1 创建断言工厂
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
@Component
public class AgeRoutePredicateFactory
extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
public AgeRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("minAge", "maxAge");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// 从请求头获取年龄
String ageStr = exchange.getRequest().getHeaders().getFirst("Age");
if (ageStr == null) {
return false;
}
try {
int age = Integer.parseInt(ageStr);
return age >= config.minAge && age <= config.maxAge;
} catch (NumberFormatException e) {
return false;
}
};
}
@Validated
public static class Config {
@NotNull
private Integer minAge;
@NotNull
private Integer maxAge;
public Integer getMinAge() {
return minAge;
}
public void setMinAge(Integer minAge) {
this.minAge = minAge;
}
public Integer getMaxAge() {
return maxAge;
}
public void setMaxAge(Integer maxAge) {
this.maxAge = maxAge;
}
}
}
2.2 使用自定义断言工厂
spring:
cloud:
gateway:
routes:
- id: age-restricted-route
uri: lb://user-service
predicates:
- Path=/api/users/**
- Age=18,60 # 使用自定义断言工厂,年龄在18-60之间
四、过滤器配置
1. 内置过滤器工厂
Gateway 提供了丰富的内置过滤器:
spring:
cloud:
gateway:
routes:
- id: sentinel-consumer
uri: lb://sentinel-consumer
predicates:
- Path=/sentinel-consumer/**
filters:
- StripPrefix=1 # 去除路径前缀
- AddRequestHeader=X-Request-color, blue # 添加请求头
- AddRequestParameter=color, blue # 添加请求参数
- AddResponseHeader=X-Response-color, blue # 添加响应头
- PrefixPath=/api # 添加路径前缀
- SetPath=/api/{segment} # 设置路径
- SetStatus=401 # 设置状态码
- RedirectTo=302, https://example.com # 重定向
- RemoveRequestHeader=X-Request-Foo # 移除请求头
- RemoveResponseHeader=X-Response-Foo # 移除响应头
- RewritePath=/red/(?<segment>.*), /$\{segment} # 重写路径
- Retry=3 # 重试3次
- RequestSize=5000000 # 请求大小限制(5MB)
- RequestRateLimiter=#{@myRateLimiter} # 限流
2. 自定义过滤器工厂
2.1 创建过滤器工厂
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Component
public class LogGatewayFilterFactory
extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
public LogGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("enabled");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
if (config.isEnabled()) {
ServerHttpRequest request = exchange.getRequest();
// 记录请求信息
System.out.println("======================================");
System.out.println("请求路径: " + request.getURI().getPath());
System.out.println("请求方法: " + request.getMethod());
System.out.println("请求参数: " + request.getQueryParams());
System.out.println("请求头: " + request.getHeaders());
System.out.println("======================================");
// 在请求头中添加日志标识
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-Log-Enabled", "true")
.build();
return chain.filter(exchange.mutate().request(modifiedRequest).build())
.then(Mono.fromRunnable(() -> {
// 记录响应信息
System.out.println("响应状态码: " +
exchange.getResponse().getStatusCode());
}));
}
return chain.filter(exchange);
};
}
public static class Config {
private boolean enabled = false;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}
2.2 使用自定义过滤器工厂
spring:
cloud:
gateway:
routes:
- id: logging-route
uri: lb://sentinel-consumer
predicates:
- Path=/sentinel-consumer/**
filters:
- StripPrefix=1
- Log=true # 启用日志过滤器
3. 自定义全局过滤器
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
@Component
public class GlobalLoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 获取请求路径
String path = request.getURI().getPath();
// 排除登录接口和公开接口
if (path.contains("/auth/login") ||
path.contains("/auth/register") ||
path.contains("/public/")) {
return chain.filter(exchange);
}
// 从请求头中获取token
String token = request.getHeaders().getFirst("Authorization");
if (!StringUtils.hasText(token)) {
// 从查询参数中获取token
token = request.getQueryParams().getFirst("token");
}
// token为空,返回未授权
if (!StringUtils.hasText(token)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String message = "{\"code\":401,\"message\":\"未授权访问,请先登录\"}";
DataBuffer buffer = response.bufferFactory()
.wrap(message.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
// 验证token(这里简化处理,实际应该调用认证服务)
if (!isValidToken(token)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String message = "{\"code\":401,\"message\":\"Token无效或已过期\"}";
DataBuffer buffer = response.bufferFactory()
.wrap(message.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
// 将用户信息添加到请求头中
ServerHttpRequest newRequest = request.mutate()
.header("X-User-Id", extractUserIdFromToken(token))
.header("X-User-Name", extractUsernameFromToken(token))
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
}
@Override
public int getOrder() {
return 0; // 执行顺序,值越小优先级越高
}
private boolean isValidToken(String token) {
// 这里应该调用认证服务验证token
// 简化处理,假设以"Bearer "开头的token为有效
return token.startsWith("Bearer ");
}
private String extractUserIdFromToken(String token) {
// 从token中提取用户ID
// 简化处理,返回固定值
return "123";
}
private String extractUsernameFromToken(String token) {
// 从token中提取用户名
// 简化处理,返回固定值
return "admin";
}
}
五、高级配置
1. 统一异常处理
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public GlobalErrorWebExceptionHandler(ErrorAttributes errorAttributes,
WebProperties.Resources resources,
ApplicationContext applicationContext,
ServerCodecConfigurer configurer) {
super(errorAttributes, resources, applicationContext);
this.setMessageWriters(configurer.getWriters());
this.setMessageReaders(configurer.getReaders());
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> errorPropertiesMap = getErrorAttributes(request, false);
int status = (int) errorPropertiesMap.getOrDefault("status", 500);
String message = (String) errorPropertiesMap.getOrDefault("message", "Internal Server Error");
String path = (String) errorPropertiesMap.get("path");
Map<String, Object> result = new HashMap<>();
result.put("code", status);
result.put("message", message);
result.put("path", path);
result.put("timestamp", System.currentTimeMillis());
return ServerResponse
.status(HttpStatus.valueOf(status))
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(result));
}
}
2. 限流配置
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
@Configuration
public class RateLimiterConfig {
/**
* 根据IP进行限流
*/
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
/**
* 根据用户ID进行限流
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("X-User-Id")
);
}
/**
* 根据接口进行限流
*/
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getURI().getPath()
);
}
}
3. 配置文件 application-gateway.yml
spring:
cloud:
gateway:
# 默认过滤器配置
default-filters:
- AddRequestHeader=X-Request-Gateway, api-gateway
- AddResponseHeader=X-Response-Gateway, api-gateway
# 路由配置
routes:
# 用户服务路由
- id: user-service
uri: lb://user-service
predicates:
- Path=/user-service/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 10 # 每秒令牌数
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
# 订单服务路由
- id: order-service
uri: lb://order-service
predicates:
- Path=/order-service/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback/order-service
# 商品服务路由
- id: product-service
uri: lb://product-service
predicates:
- Path=/product-service/**
filters:
- StripPrefix=1
# 认证服务路由
- id: auth-service
uri: lb://auth-service
predicates:
- Path=/auth/**
filters:
- StripPrefix=0
# 熔断降级配置
hystrix:
command:
fallbackcmd:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
# 重试配置
retry:
retries: 3
series: SERVER_ERROR
methods: GET,POST
exceptions: java.io.IOException, java.util.concurrent.TimeoutException
六、监控和日志
1. 集成 Spring Boot Admin
<!-- 在pom.xml中添加 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.7.0</version>
</dependency>
2. 配置监控端点
management:
endpoints:
web:
exposure:
include: "*"
cors:
allowed-origins: "*"
allowed-methods: GET,POST
endpoint:
health:
show-details: always
gateway:
enabled: true
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
3. 日志配置
logging:
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
level:
root: INFO
org.springframework.cloud.gateway: DEBUG
org.springframework.web: DEBUG
reactor.netty: DEBUG
file:
name: logs/gateway.log
max-size: 10MB
max-history: 30
七、最佳实践
1. 生产环境建议
-
使用配置中心(Nacos Config)管理路由配置
-
启用熔断和降级机制
-
配置合理的限流策略
-
开启请求日志和监控
-
使用HTTPS确保通信安全
-
配置合理的超时时间
2. 性能优化
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s
pool:
type: elastic
max-idle-time: 60s
max-life-time: 60s
metrics:
enabled: true
3. 安全配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/auth/**", "/public/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.and()
.build();
}
}
总结
Spring Cloud Gateway 作为微服务架构的入口,提供了强大的路由、过滤、限流等功能。通过合理的配置和使用,可以构建出高性能、高可用的API网关。在实际项目中,需要根据具体业务需求选择合适的配置方案,并注意监控和安全防护。
这份笔记涵盖了Gateway的核心概念、配置方法和最佳实践,适合在CSDN等技术平台分享。