参考:
Spring Cloud Gateway 介绍
6000 字 | 16 图 | 深入理解 Spring Cloud Gateway 的原理
Spring Cloud Gateway 官方文档
SpringCloud 网关组件 Gateway 原理深度解析
(1)API 网关是一个在分布式系统中作为入口的服务器,用于接收所有外部请求,并将这些请求路由到相应的服务端节点上。它是构建微服务架构中的关键组件之一。
(2)API 网关的作用如下:
(3)总之,API 网关充当了微服务架构中客户端和后端服务之间的中间层,集中管理和解耦了微服务的接入和治理。它提供了路由转发、负载均衡、认证授权、缓存和性能优化等功能,简化了系统的复杂性,提供了安全性、可靠性和可扩展性,是构建高效、可靠的分布式系统的重要组件。
(1)以下是几个常见的网关系统:
(2)以上是一些常见的网关系统,它们都提供了类似的基本功能,包括路由转发、负载均衡、认证授权、限流、监控等。选择适合自身需求的网关系统时,可以根据具体的技术栈、性能要求、可扩展性和社区支持等因素进行评估和选择。
(1)Spring Cloud Gateway 属于 Spring Cloud 生态中的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。
(2)Spring Cloud Gateway的主要作用如下:
(3)Spring Cloud Gateway 的优缺点如下:
Spring Cloud Gateway 的工作流程如下图所示,具体步骤为:
上述图片来源于 Spring 官网
在 Spring Cloud Gateway 中,路由 (Route) 是定义请求转发规则的对象。它指定了接收到的请求应该如何进行路由,并将请求转发到指定的目标服务。一个路由由以下几个要素组成:
(2)以下是一个路由的示例配置:
spring: cloud: gateway: routes: - id: example_route uri: http://example.com predicates: - Path=/example/** filters: - AddRequestHeader=X-Request-Id, 1234567890 order: 1
在上述示例中,我们定义了一个 id 为 example_route 的路由,将匹配到路径为 /example/** 的请求转发到 http://example.com。同时,还使用了一个过滤器,将请求头 X-Request-Id 添加到请求中,值为 1234567890。
(3)通过路由的配置,Spring Cloud Gateway 能够根据请求的路径、请求头、请求方法等条件进行请求的转发和过滤。你可以根据具体的业务需求配置不同的路由来实现灵活的请求路由和转发策略。
(1)在 Spring Cloud Gateway 中,断言 (Predicate) 用于定义路由规则的匹配条件。它是一个逻辑函数,可以根据请求的特性进行条件判断,以确定是否匹配该路由规则。
(2)断言的主要作用在于路由匹配,即通过断言对请求的特征进行匹配,确定是否满足该路由规则的条件。常用的断言条件包括请求的 URI、请求的方法、请求的头信息、请求的参数等。只有当请求满足断言的条件时,才会进一步考虑该路由规则的转发。
(3)在配置断言时,可以使用多个断言组合,通过逻辑运算符(如AND、OR、NOT)来定义更复杂的路由规则。常见的内置断言如下:
上述图片来源于网络。
下面是一个示例:
(4)需要注意的是,断言的选择和配置需要结合具体的业务需求和场景来进行,合理定义条件判断能够提高路由的准确性和效率。同时,过多或过于复杂的断言条件可能会导致路由规则的维护和管理复杂化,因此需要根据实际情况进行权衡和选择。
要实现自定义断言,需要创建一个继承了 AbstractRoutePredicateFactory 类的路由断言工厂类,并完成相关配置和逻辑处理。然后,将该自定义断言添加到路由配置中。具体细节如下:
import lombok.Data; import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; /* 这是一个自定义的路由断言工厂类,要求有两个: 1.名字必须是 配置+RoutePredicateFactory 2.必须继承 AbstractRoutePredicateFactory<配置类> */ @Component public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory{ //构造函数 public AgeRoutePredicateFactory() { super(AgeRoutePredicateFactory.Config.class); } //读取配置文件的中参数值,并将其赋值到配置类中的属性上 public List shortcutFieldOrder() { //这个位置的顺序必须跟配置文件中的值的顺序对应 return Arrays.asList("minAge", "maxAge"); } //断言逻辑 public Predicate apply(AgeRoutePredicateFactory.Config config) { return new Predicate () { @Override public boolean test(ServerWebExchange serverWebExchange) { //1.接收前台传入的 age 参数 String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age"); //2.先判断是否为空 if (StringUtils.isNotEmpty(ageStr)) { //3.如果不为空,再进行路由逻辑判断 int age = Integer.parseInt(ageStr); return age <= config.getMaxAge() && age >= config.getMinAge(); } return false; } }; } //配置类,用于接收配置文件中的对应参数 @Data @NoArgsConstructor public static class Config { private int minAge; // 18 private int maxAge; // 60 } }
路由和断言之间的关系如下:
(1)在Spring Cloud Gateway中,过滤器 (Filter) 是用于在请求路由过程中对请求和响应进行处理和修改的组件。过滤器可以在请求被路由到目标服务之前或之后执行一些操作,如校验请求、修改请求头、记录日志、添加认证等。
从请求和响应的角度来划分,过滤器可以以下两种:
从作用范围的角度来划分,过滤器可以以下两种:
(1)全局过滤器 (Global Filter):全局过滤器对所有的路由都生效,可以用于实现全局的请求处理逻辑。常见的全局过滤器如下所示:
全局过滤器最常见的用法是进行负载均衡。配置如下所示:
spring: cloud: gateway: routes: - id: product_route # 第三方微服务路由规则 uri: lb://service-product # 负载均衡,将请求转发到注册中心注册的 service-product 服务 predicates: - Path=/product-serv/** filters: #过滤器 - StripPrefix=1 # 转发之前去掉 1 层路径(这里是去掉 /product-serv)
这里有个关键字 lb,用到了全局过滤器 LoadBalancerClientFilter,当匹配到这个路由后,会将请求转发到 service-product 服务,且支持负载均衡转发,也就是先将 service-product 解析成实际的微服务的 host 和 port,然后再转发给实际的微服务。
(2)局部过滤器 (Gateway Filter):路由过滤器只对指定的路由生效,可以用于实现对特定路由的请求处理逻辑。常见的局部过滤器如下所示:
(1)内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。开发中的鉴权逻辑如下:
(2)如上图,对于验证用户是否已经登录鉴权的过程可以在网关统一检验。检验的标准就是请求中是否携带 token 凭证以及 token 的正确性。下面的我们自定义一个 GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。
package com.itheima.filters; import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; //自定义全局过滤器,作用是统一鉴权,需要实现 GlobalFilter 和 Ordered 接口 @Component public class AuthGlobalFilter implements GlobalFilter, Ordered { //完成判断逻辑 @Override public Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst("token"); if (!StringUtils.equals(token, "admin")) { System.out.println("鉴权失败"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } //调用 chain.filter 继续向下游执行 return chain.filter(exchange); } //顺序,数值越小,优先级越高 @Override public int getOrder() { return 0; } }
要实现自定义局部过滤器,需要创建一个继承了 AbstractGatewayFilterFactory 类的过滤器工厂类,并完成相关配置和逻辑处理。然后,将该自定义自定义局部过滤器添加到路由配置中。具体细节如下:
package com.itheima.filters; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.List; //自定义局部过滤器 @Component public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory{ //构造函数 public LogGatewayFilterFactory() { super(LogGatewayFilterFactory.Config.class); } //读取配置文件中的参数,并将其赋值到配置类中 @Override public List shortcutFieldOrder() { return Arrays.asList("consoleLog", "cacheLog"); } //过滤器逻辑 @Override public GatewayFilter apply(LogGatewayFilterFactory.Config config) { return new GatewayFilter() { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { if (config.isCacheLog()) { System.out.println("cacheLog 已经开启了...."); } if (config.isConsoleLog()) { System.out.println("consoleLog 已经开启了...."); } return chain.filter(exchange); } }; } //配置类,用于接收配置参数 @Data @NoArgsConstructor public static class Config { private boolean consoleLog; private boolean cacheLog; } }
(1)在 SpringBoot 项目中,我们捕获全局异常只需要在项目中配置 @RestControllerAdvice 和 @ExceptionHandler 就可以了。不过,这种方式在 Spring Cloud Gateway 下不适用。Spring Cloud Gateway 中实现自定义全局异常常用的一种方式是实现 ErrorWebExceptionHandler 接口并重写其中的 handle 方法。下面是一个示例:
@Component public class GlobalExceptionHandler implements ErrorWebExceptionHandler { @Override public Monohandle(ServerWebExchange exchange, Throwable ex) { // 自定义异常处理逻辑 // ... } }
(2)需要注意的是,Spring Boot 中的全局异常处理器可以返回 ResponseEntity 或直接通过 @ResponseBody 返回响应体,而 Spring Cloud Gateway 的全局异常处理则需要返回 Mono
(3)需要注意的是,Spring Cloud Gateway 使用 ErrorWebExceptionHandler 来处理异常,而不是 @ExceptionHandler 注解。这是因为 Spring Cloud Gateway 处理请求的过程是一个管道的形式,异常可能会在不同的过滤器中抛出,需要一个统一的异常处理器来处理这些异常。
(4)总结起来,Spring Boot 和 Spring Cloud Gateway 的全局异常处理方式有所不同,主要是基于各自的Web框架特性和响应式编程模型的需求而设计。
相关知识点:
Spring Boot 面试题——全局异常处理
Spring Cloud Gateway 内置了一个 RequestRateLimiter 过滤器,可以基于令牌桶算法实现请求的限流。你可以在路由配置中使用该过滤器来实现限流功能。示例如下:
spring: cloud: gateway: routes: - id: example_route uri: http://example.com predicates: - Path=/example/** filters: - RequestRateLimiter=10
以上示例中,example_route 路由使用了 RequestRateLimiter 过滤器,每秒最多处理 10 个请求。
(1)Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流网关进行限流,并且从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
(2)具体步骤如下:
com.alibaba.csp sentinel-spring-cloud-gateway-adapter
@Configuration public class GatewayConfiguration { private final ListviewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfiguration(ObjectProvider > viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } // 初始化一个限流的过滤器 @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } // 配置初始化的限流参数 @PostConstruct public void initGatewayRules() { Set
rules = new HashSet<>(); rules.add( new GatewayFlowRule("product_route") //资源名称,对应路由id .setCount(1) // 限流阈值 .setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒 ); GatewayRuleManager.loadRules(rules); } // 配置限流的异常处理器 @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } // 自定义限流异常页面 @PostConstruct public void initBlockHandlers() { BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { public Mono handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { Map map = new HashMap<>(); map.put("code", 0); map.put("message", "接口被限流了"); return ServerResponse.status(HttpStatus.OK). contentType(MediaType.APPLICATION_JSON_UTF8). body(BodyInserters.fromObject(map)); } }; GatewayCallbackManager.setBlockHandler(blockRequestHandler); } }
/** * 配置初始化的限流参数 */ @PostConstruct public void initGatewayRules() { Setrules = new HashSet<>(); rules.add(new GatewayFlowRule("product_api1").setCount(1).setIntervalSec(1)); rules.add(new GatewayFlowRule("product_api2").setCount(1).setIntervalSec(1)); GatewayRuleManager.loadRules(rules); } //自定义 API 分组 @PostConstruct private void initCustomizedApis() { Set definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("product_api1") .setPredicateItems(new HashSet () {{ //以 /product-serv/product/api1 开头的请求 add(new ApiPathPredicateItem().setPattern("/product_serv/product/api1/**"). setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); ApiDefinition api2 = new ApiDefinition("product_api2") .setPredicateItems(new HashSet () {{ //以 /product-serv/product/api2/demo1 完成的 url 路径匹配 add(new ApiPathPredicateItem().setPattern("/product_serv/product/api2/demo1")); }}); definitions.add(api1); definitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); }