前言
最近有一个新的需求,需要手动配置一个没有 Nacos 的 Gateway 网关。大大小小踩了不少坑,今天就全面复盘一下。
Spring Cloud Gateway 简介
Spring Cloud Gateway是一个功能强大、灵活易用的API网关服务,它为微服务架构中的服务间通信提供了统一的入口和出口。
主要功能和特点
- 路由转发:Spring Cloud Gateway可以根据配置将请求转发到不同的目标服务,支持动态路由配置,灵活适应不同的场景需求。
- 过滤器链:通过过滤器链对请求和响应进行处理,支持在请求被路由前、后进行预处理或后处理,实现日志记录、认证、授权、请求转发、限流等功能。
- 断路器:集成了 Hystrix 断路器,可以在服务不可用或超时时进行熔断,保障服务的稳定性。
- 集成性:作为Spring Cloud项目的一部分,Spring Cloud Gateway天然集成了Spring Cloud组件,如服务注册与发现(Eureka、Consul等)、负载均衡(Ribbon)、断路器(Hystrix)等。
- 响应式编程模型:基于Project Reactor实现的响应式编程模型,支持异步和非阻塞的IO操作,提高了系统的性能和吞吐量。
由于篇幅和自身业务用到的知识面等因素,我只从两个方面来复盘这次的业务。
1、动态路由。
2、过滤器链。
Gateway 动态路由
Gateway动态路由是指在网关服务运行时,能够动态地添加、修改和删除路由规则,而无需重新启动网关服务。这个功能的出现是为了应对不断变化的业务需求和路由规则,使得网关服务更加灵活和可扩展。
动态路由的主要思想是将路由规则存储在外部配置中,例如数据库、ZooKeeper、或者其他配置中心。网关服务在启动时从外部配置中加载这些路由规则,并将其应用到请求转发过程中。这样,当业务需求发生变化时,我们可以通过在外部配置中心中添加、修改或删除路由规则,来实现动态路由的管理。
1、如何定义一个路由
- ID(标识符) :路由的唯一标识符,用于在路由配置中区分不同的路由。
- URI(统一资源标识符) :指定了路由请求应该转发到的目标地址。可以是一个具体的 URL,也可以是一个负载均衡的服务名称。
- 谓词(Predicates) :谓词定义了路由应该何时生效。谓词通常根据请求的特征(如路径、请求方法、请求头等)来匹配请求,只有满足谓词条件的请求才会被路由到目标地址。
- 过滤器(Filters) :过滤器用于在请求被路由之前或之后对请求进行修改或过滤。可以使用过滤器来实现请求的修改、添加认证、限流、重定向等功能。
- 顺序(Order) :路由的顺序属性指定了路由应该被应用的顺序。通常,路由会按照它们的顺序依次匹配请求,因此顺序属性很重要,可以确保路由按照预期的顺序应用。
例子:
java
[
{
"id": "route1",
"uri": "http://localhost:8081",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/service1/**"
}
}
],
"filters": [
{
"name": "RewritePath",
"args": {
"regexp": "/service1/(?<remaining>.*)",
"replacement": "/$\{remaining}"
}
}
]
},
{
"id": "route2",
"uri": "http://localhost:8082",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/service2/**"
}
}
]
}
]
2、如何加载动态路由
编写自定义的RouteDefinitionLocator接口的实现,用于从外部源加载路由规则。
java
@Component
public class CustomRouteDefinitionLocator implements RouteDefinitionLocator {
@Autowired
private ExternalConfigurationSource externalConfigurationSource;
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
// 从外部配置中心加载路由规则
List<RouteDefinition> definitions = externalConfigurationSource.loadRouteDefinitions();
return Flux.fromIterable(definitions);
}
}
定义一个外部配置源,用于加载路由规则。例如,可以实现一个从数据库或配置文件中读取路由规则的类。
java
@Component
public class ExternalConfigurationSource {
// 从外部源加载路由定义
public List<RouteDefinition> loadRouteDefinitions() {
// 从数据库或配置文件等外部源加载路由规则
// 返回路由定义的列表
}
}
3、如何管理动态路由
java
/**
* 路由管理服务接口,用于管理动态路由规则
*/
public interface RouteService {
/**
* 获取所有路由规则
* @return 包含所有路由规则的 Flux 对象
*/
public Flux<RouteDefinition> getAllRoutes();
/**
* 根据路由 ID 删除指定路由规则
* @param routeId 要删除的路由规则的唯一标识符
* @return 删除操作完成后的 Mono 对象
*/
public Mono<Void> deleteRouteById(String routeId);
/**
* 刷新所有路由规则,使得新的路由规则立即生效
* @return 刷新操作完成后的 Mono 对象
*/
public Mono<Void> refreshAllRoutes();
/**
* 根据路由 ID 刷新指定路由规则,使得新的路由规则立即生效
* @param routeId 要刷新的路由规则的唯一标识符
* @return 刷新操作完成后的 Mono 对象
*/
public Mono<Void> refreshRouteById(String routeId);
/**
* 添加新的路由规则
* @param routeDefinition 要添加的路由规则对象
* @return 添加操作完成后的 Mono 对象,包含添加后的路由规则信息
*/
public Mono<ResponseEntity<?>> addRoute(RouteDefinition routeDefinition);
}
java
@Service
public class RouteServiceImpl implements RouteService {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;
/**
* 查询全部路由
* @return 包含全部路由定义的 Flux 对象
*/
@Override
public Flux<RouteDefinition> getAllRoutes() {
return routeDefinitionLocator.getRouteDefinitions();
}
/**
* 根据路由 ID 删除路由
* @param routeId 要删除的路由的唯一标识符
* @return 删除操作完成后的 Mono 对象
*/
@Override
public Mono<Void> deleteRouteById(String routeId) {
return routeDefinitionWriter.delete(Mono.just(routeId));
}
/**
* 刷新全部路由
* @return 刷新操作完成后的 Mono 对象
*/
@Override
public Mono<Void> refreshAllRoutes() {
return routeDefinitionLocator.getRouteDefinitions()
.flatMap(routeDefinition -> routeDefinitionWriter.save(Mono.just(routeDefinition)))
.then();
}
/**
* 根据路由 ID 刷新指定路由
* @param routeId 要刷新的路由的唯一标识符
* @return 刷新操作完成后的 Mono 对象
*/
@Override
public Mono<Void> refreshRouteById(String routeId) {
return routeDefinitionLocator.getRouteDefinitions()
.filter(routeDefinition -> routeDefinition.getId().equals(routeId))
.flatMap(routeDefinition -> routeDefinitionWriter.save(Mono.just(routeDefinition)))
.then();
}
/**
* 添加新的路由
* @param routeDefinition 要添加的路由定义对象
* @return 添加操作完成后的 Mono 对象,包含添加后的路由信息
*/
@Override
public Mono<ResponseEntity<?>> addRoute(RouteDefinition routeDefinition) {
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
return Mono.just(ResponseEntity.ok().build());
}
}
Gateway 过滤器链
1、一个简单的全局过滤器
java
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 全局过滤器示例类,实现了 GlobalFilter 和 Ordered 接口。
*/
public class SimpleFilter implements GlobalFilter, Ordered {
/**
* 过滤器逻辑方法,在此方法中可以实现对请求的处理逻辑。
* @param exchange ServerWebExchange 对象,表示当前的请求和响应的上下文信息。
* @param chain GatewayFilterChain 对象,用于将请求交给下一个过滤器处理。
* @return Mono<Void> 对象,表示异步处理结果的流。
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 在此处编写过滤器的逻辑处理代码
return null;
}
/**
* 获取过滤器的顺序,数值越小优先级越高。
* @return 整数值,表示过滤器的顺序。
*/
@Override
public int getOrder() {
return 0;
}
}
2、讲解几个概念
Spring WebFlux 框架 是什么?
Spring WebFlux 是 Spring Framework 5 引入的一种新的反应式编程模型,用于构建基于异步和非阻塞的响应式应用程序。与传统的基于 Servlet 的 Spring MVC 框架不同,Spring WebFlux 提供了一种更加灵活和高效的编程方式,适用于处理高并发、高吞吐量的应用场景。
Spring WebFlux 的核心思想是响应式编程,它基于 Reactor 库实现了反应式流处理。在 Spring WebFlux 中,所有的输入和输出都被抽象为流(Flux 和 Mono),这使得开发者可以更容易地处理异步数据流,实现非阻塞的事件驱动编程。
Spring WebFlux 框架提供了基于注解的编程模型,允许开发者使用常见的 Spring 注解来定义控制器、路由和其他组件。同时,它也支持函数式编程风格,允许开发者使用函数式端点来定义路由和处理器。
ServerWebExchange 是什么? ServerWebExchange
是 Spring WebFlux 框架中的核心接口,用于表示一次客户端与服务器之间的 HTTP 交换。
ServerWebExchange 的作用是什么? 它封装了 HTTP 请求和响应,并提供了操作请求和响应的方法,以及与请求处理相关的上下文信息。
ServerWebExchange 的主要功能和特点
- 封装了 HTTP 请求和响应,提供了统一的处理接口。
- 可以对请求和响应进行修改和定制化处理。
- 包含了与请求处理相关的上下文信息,如路由信息、路径变量、查询参数等。
- 提供了属性存储机制,允许在请求处理过程中传递和共享数据。
ServerWebExchange 的组成
-
ServerHttpRequest:请求对象
- 作用和功能: 代表客户端发送的 HTTP 请求,包含了请求的方法、URL、头部信息、参数等。
- 常用方法和属性:
getMethod()
、getURI()
、getHeaders()
、getQueryParams()
等。
-
ServerHttpResponse:响应对象
- 作用和功能: 代表服务器发送的 HTTP 响应,包含了响应的状态码、头部信息、响应体等。
- 常用方法和属性:
setStatusCode()
、getHeaders()
、writeWith()
、writeAndFlushWith()
等。
-
Attributes:属性对象
- 作用和功能: 用于存储和传递与请求处理相关的属性信息,提供了属性的增删改查操作。
- 常用方法和属性:
getAttribute()
、setAttribute()
、removeAttribute()
、getAttributeOrDefault()
等。
ServerWebExchange 的使用
-
如何获取 ServerWebExchange 对象? 可以通过 WebFlux 框架提供的处理器或过滤器中的方法参数获取。
-
ServerWebExchange 对象的常见用法和场景
- 从请求中获取信息,如路径、参数、头部等。
- 修改请求和响应,如重定向、添加头部信息等。
- 传递和共享属性,如在拦截器或过滤器中传递数据。
3、过滤器 Filter 可以实现什么功能
修改请求路径,原始请求的 Path 修改为 "/test"
java
@Component
public class TestFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String newServletPath = "/test"; // 修改请求路径
ServerHttpRequest newRequest = request.mutate().path(newServletPath).build();
return chain.filter(exchange.mutate().request(newRequest).build());
}
}
修改请求数据,过滤器会拦截传入的请求,并修改请求体中的JSON数据,向其中添加一个名为 "userId" 的新属性。
java
@Component
public class ModifyRequestBodyGlobalFilter implements GlobalFilter {
@Autowired
private ObjectMapper objectMapper;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 从请求中获取请求体
Flux<DataBuffer> body = request.getBody();
// 将请求体中的数据转换成字符串
return DataBufferUtils.join(body)
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
try {
// 解析JSON数据
JsonNode jsonNode = objectMapper.readTree(bodyString);
Assert.isTrue(jsonNode instanceof ObjectNode, "JSON格式异常");
ObjectNode objectNode = (ObjectNode) jsonNode;
// 在JSON数据中添加新属性
objectNode.put("userId", "123");
// 将修改后的JSON数据转换回字节数组
byte[] modifiedBytes = objectMapper.writeValueAsBytes(objectNode);
// 创建新的请求体数据缓冲区
DataBuffer modifiedDataBuffer = exchange.getResponse().bufferFactory().wrap(modifiedBytes);
// 创建新的请求对象,并将修改后的请求体设置进去
ServerHttpRequest modifiedRequest = new ServerHttpRequestDecorator(request) {
@Override
public Flux<DataBuffer> getBody() {
return Flux.just(modifiedDataBuffer);
}
};
// 使用修改后的请求对象创建新的ServerWebExchange
ServerWebExchange modifiedExchange = exchange.mutate().request(modifiedRequest).build();
// 继续执行过滤器链
return chain.filter(modifiedExchange);
} catch (IOException e) {
log.error("Failed to modify request body", e);
return Mono.error(e);
}
});
}
}
修改路由,根据路由的元数据中的 targetUris 列表中随机选择一个目标 URI,并修改当前的路由为选定的 URI。
java
@Component
public class RandomUriRoutingFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
if (route != null) {
List<String> targetUris = (List<String>) route.getMetadata().get("targetUris");
if (targetUris != null && !targetUris.isEmpty()) {
String selectedUri = getRandomUri(targetUris);
try {
URI uri = new URI(selectedUri);
Route newRoute = Route.async()
.asyncPredicate(route.getPredicate())
.filters(route.getFilters())
.id(route.getId())
.order(route.getOrder())
.uri(uri).build();
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, newRoute);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
return chain.filter(exchange);
}
private String getRandomUri(List<String> targetUris) {
Random random = new Random();
int randomIndex = random.nextInt(targetUris.size());
return targetUris.get(randomIndex);
}
@Override
public int getOrder() {
return 0;
}
}
修改响应体。
java
@Component
public class ModifyResponseFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取当前路由信息
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
if (route != null) {
// 在响应中修改需要的内容
RewriteFunction<String, String> rewriteFunction = (exchange1, response) ->
response.map(body -> body.toUpperCase()); // 示例:将响应体转换为大写
// 使用 ModifyResponseBodyGatewayFilterFactory 创建修改响应体的过滤器
ModifyResponseBodyGatewayFilterFactory filterFactory = new ModifyResponseBodyGatewayFilterFactory();
GatewayFilter modifyResponseBodyFilter = filterFactory.apply(rewriteFunction);
// 执行过滤器链
return modifyResponseBodyFilter.filter(exchange, chain);
}
return chain.filter(exchange);
}
}
总结
一定要多思考,如果人永远待在舒适圈的话,人永远不会成长。共勉
觉得作者写的不错的,值得你们借鉴的话,就请点一个免费的赞吧!这个对我来说真的很重要。૮(˶ᵔ ᵕ ᵔ˶)ა