引言:为什么每个微服务架构都需要 Spring Cloud Gateway?
在微服务架构盛行的今天,当系统被拆分为数十甚至上百个微服务时,一个新的挑战出现了:如何有效地管理这些服务的入口?如何处理认证授权、流量控制、日志监控等横切关注点?如何让客户端能够简单地与众多微服务交互而无需了解其具体位置?
Spring Cloud Gateway 应运而生,作为 Spring Cloud 生态系统中的网关组件,它不仅解决了上述问题,还提供了更强大的功能和更好的性能。相比之前的 Zuul 网关,Spring Cloud Gateway 基于 ***ty 和 WebFlux 构建,采用非阻塞响应式编程模型,性能提升显著,同时提供了更丰富的功能和更灵活的配置方式。
本文将带你全面深入地了解 Spring Cloud Gateway,从核心概念到底层原理,从环境搭建到实战应用,让你真正掌握这一微服务架构的 "门户"。无论你是刚接触微服务的新手,还是寻求进阶的资深开发者,都能从本文中获益。
一、Spring Cloud Gateway 核心概念与架构解析
1.1 什么是 Spring Cloud Gateway?
Spring Cloud Gateway 是 Spring 官方推出的基于 Spring 5、Spring Boot 2 和 Project Reactor 的网关解决方案,旨在为微服务架构提供一种简单而有效的统一入口点。它提供了路由转发、负载均衡、熔断、限流、安全认证等一系列功能,是构建微服务架构不可或缺的组件。
Spring Cloud Gateway 的核心目标是:
- 提供统一的路由方式
- 基于 Filter 链的方式提供网关的基本功能
- 集成 Spring 生态系统,与 Spring Boot 2.x、Spring Cloud 等无缝集成
- 支持动态路由配置
- 提供异步非阻塞的高性能体验
1.2 核心概念
在使用 Spring Cloud Gateway 之前,我们需要理解几个核心概念:
-
路由(Route):路由是网关的基本构建块,它由 ID、目标 URI、一组断言(Predicate)和一组过滤器(Filter)组成。当断言为真时,路由匹配成功,请求将被转发到目标 URI。
-
断言(Predicate):这是一个 Java 8 的 Function Predicate,用于匹配 HTTP 请求的各种属性,如路径、方法、请求头、参数等。所有断言都必须为真,路由才会被匹配。
-
过滤器(Filter):过滤器是在请求被路由之前或之后执行的操作,可以修改请求或响应。Spring Cloud Gateway 提供了两种类型的过滤器:GatewayFilter(针对特定路由)和 GlobalFilter(针对所有路由)。
-
网关(Gateway):由一系列路由、断言和过滤器组成,负责接收所有客户端请求,根据断言进行路由匹配,应用相应的过滤器,最后将请求转发到目标服务。
1.3 架构设计
Spring Cloud Gateway 的架构设计充分利用了响应式编程的优势,基于 ***ty 和 WebFlux 构建,实现了非阻塞的请求处理。其核心架构如图所示:
核心组件说明:
-
DispatcherHandler:作为请求的入口点,负责将请求分发到相应的处理器。
-
RoutePredicateHandlerMapping:根据路由断言查找匹配的路由。
-
FilterWebHandler:负责执行路由关联的过滤器链,并最终将请求转发到目标服务。
-
***tyRoutingFilter:基于 ***ty 的 HTTP 客户端,负责将请求转发到目标服务并接收响应。
-
过滤器链:由多个过滤器组成,分为 "前置" 和 "后置" 阶段,分别在请求转发前和响应返回后执行。
1.4 工作原理
Spring Cloud Gateway 的工作流程可以概括为以下几个步骤:
- 客户端发送请求到 Spring Cloud Gateway。
- Gateway 接收请求后,由 DispatcherHandler 将请求转发给 RoutePredicateHandlerMapping。
- RoutePredicateHandlerMapping 根据请求的属性(如路径、方法等)匹配最合适的路由。
- 如果找到匹配的路由,请求将被发送到 FilterWebHandler。
- FilterWebHandler 创建一个包含所有相关过滤器的过滤器链,并按顺序执行 "前置" 过滤器。
- 过滤器链执行完成后,请求被转发到目标服务。
- 目标服务处理请求并返回响应。
- 响应返回后,FilterWebHandler 执行所有 "后置" 过滤器。
- 最后,响应被返回给客户端。
如果没有找到匹配的路由,Gateway 将返回 404 Not Found 响应。
二、Spring Cloud Gateway 环境搭建与入门示例
2.1 准备工作
在开始使用 Spring Cloud Gateway 之前,我们需要准备以下环境:
- JDK 17+(推荐使用 JDK 17)
- Maven 3.6+
- Spring Boot 3.2.0+
- Spring Cloud 2023.0.0+
2.2 创建基础网关项目
2.2.1 创建 Maven 项目,添加依赖
<?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>3.2.0</version>
<relativePath/>
</parent>
<groupId>***.example</groupId>
<artifactId>spring-cloud-gateway-demo</artifactId>
<version>1.0.0</version>
<name>spring-cloud-gateway-demo</name>
<description>Spring Cloud Gateway Demo Project</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<optional>true</optional>
</dependency>
<!-- Spring Boot Starter Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Reactor Test -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Swagger3 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud -->
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
注意:Spring Cloud Gateway 依赖于 Spring WebFlux,而不是传统的 Spring Web MVC。因此,在网关项目中不应添加 spring-boot-starter-web 依赖,否则会导致冲突。
2.2.2 创建启动类
package ***.example.gateway;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Cloud Gateway示例应用启动类
*
* @author ken
*/
@SpringBootApplication
@OpenAPIDefinition(
info = @Info(
title = "Spring Cloud Gateway示例API",
version = "1.0.0",
description = "Spring Cloud Gateway功能演示API文档"
)
)
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
2.3 创建测试用的微服务
为了演示网关的功能,我们需要创建两个简单的微服务:用户服务和订单服务。
2.3.1 用户服务
1. 创建用户服务的 Maven 依赖
<!-- 仅列出核心依赖,其他基础依赖与网关项目类似 -->
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<optional>true</optional>
</dependency>
<!-- Swagger3 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
2. 创建用户实体类
package ***.example.userservice.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 用户实体类
*
* @author ken
*/
@Data
@Schema(description = "用户实体")
public class User {
/**
* 用户ID
*/
@Schema(description = "用户ID")
private Long id;
/**
* 用户名
*/
@Schema(description = "用户名")
private String username;
/**
* 用户年龄
*/
@Schema(description = "用户年龄")
private Integer age;
/**
* 用户邮箱
*/
@Schema(description = "用户邮箱")
private String email;
}
3. 创建用户服务控制器
package ***.example.userservice.controller;
import ***.example.userservice.entity.User;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 用户服务控制器
*
* @author ken
*/
@RestController
@RequestMapping("/users")
@Slf4j
@Tag(name = "用户服务接口", description = "提供用户相关操作的接口")
public class UserController {
/**
* 模拟数据库存储用户信息
*/
private static final Map<Long, User> USER_MAP = new ConcurrentHashMap<>();
/**
* 当前服务端口
*/
@Value("${server.port}")
private String serverPort;
static {
// 初始化测试数据
User user1 = new User();
user1.setId(1L);
user1.setUsername("张三");
user1.setAge(25);
user1.setEmail("zhangsan@example.***");
USER_MAP.put(1L, user1);
User user2 = new User();
user2.setId(2L);
user2.setUsername("李四");
user2.setAge(30);
user2.setEmail("lisi@example.***");
USER_MAP.put(2L, user2);
}
/**
* 根据ID查询用户
*
* @param id 用户ID
* @return 用户信息
*/
@GetMapping("/{id}")
@Operation(summary = "根据ID查询用户", description = "根据用户ID查询用户详细信息")
public User getUserById(
@Parameter(description = "用户ID", required = true)
@PathVariable Long id) {
log.info("查询用户信息,ID: {}, 服务端口: {}", id, serverPort);
return USER_MAP.get(id);
}
/**
* 创建用户
*
* @param user 用户信息
* @return 创建的用户信息
*/
@PostMapping
@Operation(summary = "创建用户", description = "创建新用户并返回用户信息")
public User createUser(
@Parameter(description = "用户信息", required = true)
@RequestBody User user) {
log.info("创建用户,用户信息: {}, 服务端口: {}", user, serverPort);
USER_MAP.put(user.getId(), user);
return user;
}
/**
* 获取服务信息
*
* @return 服务信息
*/
@GetMapping("/service-info")
@Operation(summary = "获取服务信息", description = "获取当前服务的信息")
public Map<String, String> getServiceInfo() {
Map<String, String> info = new HashMap<>(2);
info.put("serviceName", "user-service");
info.put("serverPort", serverPort);
log.info("获取服务信息: {}", info);
return info;
}
}
4. 创建用户服务启动类
package ***.example.userservice;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 用户服务启动类
*
* @author ken
*/
@SpringBootApplication
@OpenAPIDefinition(
info = @Info(
title = "用户服务API",
version = "1.0.0",
description = "用户服务的API文档"
)
)
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
5. 创建配置文件 application.properties
# 服务端口
server.port=8081
# 应用名称
spring.application.name=user-service
2.3.2 订单服务
订单服务的结构与用户服务类似,这里只列出核心代码:
1. 订单实体类
package ***.example.orderservice.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 订单实体类
*
* @author ken
*/
@Data
@Schema(description = "订单实体")
public class Order {
/**
* 订单ID
*/
@Schema(description = "订单ID")
private Long id;
/**
* 用户ID
*/
@Schema(description = "用户ID")
private Long userId;
/**
* 商品名称
*/
@Schema(description = "商品名称")
private String productName;
/**
* 订单金额
*/
@Schema(description = "订单金额")
private Double amount;
}
2. 订单服务控制器
package ***.example.orderservice.controller;
import ***.example.orderservice.entity.Order;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 订单服务控制器
*
* @author ken
*/
@RestController
@RequestMapping("/orders")
@Slf4j
@Tag(name = "订单服务接口", description = "提供订单相关操作的接口")
public class OrderController {
/**
* 模拟数据库存储订单信息
*/
private static final Map<Long, Order> ORDER_MAP = new ConcurrentHashMap<>();
/**
* 订单ID生成器
*/
private static final AtomicLong ORDER_ID_GENERATOR = new AtomicLong(1000);
/**
* 当前服务端口
*/
@Value("${server.port}")
private String serverPort;
/**
* 根据ID查询订单
*
* @param id 订单ID
* @return 订单信息
*/
@GetMapping("/{id}")
@Operation(summary = "根据ID查询订单", description = "根据订单ID查询订单详细信息")
public Order getOrderById(
@Parameter(description = "订单ID", required = true)
@PathVariable Long id) {
log.info("查询订单信息,ID: {}, 服务端口: {}", id, serverPort);
return ORDER_MAP.get(id);
}
/**
* 创建订单
*
* @param order 订单信息
* @return 创建的订单信息
*/
@PostMapping
@Operation(summary = "创建订单", description = "创建新订单并返回订单信息")
public Order createOrder(
@Parameter(description = "订单信息", required = true)
@RequestBody Order order) {
long orderId = ORDER_ID_GENERATOR.incrementAndGet();
order.setId(orderId);
ORDER_MAP.put(orderId, order);
log.info("创建订单,订单信息: {}, 服务端口: {}", order, serverPort);
return order;
}
/**
* 获取服务信息
*
* @return 服务信息
*/
@GetMapping("/service-info")
@Operation(summary = "获取服务信息", description = "获取当前服务的信息")
public Map<String, String> getServiceInfo() {
Map<String, String> info = new HashMap<>(2);
info.put("serviceName", "order-service");
info.put("serverPort", serverPort);
log.info("获取服务信息: {}", info);
return info;
}
}
3. 配置文件 application.properties
# 服务端口
server.port=8082
# 应用名称
spring.application.name=order-service
2.4 配置基础路由
现在我们已经有了两个微服务,接下来配置 Spring Cloud Gateway,实现请求的路由转发。
2.4.1 使用配置文件配置路由
在网关项目的 src/main/resources 目录下创建 application.yml 文件:
server:
port: 8080 # 网关端口
spring:
cloud:
gateway:
routes:
# 用户服务路由
- id: user-service-route
uri: http://localhost:8081 # 用户服务地址
predicates:
- Path=/api/users/** # 匹配路径
filters:
- StripPrefix=1 # 移除路径中的第一个前缀(即/api)
# 订单服务路由
- id: order-service-route
uri: http://localhost:8082 # 订单服务地址
predicates:
- Path=/api/orders/** # 匹配路径
filters:
- StripPrefix=1 # 移除路径中的第一个前缀(即/api)
2.4.2 测试路由功能
- 启动用户服务(端口 8081)
- 启动订单服务(端口 8082)
- 启动网关服务(端口 8080)
- 测试路由功能:
- 访问 http://localhost:8080/api/users/1,应该返回 ID 为 1 的用户信息
- 访问 http://localhost:8080/api/orders/service-info,应该返回订单服务的信息
如果一切正常,说明网关已经成功将请求转发到对应的微服务。
2.4.3 使用 Java 代码配置路由
除了使用配置文件,我们还可以使用 Java 代码配置路由:
package ***.example.gateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 网关路由配置类
*
* @author ken
*/
@Configuration
public class GatewayRouteConfig {
/**
* 配置路由规则
*
* @param builder 路由构建器
* @return 路由定位器
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 用户服务路由
.route("user-service-route", r -> r
.path("/api/users/**")
.filters(f -> f.stripPrefix(1))
.uri("http://localhost:8081"))
// 订单服务路由
.route("order-service-route", r -> r
.path("/api/orders/**")
.filters(f -> f.stripPrefix(1))
.uri("http://localhost:8082"))
.build();
}
}
注意:如果同时存在配置文件和 Java 代码配置的路由,两者都会生效。在实际项目中,建议根据需求选择一种配置方式,或结合使用。
三、Spring Cloud Gateway 核心功能详解
3.1 路由断言(Predicate)详解
路由断言用于判断请求是否匹配某个路由,Spring Cloud Gateway 提供了多种内置的断言工厂,可以满足大部分场景需求。
3.1.1 路径断言(Path Predicate)
路径断言根据请求路径进行匹配,是最常用的断言之一。
spring:
cloud:
gateway:
routes:
- id: path-predicate-example
uri: http://localhost:8081
predicates:
- Path=/users/**,/api/users/** # 匹配多个路径
3.1.2 方法断言(Method Predicate)
方法断言根据 HTTP 请求方法进行匹配。
spring:
cloud:
gateway:
routes:
- id: method-predicate-example
uri: http://localhost:8081
predicates:
- Path=/users/**
- Method=GET,POST # 只匹配GET和POST方法
3.1.3 请求头断言(Header Predicate)
请求头断言根据请求头信息进行匹配。
spring:
cloud:
gateway:
routes:
- id: header-predicate-example
uri: http://localhost:8081
predicates:
- Path=/users/**
- Header=X-Request-Id, \d+ # 匹配X-Request-Id头,值为数字
3.1.4 请求参数断言(Query Predicate)
请求参数断言根据请求参数进行匹配。
spring:
cloud:
gateway:
routes:
- id: query-predicate-example
uri: http://localhost:8081
predicates:
- Path=/users/**
- Query=name # 匹配包含name参数的请求
# - Query=name, ^[a-zA-Z]+$ # 匹配name参数且值为字母的请求
3.1.5 时间断言(Time Predicate)
时间断言根据时间进行匹配,有三种类型:
- After:在指定时间之后
- Before:在指定时间之前
- Between:在两个时间之间
spring:
cloud:
gateway:
routes:
- id: time-predicate-example
uri: http://localhost:8081
predicates:
- Path=/users/**
# 在2023年1月1日之后
- After=2023-01-01T00:00:00+08:00[Asia/Shanghai]
# 在2024年1月1日之前
# - Before=2024-01-01T00:00:00+08:00[Asia/Shanghai]
# 在两个时间之间
# - Between=2023-01-01T00:00:00+08:00[Asia/Shanghai], 2024-01-01T00:00:00+08:00[Asia/Shanghai]
3.1.6 远程地址断言(RemoteAddr Predicate)
远程地址断言根据客户端 IP 地址进行匹配。
spring:
cloud:
gateway:
routes:
- id: remoteaddr-predicate-example
uri: http://localhost:8081
predicates:
- Path=/users/**
- RemoteAddr=192.168.1.0/24,10.0.0.0/8 # 匹配指定网段的IP
3.1.7 组合断言
一个路由可以配置多个断言,只有当所有断言都匹配时,路由才会被选中。
spring:
cloud:
gateway:
routes:
- id: ***posite-predicate-example
uri: http://localhost:8081
predicates:
- Path=/users/**
- Method=GET
- Header=A***ept, application/json
- Query=format, json
3.2 过滤器(Filter)详解
过滤器是 Spring Cloud Gateway 的另一个核心组件,用于在请求被路由前后修改请求或响应。Spring Cloud Gateway 提供了丰富的内置过滤器,同时也支持自定义过滤器。
3.2.1 过滤器的类型
Spring Cloud Gateway 的过滤器分为两种类型:
- GatewayFilter:应用于特定路由的过滤器,需要在路由配置中显式声明。
- GlobalFilter:应用于所有路由的全局过滤器,不需要在路由中配置,会自动生效。
此外,过滤器还可以根据执行时机分为:
- 前置过滤器:在请求被路由到目标服务之前执行
- 后置过滤器:在目标服务返回响应之后执行
3.2.2 常用内置 GatewayFilter
- AddRequestHeader:添加请求头
spring:
cloud:
gateway:
routes:
- id: add-request-header-filter-example
uri: http://localhost:8081
predicates:
- Path=/users/**
filters:
- AddRequestHeader=X-Request-From, gateway # 添加请求头
- StripPrefix=1
- AddResponseHeader:添加响应头
spring:
cloud:
gateway:
routes:
- id: add-response-header-filter-example
uri: http://localhost:8081
predicates:
- Path=/users/**
filters:
- AddResponseHeader=X-Response-From, gateway # 添加响应头
- StripPrefix=1
- RewritePath:重写路径
spring:
cloud:
gateway:
routes:
- id: rewrite-path-filter-example
uri: http://localhost:8081
predicates:
- Path=/v1/**
filters:
- RewritePath=/v1/(?<segment>.*), /users/$\{segment} # 重写路径,将/v1/xxx重写为/users/xxx
- PrefixPath:添加路径前缀
spring:
cloud:
gateway:
routes:
- id: prefix-path-filter-example
uri: http://localhost:8081
predicates:
- Path=/**
filters:
- PrefixPath=/users # 为所有请求添加/users前缀
- RequestRateLimiter:请求限流
spring:
cloud:
gateway:
routes:
- id: request-rate-limiter-filter-example
uri: http://localhost:8081
predicates:
- Path=/users/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 令牌桶填充速率(每秒)
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
key-resolver: "#{@userKeyResolver}" # 限流键解析器
需要添加 Redis 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
配置限流键解析器:
package ***.example.gateway.config;
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;
/**
* 限流配置
*
* @author ken
*/
@Configuration
public class RateLimiterConfig {
/**
* 用户限流键解析器,基于用户ID限流
*
* @return 限流键解析器
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
// 从请求参数中获取用户ID作为限流键
String userId = exchange.getRequest().getQueryParams().getFirst("userId");
return Mono.justOrEmpty(userId)
.defaultIfEmpty("anonymous"); // 匿名用户
};
}
/**
* IP限流键解析器,基于客户端IP限流
*
* @return 限流键解析器
*/
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
}
- CircuitBreaker:熔断降级
spring:
cloud:
gateway:
routes:
- id: circuit-breaker-filter-example
uri: http://localhost:8081
predicates:
- Path=/users/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: userServiceCircuitBreaker
fallbackUri: forward:/fallback/users # 降级回调地址
需要添加 Resilience4j 依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
创建降级回调控制器:
package ***.example.gateway.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 降级回调控制器
*
* @author ken
*/
@RestController
@RequestMapping("/fallback")
@Slf4j
public class FallbackController {
/**
* 用户服务降级处理
*
* @return 降级响应
*/
@GetMapping("/users/{id}")
public Map<String, Object> userServiceFallback(@PathVariable Long id) {
log.warn("用户服务降级,用户ID: {}", id);
Map<String, Object> result = new HashMap<>(2);
result.put("su***ess", false);
result.put("message", "用户服务暂时不可用,请稍后再试");
result.put("userId", id);
return result;
}
/**
* 用户服务通用降级处理
*
* @return 降级响应
*/
@GetMapping("/users/**")
public Map<String, Object> userServiceGeneralFallback() {
log.warn("用户服务通用降级");
Map<String, Object> result = new HashMap<>(2);
result.put("su***ess", false);
result.put("message", "用户服务暂时不可用,请稍后再试");
return result;
}
}
3.2.3 自定义 GatewayFilter
除了使用内置过滤器,我们还可以自定义 GatewayFilter 以满足特定需求。
package ***.example.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.***ponent;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
/**
* 自定义日志过滤器工厂
*
* @author ken
*/
@***ponent
@Slf4j
public class LoggingGatewayFilterFactory extends AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> {
/**
* 配置类,用于存储过滤器参数
*/
public static class Config {
/**
* 是否记录请求体
*/
private boolean logRequestBody;
/**
* 是否记录响应体
*/
private boolean logResponseBody;
public boolean isLogRequestBody() {
return logRequestBody;
}
public void setLogRequestBody(boolean logRequestBody) {
this.logRequestBody = logRequestBody;
}
public boolean isLogResponseBody() {
return logResponseBody;
}
public void setLogResponseBody(boolean logResponseBody) {
this.logResponseBody = logResponseBody;
}
}
public LoggingGatewayFilterFactory() {
super(Config.class);
}
/**
* 解析配置参数
*
* @return 参数名称列表
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("logRequestBody", "logResponseBody");
}
/**
* 创建过滤器
*
* @param config 过滤器配置
* @return 网关过滤器
*/
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// 前置处理:记录请求信息
log.info("请求路径: {}", exchange.getRequest().getPath());
log.info("请求方法: {}", exchange.getRequest().getMethod());
log.info("请求参数: {}", exchange.getRequest().getQueryParams());
// 如果需要记录请求体
if (config.isLogRequestBody()) {
// 注意:获取请求体需要特殊处理,这里简化处理
log.info("记录请求体: 已开启");
}
// 继续执行过滤器链
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// 后置处理:记录响应信息
log.info("响应状态码: {}", exchange.getResponse().getStatusCode());
// 如果需要记录响应体
if (config.isLogResponseBody()) {
log.info("记录响应体: 已开启");
}
}));
};
}
}
在路由中使用自定义过滤器:
spring:
cloud:
gateway:
routes:
- id: custom-filter-example
uri: http://localhost:8081
predicates:
- Path=/users/**
filters:
- StripPrefix=1
# 使用自定义日志过滤器,记录请求体和响应体
- name: Logging
args:
logRequestBody: true
logResponseBody: true
# 简化写法
# - Logging=true,true
3.2.4 自定义 GlobalFilter
全局过滤器会应用于所有路由,适合实现认证授权、日志记录等全局功能。
package ***.example.gateway.filter;
import ***.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* 全局过滤器配置
*
* @author ken
*/
@Configuration
@Slf4j
public class GlobalFilterConfig {
/**
* 认证过滤器,检查请求中的令牌
*
* @return 全局过滤器
*/
@Bean
@Order(-100) // 优先级,数值越小优先级越高
public GlobalFilter authFilter() {
return (exchange, chain) -> {
// 获取请求路径
String path = exchange.getRequest().getPath().toString();
// 不需要认证的路径
if (path.startsWith("/public/") || path.startsWith("/swagger-ui/") || path.startsWith("/v3/api-docs/")) {
return chain.filter(exchange);
}
// 从请求头中获取令牌
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
// 验证令牌
if (token == null || !token.startsWith("Bearer ")) {
log.warn("未授权访问: {}", path);
// 设置401响应
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 设置响应体
Map<String, Object> response = new HashMap<>(2);
response.put("su***ess", false);
response.put("message", "未授权访问,请先登录");
byte[] bytes = JSON.toJSONString(response).getBytes();
exchange.getResponse().getHeaders().add("Content-Type", "application/json");
return exchange.getResponse().writeWith(Mono.just(
exchange.getResponse().bufferFactory().wrap(bytes)
));
}
// 令牌验证通过,继续执行
log.info("令牌验证通过: {}", path);
return chain.filter(exchange);
};
}
/**
* 全局日志过滤器,记录所有请求的处理时间
*
* @return 全局过滤器
*/
@Bean
@Order(-200)
public GlobalFilter loggingFilter() {
return (exchange, chain) -> {
// 记录开始时间
long startTime = System.currentTimeMillis();
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// 计算处理时间
long duration = System.currentTimeMillis() - startTime;
log.info("请求路径: {}, 方法: {}, 状态码: {}, 处理时间: {}ms",
exchange.getRequest().getPath(),
exchange.getRequest().getMethod(),
exchange.getResponse().getStatusCode(),
duration);
}));
};
}
}
3.3 与服务发现集成
在微服务架构中,服务地址通常是动态变化的,因此需要结合服务发现组件(如 Eureka、Consul、Nacos 等)使用。Spring Cloud Gateway 可以自动从服务发现组件中获取服务实例信息,并实现负载均衡。
3.3.1 与 Nacos 集成
- 添加 Nacos 服务发现依赖:
<dependency>
<groupId>***.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2023.0.0.0</version>
</dependency>
- 配置 Nacos 地址:
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos服务地址
application:
name: api-gateway # 网关服务名称
- 配置基于服务名的路由:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 开启服务发现自动路由
lower-case-service-id: true # 服务名转为小写
routes:
# 用户服务路由,使用服务名
- id: user-service-route
uri: lb://user-service # lb表示使用负载均衡,user-service是服务名
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
# 订单服务路由,使用服务名
- id: order-service-route
uri: lb://order-service # lb表示使用负载均衡,order-service是服务名
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
注意:
lb://service-name中的lb表示启用负载均衡,Spring Cloud Gateway 默认使用 Spring Cloud LoadBalancer 作为负载均衡器。
3.3.2 负载均衡配置
可以自定义负载均衡策略:
package ***.example.gateway.config;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* 负载均衡配置
*
* @author ken
*/
@Configuration
public class LoadBalancerConfig {
/**
* 为用户服务配置随机负载均衡策略
*
* @param environment 环境变量
* @param loadBalancerClientFactory 负载均衡客户端工厂
* @return 随机负载均衡器
*/
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
在启动类上指定负载均衡配置:
@SpringBootApplication
@EnableDiscoveryClient
@LoadBalancerClient(
name = "user-service",
configuration = LoadBalancerConfig.class
)
public class GatewayApplication {
// ...
}
3.4 动态路由配置
在实际生产环境中,路由配置可能需要频繁变更,动态路由功能允许我们在不重启网关的情况下更新路由配置。Spring Cloud Gateway 支持多种动态路由配置方式,如基于数据库、配置中心等。
3.4.1 基于 Nacos 配置中心的动态路由
- 添加 Nacos 配置中心依赖:
<dependency>
<groupId>***.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2023.0.0.0</version>
</dependency>
- 创建 bootstrap.yml 配置文件:
spring:
application:
name: api-gateway
cloud:
nacos:
config:
server-addr: localhost:8848 # Nacos配置中心地址
file-extension: yaml # 配置文件格式
group: GATEWAY_GROUP # 配置分组
- 在 Nacos 控制台创建配置:
- Data ID: api-gateway.yaml
- Group: GATEWAY_GROUP
- 配置内容:
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
- 实现动态路由刷新:
package ***.example.gateway.config;
import ***.alibaba.cloud.nacos.NacosConfigProperties;
import ***.alibaba.fastjson2.JSON;
import ***.alibaba.nacos.api.NacosFactory;
import ***.alibaba.nacos.api.config.ConfigService;
import ***.alibaba.nacos.api.config.listener.Listener;
import ***.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.***ponent;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 基于Nacos的动态路由配置
*
* @author ken
*/
@***ponent
@Slf4j
public class NacosDynamicRouteConfig implements ApplicationEventPublisherAware {
/**
* 路由配置Data ID
*/
private static final String DATA_ID = "api-gateway.yaml";
/**
* 路由配置Group
*/
private static final String GROUP = "GATEWAY_GROUP";
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private NacosConfigProperties nacosConfigProperties;
private ApplicationEventPublisher publisher;
/**
* 初始化时加载路由配置
*/
@PostConstruct
public void init() {
log.info("初始化动态路由配置");
try {
ConfigService configService = NacosFactory.createConfigService(
nacosConfigProperties.getServerAddr());
// 加载当前配置
String configInfo = configService.getConfig(DATA_ID, GROUP, 5000);
this.updateRoutes(configInfo);
// 监听配置变化
configService.addListener(DATA_ID, GROUP, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
log.info("收到路由配置变更: {}", configInfo);
updateRoutes(configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
});
} catch (NacosException e) {
log.error("初始化动态路由失败", e);
}
}
/**
* 更新路由配置
*
* @param configInfo 配置信息
*/
private void updateRoutes(String configInfo) {
try {
// 解析配置
GatewayRouteConfig gatewayRouteConfig = JSON.parseObject(
configInfo, GatewayRouteConfig.class);
// 先清除所有路由
routeDefinitionWriter.delete(Mono.just("*")).block();
// 添加新路由
List<RouteDefinition> routeDefinitions = gatewayRouteConfig.getSpring().getCloud().getGateway().getRoutes();
for (RouteDefinition routeDefinition : routeDefinitions) {
routeDefinitionWriter.save(Mono.just(routeDefinition)).block();
}
// 发布路由刷新事件
publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("路由配置更新成功,共 {} 条路由", routeDefinitions.size());
} catch (Exception e) {
log.error("更新路由配置失败", e);
}
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
/**
* 网关路由配置包装类
*/
public static class GatewayRouteConfig {
private Spring spring;
public Spring getSpring() {
return spring;
}
public void setSpring(Spring spring) {
this.spring = spring;
}
public static class Spring {
private Cloud cloud;
public Cloud getCloud() {
return cloud;
}
public void setCloud(Cloud cloud) {
this.cloud = cloud;
}
public static class Cloud {
private Gateway gateway;
public Gateway getGateway() {
return gateway;
}
public void setGateway(Gateway gateway) {
this.gateway = gateway;
}
public static class Gateway {
private List<RouteDefinition> routes;
public List<RouteDefinition> getRoutes() {
return routes;
}
public void setRoutes(List<RouteDefinition> routes) {
this.routes = routes;
}
}
}
}
}
}
四、Spring Cloud Gateway 高级特性与实战
4.1 跨域资源共享(CORS)配置
在前后端分离的架构中,跨域问题是常见的挑战。Spring Cloud Gateway 可以统一配置 CORS,解决跨域问题。
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有路径
allowed-origins: "*" # 允许所有来源
allowed-methods: # 允许的HTTP方法
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed-headers: "*" # 允许的请求头
allow-credentials: true # 允许携带凭证
max-age: 3600 # 预检请求的有效期(秒)
也可以通过 Java 代码配置:
package ***.example.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
import java.util.Arrays;
/**
* CORS配置
*
* @author ken
*/
@Configuration
public class CorsConfig {
/**
* 配置CORS过滤器
*
* @return CORS过滤器
*/
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}
4.2 集成 Swagger/OpenAPI
在微服务架构中,每个服务通常都有自己的 API 文档。通过 Spring Cloud Gateway 集成 Swagger/OpenAPI,可以实现 API 文档的聚合,方便前端开发者查看和测试所有服务的 API。
4.2.1 添加依赖
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-api</artifactId>
<version>2.3.0</version>
</dependency>
4.2.2 配置 Swagger 资源聚合
package ***.example.gateway.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.Contact;
import org.springdoc.core.models.GroupedOpenApi;
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigParameters;
import org.springdoc.core.properties.SwaggerUiConfigProperties;
import org.springdoc.core.utils.SpringDocUtils;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* Swagger配置
*
* @author ken
*/
@Configuration
public class SwaggerConfig {
/**
* 服务路由定位器
*/
private final RouteDefinitionLocator routeDefinitionLocator;
public SwaggerConfig(RouteDefinitionLocator routeDefinitionLocator) {
this.routeDefinitionLocator = routeDefinitionLocator;
}
/**
* 配置OpenAPI信息
*
* @return OpenAPI配置
*/
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("微服务API文档")
.version("1.0.0")
.description("通过网关聚合的所有微服务API文档")
.contact(new Contact()
.name("API Support")
.email("support@example.***")));
}
/**
* 配置Swagger UI,聚合所有服务的API文档
*
* @return Swagger UI配置参数
*/
@Bean
public SwaggerUiConfigParameters swaggerUiConfigParameters() {
SwaggerUiConfigParameters parameters = new SwaggerUiConfigParameters();
// 获取所有路由定义
List<RouteDefinition> definitions = routeDefinitionLocator.getRouteDefinitions().collectList().block();
if (definitions != null) {
List<AbstractSwaggerUiConfigProperties.SwaggerUrl> urls = new ArrayList<>();
for (RouteDefinition definition : definitions) {
// 跳过网关自身的路由
if (definition.getId().equals("self-service-route")) {
continue;
}
// 服务名作为分组名
String serviceName = definition.getId().replace("-route", "");
// API文档地址
String url = "/"+ serviceName +"/v3/api-docs";
urls.add(new AbstractSwaggerUiConfigProperties.SwaggerUrl(
serviceName, url, serviceName + " API文档"));
}
parameters.setUrls(urls);
}
return parameters;
}
/**
* 配置网关自身的API分组
*
* @return 分组的OpenAPI
*/
@Bean
public GroupedOpenApi gatewayApi() {
return GroupedOpenApi.builder()
.group("gateway")
.pathsToMatch("/public/**", "/fallback/**")
.build();
}
}
4.2.3 添加网关自身的测试接口
package ***.example.gateway.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 网关测试控制器
*
* @author ken
*/
@RestController
@RequestMapping("/public")
@Slf4j
@Tag(name = "网关公共接口", description = "网关自身提供的公共接口")
public class Publi***ontroller {
/**
* 健康检查接口
*
* @return 健康状态
*/
@GetMapping("/health")
@Operation(summary = "健康检查", description = "检查网关是否正常运行")
public Map<String, Object> healthCheck() {
log.info("健康检查");
Map<String, Object> result = new HashMap<>(2);
result.put("status", "UP");
result.put("service", "api-gateway");
return result;
}
/**
* 版本信息接口
*
* @return 版本信息
*/
@GetMapping("/version")
@Operation(summary = "版本信息", description = "获取网关版本信息")
public Map<String, Object> versionInfo() {
log.info("获取版本信息");
Map<String, Object> result = new HashMap<>(2);
result.put("version", "1.0.0");
result.put("timestamp", System.currentTimeMillis());
return result;
}
}
4.2.4 添加网关自身的路由配置
spring:
cloud:
gateway:
routes:
# 网关自身的路由
- id: self-service-route
uri: lb://api-gateway
predicates:
- Path=/public/**,/swagger-ui/**,/v3/api-docs/**
4.2.5 访问聚合 API 文档
启动所有服务后,访问 http://localhost:8080/swagger-ui/index.html 即可看到聚合后的 API 文档,可以在不同服务的 API 文档之间切换。
4.3 限流与熔断降级实战
在高并发场景下,限流和熔断降级是保障系统稳定性的重要手段。Spring Cloud Gateway 结合 Resilience4j 和 Redis 可以实现强大的限流和熔断功能。
4.3.1 基于 Redis 的限流实现
前面已经介绍了基本的限流配置,这里我们实现一个更完善的限流方案,包括不同维度的限流(IP、用户、接口)。
package ***.example.gateway.config;
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;
/**
* 限流配置
*
* @author ken
*/
@Configuration
public class RateLimiterConfig {
/**
* IP限流键解析器,基于客户端IP限流
*
* @return 限流键解析器
*/
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
/**
* 用户限流键解析器,基于用户ID限流
*
* @return 限流键解析器
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
// 从请求头中获取用户ID
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
return Mono.justOrEmpty(userId)
.defaultIfEmpty("anonymous"); // 匿名用户
};
}
/**
* 接口限流键解析器,基于请求路径限流
*
* @return 限流键解析器
*/
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getPath().toString()
);
}
}
在路由中配置不同的限流策略:
spring:
cloud:
gateway:
routes:
# 用户服务路由 - 基于IP限流
- id: user-service-ip-limit
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 5 # 每秒5个请求
redis-rate-limiter.burstCapacity: 10 # 最多10个并发请求
key-resolver: "#{@ipKeyResolver}"
# 订单服务路由 - 基于用户限流
- id: order-service-user-limit
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 2 # 每秒2个请求
redis-rate-limiter.burstCapacity: 5 # 最多5个并发请求
key-resolver: "#{@userKeyResolver}"
4.3.2 熔断降级实战
结合 Resilience4j 实现熔断降级功能,当服务出现故障时,快速失败并返回降级响应。
- 添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
- 配置熔断策略:
resilience4j:
circuitbreaker:
instances:
userServiceCircuitBreaker:
slidingWindowSize: 10 # 滑动窗口大小
failureRateThreshold: 50 # 失败率阈值,超过此值将打开熔断器
waitDurationInOpenState: 10000 # 熔断器打开状态持续时间(毫秒)
permittedNumberOfCallsInHalfOpenState: 3 # 半开状态允许的调用次数
registerHealthIndicator: true # 注册健康指示器
orderServiceCircuitBreaker:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 10000
permittedNumberOfCallsInHalfOpenState: 3
registerHealthIndicator: true
timelimiter:
instances:
userServiceCircuitBreaker:
timeoutDuration: 3000 # 超时时间(毫秒)
orderServiceCircuitBreaker:
timeoutDuration: 3000
- 在路由中配置熔断:
spring:
cloud:
gateway:
routes:
- id: user-service-circuit-breaker
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: userServiceCircuitBreaker
fallbackUri: forward:/fallback/users
- id: order-service-circuit-breaker
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: orderServiceCircuitBreaker
fallbackUri: forward:/fallback/orders
- 实现降级回调控制器:
package ***.example.gateway.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 熔断降级回调控制器
*
* @author ken
*/
@RestController
@RequestMapping("/fallback")
@Slf4j
public class FallbackController {
/**
* 用户服务降级处理
*
* @param id 用户ID
* @return 降级响应
*/
@GetMapping("/users/{id}")
public Map<String, Object> userServiceFallback(@PathVariable(required = false) Long id) {
log.warn("用户服务降级,用户ID: {}", id);
Map<String, Object> result = new HashMap<>(3);
result.put("su***ess", false);
result.put("message", "用户服务暂时不可用,请稍后再试");
if (id != null) {
result.put("userId", id);
}
return result;
}
/**
* 用户服务通用降级处理
*
* @return 降级响应
*/
@GetMapping("/users/**")
public Map<String, Object> userServiceGeneralFallback() {
log.warn("用户服务通用降级");
Map<String, Object> result = new HashMap<>(2);
result.put("su***ess", false);
result.put("message", "用户服务暂时不可用,请稍后再试");
return result;
}
/**
* 订单服务降级处理
*
* @param id 订单ID
* @return 降级响应
*/
@GetMapping("/orders/{id}")
public Map<String, Object> orderServiceFallback(@PathVariable(required = false) Long id) {
log.warn("订单服务降级,订单ID: {}", id);
Map<String, Object> result = new HashMap<>(3);
result.put("su***ess", false);
result.put("message", "订单服务暂时不可用,请稍后再试");
if (id != null) {
result.put("orderId", id);
}
return result;
}
/**
* 订单服务通用降级处理
*
* @return 降级响应
*/
@GetMapping("/orders/**")
public Map<String, Object> orderServiceGeneralFallback() {
log.warn("订单服务通用降级");
Map<String, Object> result = new HashMap<>(2);
result.put("su***ess", false);
result.put("message", "订单服务暂时不可用,请稍后再试");
return result;
}
}
4.4 网关安全认证
网关作为所有请求的入口,是实现统一认证授权的理想位置。我们可以在网关层实现基于 JWT 的认证机制。
4.4.1 添加依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
4.4.2 实现 JWT 工具类
package ***.example.gateway.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.***ponent;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* JWT工具类
*
* @author ken
*/
@***ponent
@Slf4j
public class JwtUtil {
/**
* JWT密钥
*/
@Value("${jwt.secret:defaultSecretKeyMustBeLongEnoughToMeetTheRequirementsOfTheAlgorithm}")
private String secret;
/**
* JWT过期时间(毫秒),默认2小时
*/
@Value("${jwt.expiration:7200000}")
private long expiration;
/**
* 生成JWT令牌
*
* @param username 用户名
* @return JWT令牌
*/
public String generateToken(String username) {
return generateToken(username, new HashMap<>());
}
/**
* 生成带自定义声明的JWT令牌
*
* @param username 用户名
* @param claims 自定义声明
* @return JWT令牌
*/
public String generateToken(String username, Map<String, Object> claims) {
Date now = new Date();
Date expirationDate = new Date(now.getTime() + expiration);
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expirationDate)
.signWith(key, SignatureAlgorithm.HS256)
.***pact();
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* 从令牌中获取过期时间
*
* @param token 令牌
* @return 过期时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
/**
* 从令牌中获取指定的声明
*
* @param token 令牌
* @param claimsResolver 声明解析器
* @param <T> 声明类型
* @return 声明值
*/
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
/**
* 从令牌中获取所有声明
*
* @param token 令牌
* @return 所有声明
*/
private Claims getAllClaimsFromToken(String token) {
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
/**
* 检查令牌是否过期
*
* @param token 令牌
* @return 如果过期返回true,否则返回false
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* 验证令牌
*
* @param token 令牌
* @param username 用户名
* @return 如果验证通过返回true,否则返回false
*/
public Boolean validateToken(String token, String username) {
final String tokenUsername = getUsernameFromToken(token);
return (username.equals(tokenUsername) && !isTokenExpired(token));
}
/**
* 提取令牌中的用户ID
*
* @param token 令牌
* @return 用户ID
*/
public Long getUserIdFromToken(String token) {
Claims claims = getAllClaimsFromToken(token);
Object userIdObj = claims.get("userId");
if (userIdObj != null) {
return Long.parseLong(userIdObj.toString());
}
return null;
}
/**
* 从Authorization头中提取令牌
*
* @param authorizationHeader Authorization头
* @return 令牌,如果提取失败返回null
*/
public String extractTokenFromHeader(String authorizationHeader) {
if (StringUtils.hasText(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {
return authorizationHeader.substring(7);
}
return null;
}
}
4.4.3 实现认证过滤器
package ***.example.gateway.filter;
import ***.alibaba.fastjson2.JSON;
import ***.example.gateway.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* 认证授权过滤器配置
*
* @author ken
*/
@Configuration
@Slf4j
public class AuthFilterConfig {
private final JwtUtil jwtUtil;
public AuthFilterConfig(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
/**
* 认证过滤器,验证请求中的JWT令牌
*
* @return 全局过滤器
*/
@Bean
@Order(-100)
public GlobalFilter authFilter() {
return (exchange, chain) -> {
// 获取请求路径
String path = exchange.getRequest().getPath().toString();
// 不需要认证的路径
if (isPublicPath(path)) {
return chain.filter(exchange);
}
// 从请求头中获取令牌
String token = jwtUtil.extractTokenFromHeader(
exchange.getRequest().getHeaders().getFirst("Authorization"));
// 验证令牌
if (token == null) {
log.warn("未提供令牌,路径: {}", path);
return unauthorizedResponse(exchange, "未提供令牌,请先登录");
}
try {
// 从令牌中获取用户名
String username = jwtUtil.getUsernameFromToken(token);
if (username == null || jwtUtil.isTokenExpired(token)) {
log.warn("令牌无效或已过期,路径: {}", path);
return unauthorizedResponse(exchange, "令牌无效或已过期,请重新登录");
}
// 从令牌中获取用户ID
Long userId = jwtUtil.getUserIdFromToken(token);
// 将用户信息添加到请求头,供下游服务使用
ServerWebExchange mutatedExchange = exchange.getRequest().mutate()
.header("X-User-Name", username)
.header("X-User-Id", userId != null ? userId.toString() : "")
.build()
.exchange();
log.info("用户 {} 认证通过,路径: {}", username, path);
return chain.filter(mutatedExchange);
} catch (Exception e) {
log.warn("令牌验证失败,路径: {}", path, e);
return unauthorizedResponse(exchange, "令牌验证失败,请重新登录");
}
};
}
/**
* 判断路径是否为公开路径(不需要认证)
*
* @param path 请求路径
* @return 如果是公开路径返回true,否则返回false
*/
private boolean isPublicPath(String path) {
return path.startsWith("/public/") ||
path.startsWith("/auth/") ||
path.startsWith("/swagger-ui/") ||
path.startsWith("/v3/api-docs/");
}
/**
* 返回未授权响应
*
* @param exchange 交换对象
* @param message 错误消息
* @return 响应Mono
*/
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
// 设置401响应
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 设置响应体
Map<String, Object> response = new HashMap<>(2);
response.put("su***ess", false);
response.put("message", message);
byte[] bytes = JSON.toJSONString(response).getBytes();
exchange.getResponse().getHeaders().add("Content-Type", "application/json");
return exchange.getResponse().writeWith(Mono.just(
exchange.getResponse().bufferFactory().wrap(bytes)
));
}
}
4.4.5 实现认证接口
package ***.example.gateway.controller;
import ***.example.gateway.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 认证控制器
*
* @author ken
*/
@RestController
@RequestMapping("/auth")
@Slf4j
@Tag(name = "认证接口", description = "提供用户认证和令牌相关操作的接口")
public class AuthController {
@Autowired
private JwtUtil jwtUtil;
/**
* 用户登录,获取令牌
*
* @param username 用户名
* @param password 密码
* @return 包含令牌的响应
*/
@PostMapping("/login")
@Operation(summary = "用户登录", description = "用户登录并获取JWT令牌")
public Map<String, Object> login(
@Parameter(description = "用户名", required = true)
@RequestParam String username,
@Parameter(description = "密码", required = true)
@RequestParam String password) {
log.info("用户登录,用户名: {}", username);
// 这里简化处理,实际项目中应该验证用户名和密码
// 例如查询数据库或调用用户服务进行验证
if (!"password123".equals(password)) {
log.warn("用户登录失败,用户名: {}, 密码错误", username);
Map<String, Object> result = new HashMap<>(2);
result.put("su***ess", false);
result.put("message", "用户名或密码错误");
return result;
}
// 生成用户ID(实际项目中从数据库获取)
Long userId = 1001L;
if ("admin".equals(username)) {
userId = 1L;
}
// 创建自定义声明
Map<String, Object> claims = new HashMap<>(1);
claims.put("userId", userId);
// 生成JWT令牌
String token = jwtUtil.generateToken(username, claims);
log.info("用户登录成功,用户名: {}", username);
Map<String, Object> result = new HashMap<>(3);
result.put("su***ess", true);
result.put("token", token);
result.put("userId", userId);
return result;
}
}
五、Spring Cloud Gateway 性能优化与生产实践
5.1 性能优化建议
Spring Cloud Gateway 基于 ***ty 和 WebFlux,本身具有良好的性能,但在生产环境中仍需进行适当的优化以发挥最佳性能。
5.1.1 JVM 参数优化
# JVM参数建议
-Xms4g -Xmx4g -Xmn2g
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
-XX:+UseG1GC -XX:G1HeapRegionSize=16m
-XX:G1ReservePercent=25
-XX:InitiatingHeapO***upancyPercent=30
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/gateway/heapdump.hprof
5.1.2 ***ty 参数优化
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000 # 连接超时时间(毫秒)
response-timeout: 5s # 响应超时时间
pool:
type: fixed # 连接池类型
max-connections: 500 # 最大连接数
acquire-timeout: 2000 # 获取连接超时时间(毫秒)
httpserver:
max-initial-line-length: 4096 # 请求行最大长度
max-header-size: 8192 # 请求头最大长度
max-chunk-size: 8192 # 分块最大大小
5.1.3 路由缓存优化
spring:
cloud:
gateway:
route-cache:
enabled: true # 启用路由缓存
time-to-live: 30s # 缓存存活时间
5.1.4 其他优化建议
-
减少不必要的过滤器:每个过滤器都会增加请求处理时间,只保留必要的过滤器。
-
异步处理:尽量使用异步处理,避免在过滤器中执行阻塞操作。
-
合理设置超时时间:根据服务响应时间合理设置超时时间,避免长时间等待。
-
启用压缩:对响应进行压缩,减少网络传输量。
server:
***pression:
enabled: true
mime-types: application/json,application/xml,text/html,text/plain,text/css,application/javascript
min-response-size: 1024 # 最小响应大小,小于此值不压缩
- 使用连接池:配置合理的连接池参数,避免频繁创建和关闭连接。
5.2 高可用部署方案
在生产环境中,为了保证网关的高可用性,需要采用集群部署方案。
5.2.1 部署要点
-
多节点部署:至少部署 3 个 Gateway 节点,避免单点故障。
-
负载均衡:在 Gateway 集群前部署负载均衡器(如 Nginx、云服务商的 SLB 等)。
-
共享配置:使用配置中心(如 Nacos、Apollo)管理路由配置,确保所有节点配置一致。
-
服务发现:结合服务发现组件(如 Nacos、Eureka),实现服务地址的动态获取。
-
健康检查:配置负载均衡器的健康检查,自动剔除不健康的 Gateway 节点。
-
会话共享:如果需要会话状态,使用 Redis 等实现会话共享。
5.2.2 Nginx 负载均衡配置示例
upstream gateway_cluster {
server 192.168.1.101:8080;
server 192.168.1.102:8080;
server 192.168.1.103:8080;
# 健康检查
health_check interval=3000 rise=2 fall=3 timeout=1000 type=http;
# 健康检查路径
health_check_uri /public/health;
}
server {
listen 80;
server_name api.example.***;
# 转发到网关集群
location / {
proxy_pass http://gateway_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 30s;
}
}
5.3 监控与日志
为了及时发现和解决问题,需要对 Spring Cloud Gateway 进行全面的监控和日志收集。
5.3.1 监控配置
添加 Spring Boot Actuator 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置监控端点:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,gateway # 暴露的端点
endpoint:
health:
show-details: always # 显示健康详情
probes:
enabled: true
metrics:
enabled: true
metrics:
export:
prometheus:
enabled: true # 启用Prometheus导出
server:
port: 8081 # 监控端口,与应用端口分离
5.3.2 集成 Prometheus 和 Grafana
- 部署 Prometheus,配置抓取 Gateway 的监控指标:
scrape_configs:
- job_name: 'gateway'
scrape_interval: 5s
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['192.168.1.101:8081', '192.168.1.102:8081', '192.168.1.103:8081']
- 部署 Grafana,导入 Spring Cloud Gateway 的仪表盘(可在 Grafana 官网搜索合适的仪表盘模板)。
5.3.3 日志配置
配置日志输出格式和滚动策略:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>***.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
创建 logback-spring.xml 配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!-- 日志文件路径 -->
<property name="LOG_FILE_PATH" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/logs}"/>
<property name="LOG_FILE_NAME" value="gateway"/>
<!-- 滚动文件追加器 -->
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE_PATH}/${LOG_FILE_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天滚动 -->
<fileNamePattern>${LOG_FILE_PATH}/${LOG_FILE_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保留30天的日志 -->
<maxHistory>30</maxHistory>
<!-- 总大小限制 -->
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder class="***.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>spanId</includeMdcKeyName>
<fieldNames>
<timestamp>timestamp</timestamp>
<message>message</message>
<logger>logger</logger>
<thread>thread</thread>
<level>level</level>
</fieldNames>
<timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSSZ</timestampPattern>
</encoder>
</appender>
<!-- 控制台输出JSON格式日志 -->
<appender name="CONSOLE_JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="***.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>spanId</includeMdcKeyName>
<fieldNames>
<timestamp>timestamp</timestamp>
<message>message</message>
<logger>logger</logger>
<thread>thread</thread>
<level>level</level>
</fieldNames>
<timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSSZ</timestampPattern>
</encoder>
</appender>
<!-- 根日志级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE_JSON"/>
<appender-ref ref="ROLLING_FILE"/>
</root>
<!-- Spring Cloud Gateway相关日志级别 -->
<logger name="org.springframework.cloud.gateway" level="INFO"/>
<logger name="reactor.***ty" level="INFO"/>
</configuration>
六、常见问题与解决方案
6.1 路由不生效问题
问题现象:配置了路由规则,但请求没有被正确路由到目标服务。
可能原因及解决方案:
-
路由顺序问题:
- Spring Cloud Gateway 按照路由配置的顺序匹配路由,前面的路由可能会优先匹配。
- 解决方案:调整路由顺序,将更具体的路由放在前面。
-
断言配置错误:
- 检查断言配置是否正确,特别是路径匹配是否准确。
- 解决方案:使用更精确的断言,或通过日志查看断言匹配情况。
-
服务地址错误:
- 检查路由的 uri 配置是否正确,特别是使用服务名时,确保服务已注册到服务发现组件。
- 解决方案:验证服务地址的正确性,确保服务正常运行。
-
过滤器问题:
- 某些过滤器可能会修改请求路径或阻止请求继续处理。
- 解决方案:暂时移除可疑的过滤器,逐步排查问题。
6.2 跨域问题
问题现象:前端调用 API 时出现跨域错误(CORS error)。
可能原因及解决方案:
-
CORS 配置不正确:
- 检查 CORS 配置是否覆盖了所有需要的路径、方法和请求头。
- 解决方案:调整 CORS 配置,确保允许前端的来源、方法和请求头。
-
过滤器顺序问题:
- CORS 过滤器需要在其他过滤器之前执行。
- 解决方案:确保 CORS 过滤器的优先级高于其他过滤器。
-
服务端也配置了 CORS:
- 如果后端服务也配置了 CORS,可能会与网关的 CORS 配置冲突。
- 解决方案:统一在网关层处理 CORS,后端服务不再配置 CORS。
6.3 性能问题
问题现象:网关响应缓慢,吞吐量低。
可能原因及解决方案:
-
JVM 参数不合理:
- 内存设置过小可能导致频繁 GC,影响性能。
- 解决方案:调整 JVM 参数,特别是堆内存大小和 GC 策略。
-
连接池配置不当:
- 连接池大小不合理可能导致连接等待或资源浪费。
- 解决方案:根据实际负载调整连接池参数。
-
过滤器过多或执行耗时操作:
- 过多的过滤器或在过滤器中执行耗时操作会增加响应时间。
- 解决方案:减少不必要的过滤器,避免在过滤器中执行阻塞操作。
-
超时设置不合理:
- 超时设置过短可能导致正常请求被中断,过长则可能导致资源占用过久。
- 解决方案:根据服务响应时间合理设置超时参数。
-
未启用压缩:
- 未启用响应压缩会增加网络传输时间。
- 解决方案:启用压缩功能,减少传输数据量。
七、总结与展望
Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关组件,提供了强大而灵活的功能,是构建微服务架构的理想选择。通过本文的介绍,我们全面了解了 Spring Cloud Gateway 的核心概念、工作原理、配置方式和高级特性。
Spring Cloud Gateway 的主要优势包括:
- 基于响应式编程:采用 ***ty 和 WebFlux,实现非阻塞 IO,性能优异。
- 功能丰富:提供路由、断言、过滤器等核心功能,支持限流、熔断、认证等高级特性。
- 灵活配置:支持配置文件、Java 代码、配置中心等多种配置方式,支持动态路由。
- 易于扩展:提供丰富的扩展点,可以自定义断言和过滤器。
- 生态集成:与 Spring Cloud 生态中的服务发现、配置中心等组件无缝集成。
参考资料
- Spring Cloud Gateway 官方文档
- Spring Cloud 官方文档
- Spring Boot 官方文档
- Resilience4j 官方文档
- Spring Cloud Alibaba 官方文档