Spring Cloud Gateway路由
文章目录
- 1. 前言
- 2. Gateway路由的基本概念
- 3. 三种路由
- 1. 静态路由
- 2. 动态路由
- 1. 利用外部存储
- 2. API动态路由
- 3. 服务发现路由(自动路由)
- 3.1. 配置方式
- 3.2 自动路由(服务发现)原理
- 核心源码
- GatewayDiscoveryClientAutoConfiguration
- `DiscoveryClientRouteDefinitionLocator`核心方法`getRouteDefinitions`
- 4. Gateway路由的核心组件
- 4.1 路由断言
- 组合并生效相当于并且的关系
- 如果要或的关系需要分配多个路由
- 4.2 路由过滤器
- 路由过滤器概念
- 路由过滤器工厂
- 内置路由过滤器
- 自定义路由过滤器
- 5. 路由的动态刷新
- 5.1. 添加依赖
- 5.2. 配置application.yml
- 5.3. 创建Config Server
- 5.4. 在远程Git仓库中添加配置文件
- 5.5. 启动Gateway服务
- 5.6. 动态刷新路由信息
- 6. 处理路由失败和异常
- 6.1 处理请求失败和异常
- 6.2 配置回退和重试策略
- 7. 高级路由功能
- 8. 参考文档
1. 前言
其实Spring Cloud Gateway2.x 的官网文档写的已经很全面了,如果想要了解,可以访问 《Spring Cloud Gateway》
Spring Cloud Gateway是一个基于Spring Boot构建的API网关,主要用于微服务架构中。它提供了一种简单而高效的方式来对请求进行路由、过滤和转发,从而实现对服务的统一访问入口和流量管理。Spring Cloud Gateway通过一个高度可配置的路由规则集,支持各种复杂的请求处理场景。
与传统的API网关(如Zuul)相比,Spring Cloud Gateway提供了更好的性能和丰富的功能。它基于响应式编程模型(Reactive)和Spring WebFlux框架构建,能够提供非阻塞的异步I/O处理,并且能够很好地与其他Spring Cloud组件进行集成。此外,Spring Cloud Gateway还提供了很多现成的功能,如负载均衡、熔断、限流等,可以帮助开发者快速搭建一个完善的API网关。
图片来自Spring Cloud Gateway2 官网 https://cloud.spring.io/spring-cloud-gateway/2.0.x/single/spring-cloud-gateway.html
2. Gateway路由的基本概念
- 2.1 路由的定义和作用
路由是一个HTTP请求的处理过程。在Spring Cloud Gateway中,每个请求都会经过一系列的过滤器,最后转发到目标URI。
路由配置由三部分组成:ID、目标URI、一系列的断言和过滤器。ID是路由的唯一标识,目标URI是请求的最终目的地,断言是用来匹配HTTP请求的规则,过滤器是用来处理HTTP请求的组件。
在Spring Cloud Gateway中配置路由有两种方式:通过配置文件和通过代码。
- 通过配置文件配置路由:
在application.yml文件中添加如下配置:
spring:
cloud:
gateway:
routes:
- id: user_route
uri: http://localhost:8080
predicates:
- Path=/user/**
以上面的配置为例,id为user_route的路由会匹配所有路径为/user/的请求,然后将其转发到http://localhost:8080。
- 通过代码配置路由:
在配置类中添加如下配置:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_route", r -> r.path("/user/**")
.uri("http://localhost:8080"))
.build();
}
以上面的配置为例,id为user_route的路由会匹配所有路径为/user/的请求,然后将其转发到http://localhost:8080。
- 2.2 如何在Spring Cloud Gateway中配置路由
3. 三种路由
根据路由的创建方式和使用场景,可以将路由分类为以下三种:
1. 静态路由
在Spring Cloud Gateway启动时,通过配置文件或Java代码定义的路由规则。这些路由规则在运行时是不可修改的。
可以通过配置文件定义静态路由:这段配置会将所有以/user开始的请求转发到http://localhost:8080。
spring:
cloud:
gateway:
routes:
- id: user_route
uri: http://localhost:8080
predicates:
- Path=/user/**
Spring Cloud Gateway的静态路由实现的原理主要通过Reactive模式下的Netty处理方式,以及Project Reactor中的Flux和Mono模型来处理并发请求。此外,它还使用了Spring 5的核心Webflux框架进行路由分发。
具体来说,当请求到来时,Spring Cloud Gateway会根据配置文件中的静态路由信息,将请求转发到相应的目标地址。
核心的代码主要集中在RoutePredicateHandlerMapping和FilteringWebHandler类中。
// RoutePredicateHandlerMapping用于处理请求的映射关系
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
public RoutePredicateHandlerMapping(GatewayProperties properties,
RouteLocator routeLocator, ProxyExchangeArgumentResolver
proxyExchangeArgumentResolver, ServerCodecConfigurer
serverCodecConfigurer, Environment environment) {
...
}
...
}
// FilteringWebHandler用于处理各种过滤器
public class FilteringWebHandler implements WebHandler {
private final List<GlobalFilter> globalFilters;
public FilteringWebHandler(List<GlobalFilter> globalFilters) {
this.globalFilters = globalFilters;
}
...
}
实际上,配置静态路由的主要工作量在于编写配置文件和理解路由断言,Spring Cloud Gateway已经为处理了复杂的路由转发和过滤器操作。
具体的路由匹配和转发过程则由Spring Cloud Gateway框架自动处理。在处理请求时,Gateway会依次经过负载均衡器、过滤器链和路由断言的处理,并最终将请求转发到配置的目标地址。
2. 动态路由
动态路由通常可以理解为两种方式,一种通过外部配置存储动态更新网关路由,另一种是通过API 的方式动态新增修改删除网关路由。
1. 利用外部存储
动态路由与静态路由的主要区别在于,动态路由允许在运行时更新路由配置,而不需要重启应用。Spring Cloud Gateway
支持通过配置中心(如Spring Cloud Config
)。
实现动态路由的原理主要是通过监听配置中心的变化,当配置发生变化时,使用Event机制触发路由信息的更新。Spring Cloud Gateway会自动处理新的路由配置并更新其内部的路由表。
核心代码主要集中在以下几个类中:
当配置中心发生更改时,RouteRefreshListener会监听到相关事件,触发RefreshRoutesEvent事件,从而使CachingRouteLocator更新路由信息。这样,Spring Cloud Gateway就可以在运行时动态地处理新的路由配置。
CachingRouteLocator
:它负责缓存和管理路由信息,同时也会处理路由信息的更新。
public abstract class CachingRouteLocator implements RouteLocator,
ApplicationListener<RefreshRoutesEvent> {
public abstract Flux<Route> fetch();
@Override
public void onApplicationEvent(RefreshRoutesEvent event) {
// 当收到一个RefreshRoutesEvent事件时,将会触发路由信息的更新
this.resetRoutes().subscribe();
}
...
}
RouteDefinitionRouteLocator
:它继承自CachingRouteLocator
,并负责从RouteDefinitionLocator
中获取路由定义,然后根据路由定义创建路由。
public class RouteDefinitionRouteLocator extends CachingRouteLocator {
public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory>
routePredicates, RouteDefinitionHandlerFilter routeDefinitionHandlerFilter,
ConfigurationService configurationService, GatewayProperties properties) {
...
}
...
}
- RouteRefreshListener:它负责监听配置中心的变化,并在检测到更改时发布一个RefreshRoutesEvent事件。
@Configuration
public class RouteRefreshListener {
@Bean
public ApplicationListener<?> routeChangeListener(RouteDefinitionRouteLocator
routeDefinitionRouteLocator) {
return new ApplicationListener<RefreshRoutesResultEvent>() {
@Override
public void onApplicationEvent(RefreshRoutesResultEvent event) {
routeDefinitionRouteLocator.onApplicationEvent(new RefreshRoutesEvent(this));
}
};
}
}
要实现动态路由,还需要在配置文件中配置相应的配置中心,例如使用Spring Cloud Config:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: dynamic_route
uri: lb://service-id
predicates:
- Path=/dynamic_path/**
config:
uri: http://config-server-uri
label: master
name: gateway
2. API动态路由
在Spring Cloud Gateway运行时,通过调用API动态创建和修改的路由规则。这种路由规则可以根据需要进行实时的修改。动态路由通常需要通过编程方式创建。
在Spring Cloud Gateway中,可以通过调用Gateway API来动态地创建和修改路由规则。这些路由规则可以在运行时进行实时修改。要实现这一功能,需要使用RouteDefinitionWriter
接口,它提供了添加、删除和更新路由定义的方法。
Spring Cloud Gateway中动态地创建和修改路由规则
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
创建一个RouteController
来处理动态路由的创建、更新和删除操作
@RestController
@RequestMapping("/route")
public class RouteController {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;
@PostMapping
public Mono<Void> add(@RequestBody RouteDefinition routeDefinition) {
return routeDefinitionWriter.save(Mono.just(routeDefinition)).then();
}
@PutMapping
public Mono<Void> update(@RequestBody RouteDefinition routeDefinition) {
return routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()))
.then(routeDefinitionWriter.save(Mono.just(routeDefinition)))
.then();
}
@DeleteMapping("/{id}")
public Mono<Void> delete(@PathVariable String id) {
return routeDefinitionWriter.delete(Mono.just(id));
}
@GetMapping
public Flux<RouteDefinition> getRoutes() {
return routeDefinitionLocator.getRouteDefinitions();
}
}
在这个例子中,使用RouteDefinitionWriter
保存、更新和删除路由定义。当定义新的路由规则或者更新现有的路由规则时,会自动触发路由表的刷新。
现在就可以使用HTTP API来动态地创建、修改和删除路由规则了:
-
添加一个新的路由规则:
POST /route { "id": "my_route", "uri": "http://example.com", "predicates": [ { "name": "Path", "args": { "pattern": "/mypath/**" } } ], "filters": [ { "name": "RewritePath", "args": { "regexp": "/mypath/(?<segment>.*)", "replacement": "/${segment}" } } ] }
-
更新现有的路由规则:
PUT /route { "id": "my_route", "uri": "http://example.com", "predicates": [ { "name": "Path", "args": { "pattern": "/mypath_v2/**" } } ], "filters": [ { "name": "RewritePath", "args": { "regexp": "/mypath_v2/(?<segment>.*)", "replacement": "/${segment}" } } ] }
-
删除现有的路由规则:
DELETE /route/my_route
通过这种方法,可以在Spring Cloud Gateway运行时动态地创建和修改路由规则,以满足不断变化的业务需求。
3. 服务发现路由(自动路由)
3.1. 配置方式
这是一种特殊的动态路由,其路由规则是根据服务发现机制来自动创建的。
在微服务架构中,服务发现是一种特别重要的机制,它让微服务能够自动地发现网络中的其他服务,并知道如何与它们进行交互。Spring Cloud Gateway通过与服务注册中心(如Eureka、Consul等)集成,实现了自动的服务发现路由功能。
当一个新的服务实例被注册到服务注册中心时,Spring Cloud Gateway会自动发现它,并创建一个新的路由规则,将请求转发到这个服务。同样,当一个服务实例下线或被注销时,对应的路由规则也会被自动删除,这就是所谓的服务发现路由或者自动路由。
这种机制可以极大地简化服务间的交互,因为不需要手动地为每一个服务定义路由规则。
在Spring Cloud Gateway中使用服务发现的一个简单示例
添加 Spring Cloud Gateway和 Eureka的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
application.yml
文件中配置Eureka和Gateway:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 启用服务发现路由
application:
name: gateway-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
启动Eureka Server和的服务,然后启动Spring Cloud Gateway,Gateway就会自动发现注册在Eureka中的服务,并为它们创建路由规则。例如有一个名为user-service
的服务,那么就可以通过http://localhost:8080/user-service
来访问。
3.2 自动路由(服务发现)原理
在Spring Cloud Gateway中实现服务发现路由的核心类是DiscoveryClientRouteDefinitionLocator
。这个类负责从注册中心获取服务实例信息,并基于这些信息创建路由定义。当服务实例发生变化时,DiscoveryClientRouteDefinitionLocator
会自动更新路由定义。
服务发现路由的底层原理主要包括以下几个方面:
-
集成服务发现组件:Spring Cloud Gateway可以与多种服务注册中心(如Eureka、Consul等)集成。这些集成是通过实现
DiscoveryClient
接口来完成的。DiscoveryClient
提供了获取服务实例信息的方法,例如getInstances(String serviceId)
。这使得Gateway可以获取到注册中心中所有服务的实例信息。 -
创建路由定义:
DiscoveryClientRouteDefinitionLocator
会将从DiscoveryClient
获取到的服务实例信息转换为RouteDefinition
对象。这些RouteDefinition
对象包含了路由的基本信息,如ID、URI、断言和过滤器等。通过这些信息,Gateway可以知道如何将请求路由到特定的服务实例。 -
路由规则更新:当注册中心中的服务实例发生变化时,
DiscoveryClientRouteDefinitionLocator
会自动更新路由规则。这是通过监听服务实例变化事件来实现的。当接收到服务实例变化事件后,DiscoveryClientRouteDefinitionLocator
会重新获取服务实例信息,并更新RouteDefinition
对象。这样,Gateway就能实时地感知到服务实例的变化,并相应地调整路由规则。 -
请求转发:当Gateway接收到一个请求时,它会根据
DiscoveryClientRouteDefinitionLocator
提供的路由定义匹配相应的路由规则。然后,Gateway会将请求转发到匹配的服务实例。这个转发过程是通过NettyRoutingFilter
完成的,它会根据RouteDefinition
中的URI信息转发请求。
核心源码
GatewayDiscoveryClientAutoConfiguration
我们可以看到 当 DiscoveryClient
存在并且 spring.cloud.gateway.discovery.locator.enabled
为 true
时,创建一个 DiscoveryClientRouteDefinitionLocator
Bean,允许 Spring Cloud Gateway 动态地从服务注册中心发现路由定义。
DiscoveryClientRouteDefinitionLocator
核心方法getRouteDefinitions
getRouteDefinitions
方法的主要目的是从 DiscoveryClient
获取服务实例信息并将其转换为路由定义。首先解析 includeExpression
和 urlExpression
,然后根据 includeExpression
创建一个用于判断服务实例是否需要被包含在路由定义中的 Predicate
。接着从 DiscoveryClient
获取所有服务实例信息,并转换为 RouteDefinition
。转换过程中会生成 PredicateDefinition
和 FilterDefinition
。最后返回一个包含所有路由定义的 Flux
对象。
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
// 创建一个 SpelExpressionParser 用于解析表达式
SpelExpressionParser parser = new SpelExpressionParser();
// 解析 includeExpression,用于判断服务实例是否需要被包含在路由定义中
Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());
// 解析 urlExpression,用于生成服务实例对应的 URI
Expression urlExpr = parser.parseExpression(properties.getUrlExpression());
// 根据 includeExpression 创建一个 Predicate,用于判断服务实例是否需要被包含在路由定义中
Predicate<ServiceInstance> includePredicate;
if (properties.getIncludeExpression() == null || "true".equalsIgnoreCase(properties.getIncludeExpression())) {
includePredicate = instance -> true;
} else {
includePredicate = instance -> {
Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);
if (include == null) {
return false;
}
return include;
};
}
// 从 DiscoveryClient 获取所有服务实例信息并转换为路由定义
return Flux.fromIterable(discoveryClient.getServices())
.map(discoveryClient::getInstances) // 获取每个服务的所有实例
.filter(instances -> !instances.isEmpty()) // 过滤掉没有实例的服务
.map(instances -> instances.get(0)) // 获取每个服务的第一个实例
.filter(includePredicate) // 过滤掉不需要被包含在路由定义中的服务实例
.map(instance -> { // 将服务实例转换为 RouteDefinition
String serviceId = instance.getServiceId();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
// 获取服务实例对应的 URI
String uri = urlExpr.getValue(evalCtxt, instance, String.class);
routeDefinition.setUri(URI.create(uri));
final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties);
// 生成 PredicateDefinition
for (PredicateDefinition original : this.properties.getPredicates()) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
predicate.addArg(entry.getKey(), value);
}
routeDefinition.getPredicates().add(predicate);
}
// 生成 FilterDefinition
for (FilterDefinition original : this.properties.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
filter.addArg(entry.getKey(), value);
}
routeDefinition.getFilters().add(filter);
}
return routeDefinition;
});
}
4. Gateway路由的核心组件
4.1 路由断言
路由断言(Route Predicates)是Spring Cloud Gateway中的一个核心概念,它负责匹配HTTP请求的属性,并决定是否将请求路由到特定的服务。
使用断言,可以基于许多不同的请求属性定义路由规则,包括:
- 请求路径:比如请求的URL。
- 请求方法:GET、POST等。
- 请求头:可以匹配特定的请求头字段和值。
- 查询参数:可以匹配URL的查询参数。
- Cookie:可以匹配特定的cookie。
断言的定义通常在Spring配置文件(如application.yaml)中,作为路由定义的一部分。每个断言都是一种谓词(Predicate),这是一个返回true或false的函数。
路由断言的示例:
spring:
cloud:
gateway:
routes:
- id: user_service
uri: http://localhost:8080/user
predicates:
- Path=/user/**
- Method=GET
- Header=X-Request-User, \d+
在这个例子中,定义了一个名为"user_service"的路由,其目标URI为"http://localhost:8080/user"。定义了三个断言:
- Path断言:只有当请求路径以"/user/"开头时,才会触发该路由。
- Method断言:只有当HTTP请求方法为GET时,才会触发该路由。
- Header断言:只有当请求头中存在名为"X-Request-User"的字段,并且其值为数字时,才会触发该路由。
只有当所有的断言都返回true时,该请求才会被路由到目标服务。否则,Spring Cloud Gateway将返回404错误。
组合并生效相当于并且的关系
在这个示例中,只有满足以下所有条件的请求才会路由到http://localhost:8080/:
- 请求路径以/api/开头
- 请求方法为GET
- 请求头中包含X-Request-Id,且其值为数字
- 查询参数中包含debug,并且其值为true
- Cookie中包含sessionId,并且其值不为空
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://localhost:8080/
predicates:
- Path=/api/**
- Method=GET
- Header=X-Request-Id, \d+
- Query=debug, true
- Cookie=sessionId, .+
如果要或的关系需要分配多个路由
如果想要以"或"的关系生效,需要定义多个路由。考虑到Spring Cloud Gateway的工作方式,一个请求只能被一个路由处理。这意味着谓词是以"与"的方式工作的,而不是"或"。如果想要一个请求被多个谓词处理,需要创建多个路由。每个路由有一个谓词,这样一个请求可以匹配多个路由。
在以下的配置中,如果请求路径以/api/开头, 或请求方法为GET, 或请求头中包含X-Request-Id且其值为数字, 或查询参数中包含debug且其值为true, 或Cookie中包含sessionId且其值不为空,任何一个条件满足,请求都会路由到http://localhost:8080/。
spring:
cloud:
gateway:
routes:
- id: host_route1
uri: http://localhost:8080/
predicates:
- Path=/api/**
- id: host_route2
uri: http://localhost:8080/
predicates:
- Method=GET
- id: host_route3
uri: http://localhost:8080/
predicates:
- Header=X-Request-Id, \d+
- id: host_route4
uri: http://localhost:8080/
predicates:
- Query=debug, true
- id: host_route5
uri: http://localhost:8080/
predicates:
- Cookie=sessionId, .+
4.2 路由过滤器
Spring Cloud Gateway作为一个API网关,提供了很多功能,如路由转发、断路器、限流等。其中,路由过滤器是其核心功能之一,它允许我们在请求被路由之前或之后对请求进行处理。本文将详细介绍Spring Cloud Gateway的路由过滤器。
路由过滤器概念
路由过滤器是一个Java类,用于修改进入或退出网关的HTTP请求和响应。它包含两种类型的过滤器:
- Global Filter:全局过滤器,对所有路由请求生效。
- GatewayFilter:网关过滤器,只对特定路由请求生效。
Spring Cloud Gateway中的过滤器基于WebFilter
接口实现,并且实现了Ordered
接口来控制过滤器的执行顺序。过滤器可以在请求被路由之前(Pre过滤器)或之后(Post过滤器)进行处理。
路由过滤器工厂
Spring Cloud Gateway的过滤器是通过工厂创建的。这些工厂实现了GatewayFilterFactory
接口,这个接口包含两个方法:
apply(T config)
:将配置信息传递给过滤器,并创建过滤器实例。getConfigClass()
:返回过滤器所使用的配置类。
内置路由过滤器
Spring Cloud Gateway提供了许多内置过滤器,可以覆盖大部分基本功能。以下是一些常用的内置过滤器:
- AddRequestHeader:添加请求头。
- AddRequestParameter:添加请求参数。
- AddResponseHeader:添加响应头。
- PrefixPath:添加前缀路径。
- RewritePath:重写请求路径。
- Retry:重试。
- SetPath:设置请求路径。
- SetResponseHeader:设置响应头。
- StripPrefix:去掉前缀。
- Hystrix:使用Hystrix断路器。
自定义路由过滤器
除了内置过滤器以外,我们还可以自定义过滤器。自定义过滤器需要实现GatewayFilter
接口,并重写filter(ServerWebExchange exchange, GatewayFilterChain chain)
方法。下面是一个简单的自定义过滤器示例:
创建了一个名为 CustomGatewayFilterFactory
的类,实现了 GatewayFilterFactory<GatewayFilterFactory.Args>
接口,并重写了 apply
和 name
方法。在 apply
方法中,我们返回一个新的 CustomGatewayFilter
实例。在 name
方法中,我们返回过滤器的名称,这个名称需要与配置文件中的 filters
部分所引用的名称相匹配。最后,我们将 CustomGatewayFilterFactory
注册为一个 Bean。
public class CustomGatewayFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 在请求被路由之前执行的逻辑
System.out.println("Custom Gateway Filter Pre");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// 在请求被路由之后执行的逻辑
System.out.println("Custom Gateway Filter Post");
}));
}
}
要在配置文件中为特定路由配置 CustomGatewayFilter
,需要在 Spring Cloud Gateway 的配置文件(如 application.yml
或 application.properties
)中定义路由规则,并在 filters
部分引用的 CustomGatewayFilter
。以下是一个在 application.yml
中配置 CustomGatewayFilter
的示例:
spring:
cloud:
gateway:
routes:
- id: my_route
uri: http://example.com # 目标服务的地址
predicates:
- Path=/my_path/** # 路由条件,例如路径匹配
filters:
- name: CustomFilter # 在这里引用的自定义过滤器
需要先将 CustomGatewayFilter
注册为一个全局过滤器。为了达到这个目的,需要创建一个 GatewayFilterFactory
,并将其注册为一个 Bean。请参考以下示例:
@Configuration
public class CustomGatewayFilterConfiguration {
@Bean
public CustomGatewayFilterFactory customGatewayFilterFactory() {
return new CustomGatewayFilterFactory();
}
public static class CustomGatewayFilterFactory implements GatewayFilterFactory<GatewayFilterFactory.Args> {
@Override
public GatewayFilter apply(GatewayFilterFactory.Args args) {
return new CustomGatewayFilter();
}
@Override
public String name() {
return "CustomFilter";
}
}
}
5. 路由的动态刷新
我们写一个简化的教程来看下如何在Spring Cloud Gateway中实现动态路由刷新。
5.1. 添加依赖
在Gateway服务的pom.xml
文件中添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
这里我们使用RabbitMQ作为消息代理,如果想使用其他消息代理,请替换相应的依赖。
5.2. 配置application.yml
在Gateway服务的src/main/resources/application.yml
中添加以下配置:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user-service/**
config:
uri: http://localhost:8888
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bus:
id: ${spring.application.name}:${server.port}
这里我们配置了一个名为user-service
的路由,路由的配置信息将从Config Server(地址为http://localhost:8888
)获取。
5.3. 创建Config Server
创建一个新的Spring Boot项目,名为config-server
。在pom.xml
文件中添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
在src/main/resources/application.yml
中添加以下配置:
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/your-username/your-repo.git
这里配置了Config Server从远程Git仓库获取配置信息。
在src/main/java/com/example/configserver/ConfigServerApplication.java
中添加@EnableConfigServer
注解以启动Config Server:
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
启动Config Server。
5.4. 在远程Git仓库中添加配置文件
在的远程Git仓库中添加一个名为gateway-service.yml
的文件,内容如下:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user-service/**
这里配置了一个名为user-service
的路由。
5.5. 启动Gateway服务
现在启动的Gateway服务,它将从Config Server获取最新的路由信息。可以通过访问http://localhost:8080/user-service/some-api
来测试路由是否生效。
5.6. 动态刷新路由信息
当gateway-service.yml
文件发生变化并推送到远程Git仓库后,可以发送一个POST请求到http://localhost:8080/actuator/refresh
端点以刷新Gateway的路由信息。
也可以在config-server
项目中添加Spring Cloud Bus的依赖,并添加相应的配置,以实现自动广播路由变更消息。
6. 处理路由失败和异常
如何处理路由失败和异常也是大家在写一个高可用,健壮性良好的网关服务的基本要求。那么在本教程中,我们来了解一下Spring Cloud Gateway中如何处理路由失败和异常,以及如何配置回退和重试策略。
6.1 处理请求失败和异常
当路由转发过程中发生异常,例如目标服务不可用,可以使用Gateway的全局异常处理器来捕获异常并返回一个友好的错误响应。默认情况下,Gateway会使用org.springframework.cloud.gateway.handler.GlobalErrorWebExceptionHandler
作为全局异常处理器。可以通过实现ErrorWebExceptionHandler
接口并注册为Spring Bean来自定义全局异常处理器。
写一个简单的例子可供参考
自定义CustomGlobalExceptionHandler继承了AbstractErrorWebExceptionHandler
并覆盖了getRoutingFunction
方法,以自定义错误响应。
@Component
public class CustomGlobalExceptionHandler extends AbstractErrorWebExceptionHandler {
public CustomGlobalExceptionHandler(ErrorAttributes errorAttributes,
WebProperties.Resources resources,
ApplicationContext applicationContext,
ServerCodecConfigurer serverCodecConfigurer) {
super(errorAttributes, resources, applicationContext);
setMessageWriters(serverCodecConfigurer.getWriters());
setMessageReaders(serverCodecConfigurer.getReaders());
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Throwable error = getError(request);
// 自定义响应体
Map<String, Object> errorAttributes = new HashMap<>();
errorAttributes.put("message", error.getMessage());
errorAttributes.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
errorAttributes.put("timestamp", LocalDateTime.now());
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorAttributes));
}
}
默认实现是DefaultErrorWebExceptionHandler
6.2 配置回退和重试策略
使用Spring Cloud Gateway,可以为路由配置回退和重试策略。回退策略允许在转发请求失败时返回一个预定义的响应。重试策略允许在请求失败时尝试重新发送请求。
为了配置回退策略,可以在路由配置中添加fallbackUri
属性。
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user-service/**
filters:
- name: Hystrix
args:
name: user-service
fallbackUri: forward:/fallback
为user-service
路由配置了一个回退URI,它会将请求转发到/fallback
端点。需要在的Gateway服务中实现这个端点,并返回一个预定义的响应。
为了配置重试策略,可以在路由配置中添加Retry
过滤器。
为user-service
路由配置了一个重试策略。当请求方法为GET
且服务器返回500 Internal Server Error
状态时,Gateway会尝试重新发送请求,最多重试3次。
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user-service/**
filters:
- name: Retry
args:
retries: 3
statuses: INTERNAL_SERVER_ERROR
methods: GET
7. 高级路由功能
写不动了,路由详解就到这儿,后面有时间补充一下高级路由功能。
- 7.1 如何配置负载均衡
- 7.2 如何配置熔断器
- 7.3 如何配置速率限制
8. 参考文档
Spring Cloud Gateway官方文档 https://cloud.spring.io/spring-cloud-gateway/2.0.x/single/spring-cloud-gateway.html