文章目录
1. 什么是Spring Cloud Gateway?
Spring Cloud Gateway 是 Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul
。相比 Zuul
来说,Spring Cloud Gateway 提供更优秀的性能,更强大的有功能。
Spring Cloud Gateway是由 WebFlux
+ Netty
+ Reactor
实现的响应式的 API
网关。它不能在传统的 servlet
容器中工作,也不能构建成 war
包。
Gateway 的核心概念如下:
- 路由(
route
) :- 路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
- 断言(
predicates
) :- Java8中的断言函数,允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
- 过滤器(
Filter
)- SpringCloud Gateway中的filter分为
Gateway FilIer
和Global Filter
。Filter可以对请求和响应进行处理。
- SpringCloud Gateway中的filter分为
路由route
是想要访问的目标地址,断言predicates
是匹配请求中的一些信息,满足条件才允许访问目标地址,而Gateway FilIer
网关过滤器可以为请求做某些扩展,比如添加一些请求头、请求参数等等,但Gateway FilIer
只能作用于对应路由下的请求!
而GlobalFilter
全局过滤器和 GatewayFilter
网关过滤器虽然有一样的接口定义,只不过, GlobalFilter
会作用于所有路由。
Gateway 的工作原理如下:
其实Gateway
对请求的处理与spring mvc
的处理差不多!也是一套流程下来的
框架 | Gateway | spring mvc |
---|---|---|
请求分发 | DispatcherHandler | DispatcherServlet |
请求映射 | HandlerMapping | HandlerMapping |
请求适配 | HanderAdaper | HanderAdaper |
请求处理 | WebHander | Hander |
2. Gateway与zuul的区别
Spring Cloud Gateway是Spring Cloud 的一个子项目。而zuul
则是netflix
公司的项目,只是spring将zuul集成在 Spring Cloud 中使用而已。因为zuul2.0
连续跳票和zuul1.0
的性能表现不是很理想,所以催生了spring团队开发了Gateway
项目。
Zuul:
- 使用的是阻塞式的 API,不支持长连接,比如
websockets
。 - 底层是
servlet
,Zuul处理的是http
请求 - 没有提供异步支持,流控等均由
hystrix
支持。 - 依赖包
spring-cloud-starter-netflix-zuul
。
Gateway:
- Spring Boot和Spring Webflux提供的
Netty
底层环境,不能和传统的Servlet
容器一起使用,也不能打包成一个war
包。 - 依赖
spring-boot-starter-webflux
和spring-cloud-starter-gateway
- 提供了异步支持,提供了抽象负载均衡,提供了抽象流控,并默认实现了
RedisRateLimiter
。
相同点:
- 底层都是
servlet
- 两者均是
web
网关
不同点:
-
适用性
- gateway 对比 zuul 多依赖了spring-webflux,在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件
- zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等。
-
是否支持异步
zuul
仅支持同步gateway
支持异步。理论上gateway
则更适合于提高系统吞吐量(但不一定能有更好的性能),最终性能还需要通过严密的压测来决定
-
框架设计的角度
- gateway具有更好的扩展性,并且其已经发布了2.0.0的RELESE版本,稳定性也是非常好的
-
性能
gateway
是基于spring-webflux的响应式、非阻塞编程,且支持异步,底层通过netty
通信,性能较高。与Spring紧密集成,所以将会是一个更好的开发体验。Zuul 1.x
,是一个基于阻塞io的API Gateway。Zuul已经发布了Zuul 2.x
,基于Netty,也是非阻塞的,支持长连接,但Spring Cloud暂时还没有整合计划。
3. Gateway的配置和使用
- ①:引入依赖
注意:由于Gateway
不能在传统的 servlet 容器中工作,也不能构建成 war 包,会和spring-webmvc
的依赖冲突,所以不能引入web
环境依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- gateway网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- ②:编写
yml
配置文件
spring:
application:
name: mall-gateway
# 配置nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
# 默认为false,设为true开启通过微服务创建路由的功能,即可以通过微服务名访问服务
# http://localhost:8888/mall-order/order/findOrderByUserId/1
enabled: false
# 是否开启网关
enabled: true
# 网关配置跨域
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTION
# 设置路由:路由id、路由到微服务的uri、断言
routes:
- id: order_route #路由ID,全局唯一,建议配合服务名
# # uri: http://localhost:8020 #目标微服务的请求地址和端口
uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer
predicates:
# # Path路径匹配
- Path=/order/**
# 测试 http://localhost:8888/order/findOrderByUserId/1
# 匹配在指定的日期时间之后发生的请求 入参是ZonedDateTime类型
- After=2021-05-16T20:50:57.511+08:00[Asia/Shanghai]
# Cookie匹配
- Cookie=username, fox
# Header匹配 请求中带有请求头名为 x-request-id,其值与 \d+ 正则表达式匹配
- Header=X-Request-Id, \d+
#自定义CheckAuth断言工厂
- name: CheckAuth
args:
name: fox
- CheckAuth=abc
#配置过滤器工厂
filters:
- AddRequestHeader=X-Request-color, red #添加请求头
- AddRequestParameter=color, blue # 添加请求参数
- PrefixPath=/mall-order # 添加前缀 对应微服务需要配置context-path
#- RedirectTo=302, http://baidu.com #重定向到百度
- CheckAuth=aaa,男 #配置自定义的过滤器工厂
- id: user_route
uri: lb://mall-user #lb 整合负载均衡器ribbon,loadbalancer
predicates:
- Path=/user/**
启动应用!配置即可生效
①:常用的路由断言工厂
更多路由断言工厂请参考spring官网 https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories,下面仅列出几种常用的断言类型!
-
时间匹配:可以用在限时抢购的一些场景中。
# 匹配在指定的日期时间之后发生的请求 入参是ZonedDateTime类型 - After=2021-01-31T22:22:07.783+08:00[Asia/Shanghai]
其中 获取
ZonedDateTime
类型的指定日期时间代码如下:ZonedDateTime zonedDateTime = ZonedDateTime.now();//默认时区 // 用指定时区获取当前时间 ZonedDateTime zonedDateTime2 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
-
Cookie匹配:请求头必须加上 Cookie :aaa,否则请求失败!
# Cookie匹配 - Cookie=username, aaa
-
Header匹配:请求头必须加上 X-Request-Id:2(整数),否则请求失败!
# Header匹配 请求中带有请求头名为 x-request-id,其值与 \d+ 正则表达式匹配 - Header=X-Request-Id, \d+
-
路径匹配:只有是 order/** 的请求,才能通过断言
# 测试:http://localhost:8888/order/findOrderByUserId/1 - Path=/order/** #Path路径匹配
-
自定义路由断言工厂
上面所有的断言配置都继承自AbstractPredicateFactory
抽象工厂,如下所示:
所以当我们有个性化需求需要自定义自定义路由断言工厂时,也可参照上述配置进行定制,需要继承AbstractRoutePredicateFactory
类,重写apply
方法的逻辑。在apply
方法中可以通过exchange.getRequest()
拿到ServerHttpRequest
对象,从而可以获取到请求的参数、请求方式、请求头等信息。注意: 命名需要以
RoutePredicateFactory
结尾自定义断言工厂:判断参数
name
是否是aaa
!/** * 自定义RoutePredicateFactory */ @Component @Slf4j public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> { public CheckAuthRoutePredicateFactory() { super(Config.class); } @Override public Predicate<ServerWebExchange> apply(Config config) { return new GatewayPredicate() { @Override public boolean test(ServerWebExchange serverWebExchange) { log.info("调用CheckAuthRoutePredicateFactory" + config.getName()); if(config.getName().equals("aaa")){ return true; } return false; } }; } /** * 快捷配置 * @return */ @Override public List<String> shortcutFieldOrder() { return Collections.singletonList("name"); } /** * 需要定义一个内部类,该类用于封装application.yml中的配置 */ public static class Config { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } }
yml
中配置predicates: - Path=/order/** # 自定义CheckAuth断言工厂 - name: CheckAuth args: name: aaa # 自定义CheckAuth断言工厂快捷配置! 二选一 # - CheckAuth=aaa
②:常用的过滤器工厂(GatewayFilters)
SpringCloudGateway
内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加或去除参数等
-
添加请求头
filters: - AddRequestHeader=X-Request-color, red #添加请求头
-
添加请求参数
filters: - AddRequestParameter=color, blue # 添加请求参数
-
为匹配的路由统一添加前缀
filters: - PrefixPath=/mall-order # 添加前缀 比如某个微服务需要配置context-path
-
重定向操作
filters: - RedirectTo=302, http://baidu.com #重定向到百度
-
自定义过滤器工厂
与自定义断言工厂类似,自定义过滤器工厂需要继承AbstractNameValueGatewayFilterFactory
抽象类,且自定义类名必须要以GatewayFilterFactory
结尾并交给spring
管理。重写apply
方法后如下:自定义过滤器工厂:为当前路由下的请求添加参数
name
、sex
参数,参数值为aaa
,男
!@Component @Slf4j public class CheckAuthGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override public GatewayFilter apply(NameValueConfig config) { return (exchange, chain) -> { log.info("调用CheckAuthGatewayFilterFactory===" + config.getName() + ":" + config.getValue()); // TODO return chain.filter(exchange); }; } }
yml
配置spring: cloud: gateway: routes: - id: order_route #路由ID,全局唯一,建议配合服务名 uri: lb://mall-order #lb 整合负载均衡器ribbon,loadbalancer filters: - CheckAuth=aaa,男 #配置自定义的过滤器工厂
③:全局过滤器配置(Global Filters)
GlobalFilter
接口和 GatewayFilter
有一样的接口定义,只不过 GlobalFilter
会作用于所有路由。比如:LoadBalancerClientFilter
负载均衡过滤器 lb://mall-order
。自定义的全局过滤器需要实现GlobalFilter
接口,并重写filter
方法!
注意:全局过滤器(Global Filters
)不需要在yml
中配置!!
官方声明:GlobalFilter的接口定义以及用法在未来的版本可能会发生变化。
-
自定义校验token的全局过滤器
@Component @Order(-1) @Slf4j public class CheckAuthFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //校验请求头中的token List<String> token = exchange.getRequest().getHeaders().get("token"); log.info("token:"+ token); if (token.isEmpty()){ return chain.filter(exchange); } // TODO token校验 return chain.filter(exchange); } }
-
自定义
IP
全局过滤器,黑白名单@Component @Slf4j public class CheckIPFilter implements GlobalFilter, Ordered { // 执行顺序控制! @Override public int getOrder() { return 0; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { HttpHeaders headers = exchange.getRequest().getHeaders(); //模拟对 IP 的访问限制,即不在 IP 白名单中就不能调用的需求 if (getIp(headers).equals("127.0.0.1")) { log.info("======非法访问======"); ServerHttpResponse response = exchange.getResponse(); byte[] bytes = new String("======非法访问======").getBytes(); response.setStatusCode(HttpStatus.NOT_ACCEPTABLE); DataBuffer buffer = response.bufferFactory().wrap(bytes); response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); return exchange.getResponse().writeWith(Mono.just(buffer)); } return chain.filter(exchange); } private String getIp(HttpHeaders headers) { return headers.getHost().getHostName(); } }
测试结果: 如果请求
ip
为127.0.0.1
则不允许访问!如果是localhost
就可以访问!
4. 网关高可用解决方案
为了保证 Gateway
的高可用性,可以同时启动多个 Gateway
实例进行负载,在 Gateway
的上游使用 Nginx
进行负载转发以达到高可用。