一、网关简介
提到网关就不得不提Zuul,其实Spring Cloud Gateway 是 Zuul 网关的替代者。只所以弃用 Zuul 并不是因为 Zuul 在功能有什么大的问题。而是因为最开始的 Zuul 是开源的,所以 Spring Cloud 就集成了 Zuul 做网关。但后来 Zuul 又宣布闭源,所以 Spring Cloud 自己开发了 Spring Cloud Gateway。再后来Zuul 2.0又开源了,但 Spring Cloud 不再集成它了。
两者的对比如下:
1、Zuul:Zuul 是由 Netflix 开源的 API 网关,基于 servlet 的,使用阻塞 API,它不支持任何长连接。
2、Spring Cloud Gateway:Spring Cloud Gateway 是 Spring Cloud 自己开发的开源 API 网关,建立在 Spring5,Reactor和 Spring Boot 2 之上,使用非阻塞 API,支持异步开发。
Spring Cloud Gateway中有三个非常重要的概念:route路由、predicate断言、filter过滤器。
1、route路由:route路由是网关最基本的组成,由一个路由id、一个目标uri、一组断言工厂及一组filter组成,若断言为true,则请求将经由filter最终路由到目标uri。
2、predicate断言:断言即一个判断条件,根据当前的http请求进行指定规则的匹配,比如说http请求头、请求时间等,只有当匹配上规则时,断言才为true,此时请求才会被路由到目标uri,或者是先路由到过滤链,经过过滤链的层层处理后,最终路由到目标uri。
3、filter过滤器:对请求处理的逻辑部分,当请求的断言为true时,才会被路由到设置好的过滤器,以对请求进行处理,例如:可以为请求添加一个请求头、或者为请求添加一个参数、再或者对uri地址进行修改等,总之就是对请求进行处理。
二、网关入门案例
(一)静态路由入门案例
这里模拟路由到百度。实现方式有两种:使用配置文件进行配置和在启动类上进行配置。
1、使用配置文件配置
(1)创建工程
创建一个工程,添加actuator和getway依赖
<!--actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--gateway 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
这里需要特殊说明一下,这里不能依赖spring-boot-starter-web依赖,否则会报异常。
(2)配置文件
可以看到,目标id是https://baidu.com,拦截的请求路径是 /b
spring:
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Path=/**
(3)验证
2、使用启动类配置
@SpringBootApplication
public class GetwayConfigApplication {
public static void main(String[] args) {
SpringApplication.run(GetwayConfigApplication.class, args);
}
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
return locatorBuilder.routes().route(predicateSpec ->
predicateSpec.path("/b").uri("").id("bkyn_route")).build();
}
}
(二)负载均衡入门案例
1、配置文件方式
(1)添加依赖
<!--nacos discovery依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)修改配置文件
配置文件主要是要配置路由策略和开启注册发现。
spring:
application:
name: getewayconfig
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Path=/a
- id: ribbon_route
uri: lb://provider02Nacosconfig
predicates:
- Path=/provider/depart/**
discovery:
locator:
enabled: true
nacos:
discovery:
server-addr: 172.20.10.2:8840,172.20.10.2:8845,172.20.10.2:8849
(3)配置负载均衡策略
@SpringBootApplication
public class GetwayConfigApplication {
public static void main(String[] args) {
SpringApplication.run(GetwayConfigApplication.class, args);
}
@Bean
public IRule loadBalancerRule(){
return new RandomRule();
}
}
(4)验证
2、配置启动类方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/b").uri("").id("bkyn_route"))
.route(predicateSpec -> predicateSpec.path("/provider/depart/**").uri("lb://provider02Nacosconfig").id("ribbon_route"))
.build();
}
三、Gateway工作原理
Spring Cloud Gateway 的核心处理流程如下图,左边为官方提供High Level 流程图,右边详细流程图。
主要流程:
1、Gateway的客户端回向Spring Cloud Gateway发起请求
2、首先请求会被HttpWebHandlerAdapter进行提取组装成网关的上下文,然后网关的上下文会传递到DispatcherHandler。
3、DispatcherHandler是所有请求的分发处理器,DispatcherHandler主要负责分发请求对应的处理器,比如将请求分发到对应RoutePredicateHandlerMapping(路由断言处理器映射器)。
4、RoutePredicateHandlerMapping路由断言处理映射器主要用于路由的查找,以及找到路由后返回对应的FilteringWebHandler,如果没有找到则丢弃处理。
5、FilteringWebHandler主要负责组装Filter链表并调用Filter执行一系列Filter处理
6、然后把请求转到后端对应的代理服务【Proxied Service】处理
7、处理完毕后,将Response返回到Gateway客户端。
在Filter链中,通过虚线分割Filter的原因是,过滤器可以在转发请求之前处理或者接收到被代理服务的返回结果之后处理。所有的Pre类型的Filter执行完毕之后,才会转发请求到被代理的服务处理。被代理的服务把所有请求完毕之后,才会执行Post类型的过滤器。
四、网关路由断言工厂
Spring Cloud Gateway 将路由匹配作为最基本的功能。而这个功能是通过路由断言工厂完成的。SpringCloud Gateway 中包含了许多内置的路由断言工厂。所有这些断言都可以匹配 HTTP 请求的不同属性,并且可以根据逻辑与状态,将多个路由断言工厂复合使用。路由断言工厂的使用方式有两种:配置式与API 式。配置式是指yml配置文件,API式即启动类中设置。
(一)Path路由断言工厂
该断言工厂用于判断请求路径中是否包含指定的uri。若包含,则匹配成功,断言为true,此时会将该匹配上的 uri 拼接到要转向的目标 uri 的后面,形成一个统一的 uri。
上面的入门案例就是Path路由断言工厂。
(二)时间类断言工厂(After、Before、Between)
时间类断言工厂有After断言工厂、Before断言工厂、Between断言工厂三种。
以After断言工厂为例,After断言工厂的参数是一个 UTC 格式的时间。其会将请求访问到 Gateway 的时间与该参数时间对比。若请求时间在参数时间之后,则匹配成功,断言为 true。
还是以上述的Path路由断言工厂为基础:在设置一个After路由断言工厂。
spring:
application:
name: getewayconfig
cloud:
gateway:
routes:- id: ribbon_route
uri: lb://provider02Nacosconfig
predicates:
- Path=/provider/depart/**
- After=2022-10-01T17:42:47.789-07:00[Asia/Shanghai]
API式配置
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
ZonedDateTime time = LocalDateTime.now().minusDays(5).atZone(ZoneId.systemDefault());
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/b").uri().id("bkyn_route"))
.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().after(time).uri("lb://provider02Nacosconfig").id("ribbon_route"))
.build();
}
可以看到,如果一个请求中需要用到多个断言工厂,就需要使用and进行增加。
其他两种时间类断言工厂用法和After一致,配置方式如下:
配置文件方式:
spring:
application:
name: getewayconfig
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Path=/a
- id: ribbon_route
uri: lb://provider02Nacosconfig
predicates:
- Path=/provider/depart/**
#- After=2022-10-01T17:42:47.789-07:00[Asia/Shanghai]
#- Before=2022-10-01T17:42:47.789-07:00[Asia/Shanghai]
- Between=2021-10-01T17:42:47.789-07:00[Asia/Shanghai],2022-11-01T17:42:47.789-07:00[Asia/Shanghai]
API方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
ZonedDateTime time = LocalDateTime.now().minusDays(5).atZone(ZoneId.systemDefault());
ZonedDateTime time2 = LocalDateTime.now().plusDays(5).atZone(ZoneId.systemDefault());return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/b").uri(").id("bkyn_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().after(time).uri("lb://provider02Nacosconfig").id("ribbon_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().before(time).uri("lb://provider02Nacosconfig").id("ribbon_route"))
.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().between(time,time2).uri("lb://provider02Nacosconfig").id("ribbon_route"))
.build();
}
(三) Cookie路由断言工厂
该断言工厂中包含两个参数,分别是 cookie 的 key 与 value。当请求中携带了指定 key与 value 的cookie 时,匹配成功,断言为 true。
配置文件方式:
spring:
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Path=/a
- id: ribbon_route
uri: lb://provider02Nacosconfig
predicates:
- Path=/provider/depart/**
#- After=2022-10-01T17:42:47.789-07:00[Asia/Shanghai]
#- Before=2022-10-01T17:42:47.789-07:00[Asia/Shanghai]
- Between=2021-10-01T17:42:47.789-07:00[Asia/Shanghai], 2022-11-01T17:42:47.789-07:00[Asia/Shanghai]
- Cookie=lcl,mm
API方式:
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
ZonedDateTime time = LocalDateTime.now().minusDays(5).atZone(ZoneId.systemDefault());
ZonedDateTime time2 = LocalDateTime.now().plusDays(5).atZone(ZoneId.systemDefault());
System.out.println("==========="+ time);
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/b").uri(").id("bkyn_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().after(time).uri("lb://provider02Nacosconfig").id("ribbon_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().before(time).uri("lb://provider02Nacosconfig").id("ribbon_route"))
.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().between(time,time2).and().cookie("lcl","mm").uri("lb://provider02Nacosconfig").id("ribbon_route"))
.build();
}
(四)Header路由断言工厂
该断言工厂中包含两个参数,分别是请求头 header 的 key 与 value。当请求中携带了指定 key 与 value的 header 时,匹配成功,断言为 true。
配置文件方式:
spring:
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Path=/a
- id: ribbon_route
uri: lb://provider02Nacosconfig
predicates:
- Path=/provider/depart/**
#- After=2022-10-01T17:42:47.789-07:00[Asia/Shanghai]
#- Before=2022-10-01T17:42:47.789-07:00[Asia/Shanghai]
- Between=2021-10-01T17:42:47.789-07:00[Asia/Shanghai], 2022-11-01T17:42:47.789-07:00[Asia/Shanghai]
- Cookie=lcl,mm
- Header=demokey,demovalue
API方式:
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
ZonedDateTime time = LocalDateTime.now().minusDays(5).atZone(ZoneId.systemDefault());
ZonedDateTime time2 = LocalDateTime.now().plusDays(5).atZone(ZoneId.systemDefault());
System.out.println("==========="+ time);
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/b").uri("").id("bkyn_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().after(time).uri("lb://provider02Nacosconfig").id("ribbon_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().before(time).uri("lb://provider02Nacosconfig").id("ribbon_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().between(time,time2).uri("lb://provider02Nacosconfig").id("ribbon_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().between(time,time2).and().cookie("lcl","mm").uri("lb://provider02Nacosconfig").id("ribbon_route"))
.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().between(time,time2).and().cookie("lcl","mm").and().header("demokey","demovalue").uri("lb://provider02Nacosconfig").id("ribbon_route"))
.build();
}
(五)Host路由断言工厂
该断言工厂中包含的参数是请求头中的 Host 属性。当请求中携带了指定的 Host 属性值时,匹配成功,断言为 true。
修改hosts文件:修改 /etc中的 hosts 文件,为 127.0.0.1 这个 ip 指定多个主机名。例如,在该文件中添加如下内容:
#gateway测试
127.0.0.1 mypc
配置文件方式:
spring:
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Path=/a
- id: ribbon_route
uri: lb://provider02Nacosconfig
predicates:
- Path=/provider/depart/**
#- After=2022-10-01T17:42:47.789-07:00[Asia/Shanghai]
#- Before=2022-10-01T17:42:47.789-07:00[Asia/Shanghai]
- Between=2021-10-01T17:42:47.789-07:00[Asia/Shanghai], 2022-11-01T17:42:47.789-07:00[Asia/Shanghai]
- Cookie=lcl,mm
- Header=demokey,demovalue
- Host=mypc:9001
需要注意,这里配置的host需要有端口号,这样就只能使用mypc这个host访问了,localhost不能再访问了。多个host使用逗号分割。
API方式:
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
ZonedDateTime time = LocalDateTime.now().minusDays(5).atZone(ZoneId.systemDefault());
ZonedDateTime time2 = LocalDateTime.now().plusDays(5).atZone(ZoneId.systemDefault());
System.out.println("==========="+ time);
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/b").uri("").id("bkyn_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().after(time).uri("lb://provider02Nacosconfig").id("ribbon_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().before(time).uri("lb://provider02Nacosconfig").id("ribbon_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().between(time,time2).uri("lb://provider02Nacosconfig").id("ribbon_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().between(time,time2).and().cookie("lcl","mm").uri("lb://provider02Nacosconfig").id("ribbon_route"))
//.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().between(time,time2).and().cookie("lcl","mm").and().header("demokey","demovalue").uri("lb://provider02Nacosconfig").id("ribbon_route"))
.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().between(time,time2).and().cookie("lcl","mm").and().header("demokey","demovalue").and().host("mypc:9001").uri("lb://provider02Nacosconfig").id("ribbon_route"))
.build();
}
(六)Query路由断言工厂
该断言工厂用于从请求中查找指定的请求参数。其可以只查看参数名称,也可以同时查看参数名与参数值。当请求中包含了指定的参数名,或名值对时,匹配成功,断言为 true。
配置文件方式
spring:
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Path=/a
- id: ribbon_route
uri: lb://provider02Nacosconfig
predicates:
- Path=/provider/depart/**
- Query=name
- Query=age, 18
上面的配置,必须要有name参数,同时age参数的值必须为18才可以。
API配置方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
ZonedDateTime time = LocalDateTime.now().minusDays(5).atZone(ZoneId.systemDefault());
ZonedDateTime time2 = LocalDateTime.now().plusDays(5).atZone(ZoneId.systemDefault());
System.out.println("==========="+ time);
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/b").uri("").id("bkyn_route"))
.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().query("name").and().query("age","18").uri("lb://provider02Nacosconfig").id("ribbon_route"))
.build();
}
(七)RemoteAddr路由断言工厂
该断言工厂用于判断请求提交的所要访问的 IP 地址是否在断言中指定的 IP 范围。当请求中的 IP 在指定范围时,匹配成功,断言为 true。
配置文件配置方式
spring:
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Path=/a
- id: ribbon_route
uri: lb://provider02Nacosconfig
predicates:
- Path=/provider/depart/**
- RemoteAddr=172.20.10.1/30
API配置方式:
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
ZonedDateTime time = LocalDateTime.now().minusDays(5).atZone(ZoneId.systemDefault());
ZonedDateTime time2 = LocalDateTime.now().plusDays(5).atZone(ZoneId.systemDefault());
System.out.println("==========="+ time);
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/b").uri("").id("bkyn_route"))
.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().remoteAddr("172.20.10.1/30").uri("lb://provider02Nacosconfig").id("ribbon_route"))
.build();
}
(八)Method路由断言工厂
该断言工厂用于判断请求是否使用了指定的请求方法,是 POST,还是 GET 等。当请求中使用了指定的请求方法时,匹配成功,断言为 true。
配置文件方式
spring:
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Path=/a
- id: ribbon_route
uri: lb://provider02Nacosconfig
predicates:
- Path=/provider/depart/**
- Method=POST, GET
API方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
ZonedDateTime time = LocalDateTime.now().minusDays(5).atZone(ZoneId.systemDefault());
ZonedDateTime time2 = LocalDateTime.now().plusDays(5).atZone(ZoneId.systemDefault());
System.out.println("==========="+ time);
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/b").uri("").id("bkyn_route"))
.route(predicateSpec -> predicateSpec.path("/provider/depart/**").and().method("GET","POST").uri("lb://provider02Nacosconfig").id("ribbon_route"))
.build();
}
五、网关过滤器Filter
过滤器允许以某种方式修改传入的 HTTP 请求或返回的 HTTP 响应。而过滤器作用域是某些特定路由。
过滤器允许以某种方式修改传入的 HTTP 请求或返回的 HTTP 响应。Filter 根据其作用范围的不同,分为两种:局部过滤器与全局过滤器。
局部过滤器:应用于单个路由策略上,其功能仅对路由断言为 true 的请求起作用
全局过滤器:应用于所有路由策略上,其功能就像前面的配置文件中设置的默认 Filter
Spring Cloud Gateway 包括许多内置的 GatewayFilter 工厂。GatewayFilter 工厂的使用方式有两种:配置式与 API 式。
(一)AddRequestHeader过滤工厂
该过滤器工厂会对匹配上的请求添加指定的 header。
配置文件方式
spring:
cloud:
gateway:
routes:
- id: baidu_route
uri: https://www.baidu.com
predicates:
- Path=/a
- id: ribbon_route
uri: lb://provider02Nacosconfig
predicates:
- Path=/provider/depart/**
- id: header_filter
uri: http://localhost
predicates:
- Path=/demo
filters:
-AddRequestHeader=demokey, demovalue
API方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/demo").filters(fs -> fs.addRequestHeader("demokey","demovalue")).uri("http://localhost").id("header_filter"))
.build();
}
(二)AddRequestParameter过滤工厂
该过滤器工厂会对匹配上的请求添加指定的请求参数。
配置文件方式
spring:
cloud:
gateway:
routes:
- id: header_filter
uri: http://localhost:9000/demo/header
predicates:
- Path=/
filters:
- AddRequestHeader=demokey, demovalue
- AddRequestParamter=name, lcl
API方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/demo").filters(fs -> fs.addRequestParameter("name","lcl")).uri("http://localhost").id("header_filter"))
.build();
}
(三)AddResponseHeader过滤工厂
该过滤器工厂会给从网关返回的响应添加上指定的 header。
配置文件方式
spring:
cloud:
gateway:
routes:
- id: header_filter
uri: http://localhost:9000/demo/header
predicates:
- Path=/
filters:
- AddRequestHeader=demokey, demovalue
- AddRequestParamter=name, lcl
- AddResponseHeader=demokey, demovalue2
API方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/demo").filters(fs -> fs.addResponseHeader("demokey","demovalue2")).uri("http://localhost").id("header_filter"))
.build();
}
(四)PrefixPath过滤工厂
该过滤器工厂会为指定的 URI 自动添加上一个指定的 URI 前辍。
配置文件方式
spring:
cloud:
gateway:
routes:
- id: header_filter
uri: http://localhost:9000/demo/header
predicates:
- Path=/
filters:
- AddRequestHeader=demokey, demovalue
- AddRequestParamter=name, lcl
- AddResponseHeader=demokey, demovalue2
- PfixPath=/demo
API方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/demo").filters(fs -> fs.prefixPath("demo")).uri("http://localhost").id("header_filter"))
.build();
}
(五)StripPrefix过滤工厂
该过滤器工厂会为指定的 URI 去掉指定长度的前辍。
配置文件方式
spring:
cloud:
gateway:
routes:
- id: header_filter
uri: http://localhost:9000/demo/header
predicates:
- Path=/
filters:
- AddRequestHeader=demokey, demovalue
- AddRequestParamter=name, lcl
- AddResponseHeader=demokey, demovalue2
- PfixPath=/demo
- StripPrefix=2
API方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/demo").filters(fs -> fs.stripPrefix(2)).uri("http://localhost").id("header_filter"))
.build();
}
(六)RewritePath过滤工厂
该过滤器工厂会将请求 URI 替换为另一个指定的 URI 进行访问。RewritePath 有两个参数,第一个是正则表达式,第二个是要替换为的目标表达式。
配置文件方式
spring:
cloud:
gateway:
routes:
- id: header_filter
uri: http://localhost:9000/demo/header
predicates:
- Path=/
filters:
- AddRequestHeader=demokey, demovalue
- AddRequestParamter=name, lcl
- AddResponseHeader=demokey, demovalue2
- PfixPath=/demo
- StripPrefix=2
- RewritePath=/red(?<segment>/?.*), ${segment}
API方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/demo").filters(fs -> fs.rewritePath("/red(?<segment>/?.*)", "${segment}")).uri("http://localhost").id("header_filter"))
.build();
}
(七)全局过滤器
前面的 GatewayFilter 工厂是在某一特定路由策略中设置的,仅对这一种路由生效。若要使某些过滤效果应用到所有路由策略中,就可以将该 GatewayFilter 工厂定义在默认 Filters中。修改 gateway 工程配置文件。
spring:
cloud:
gateway:
default-filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fallback
对于过滤器,局部过滤器优先级要高于全局的过滤器,如果同时存在相同的配置过滤器和API过滤器,配置方式的过滤器优先级要高于API方式的过滤器。
在自定义了一些 GlobalFilter 后,为了保证这些Filter 的执行顺序,在每个返回 GlobalFilter 的@Bean方法上添加@Order,或直接使自定义的 GlobalFilter 类实现 Order 接口,就可以修改Filter的执行顺序。
(八)自定义过滤器
前面的 GatewayFilter 工厂可以创建出某种特定的 Filter 过滤效果,但这些过滤功能可能并不能满足全部业务需求,此时可以根据具体需求自定义自己的 Filter。
1、自定义局部过滤器
(1)自定义三个过滤器
@Slf4j
public class OneGatewayFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long startTime = System.currentTimeMillis();
log.info("pre-filter-1=========={}", startTime);
exchange.getAttributes().put("startTime", startTime);
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("========== post filter 1 =======");
long startTime2 = exchange.getAttribute("startTime");
log.info(" filter 1 run time ======= {}", System.currentTimeMillis() - startTime2);
}));
}
}
@Slf4j
public class TwoGatewayFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("pre-filter-2=========={}");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info(" filter 2 run time ======= {}", System.currentTimeMillis());
}));
}
}
@Slf4j
public class ThreeGatewayFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("pre-filter-3=========={}");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info(" filter 3 run time ======= {}", System.currentTimeMillis());
}));
}
}
(2)修改启动类
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/**")
.filters(fs -> fs.filter(new OneGatewayFilter())
.filter(new TwoGatewayFilter())
.filter(new ThreeGatewayFilter()))
.uri("http://localhost:9000").id("selfFilter"))
.build();
}
(3)输出
2021-10-17 12:45:51.485 INFO 27332 --- [ctor-http-nio-4] c.l.c.a.gateway.filter.OneGatewayFilter : pre-filter-1==========1634445951485
2021-10-17 12:45:51.485 INFO 27332 --- [ctor-http-nio-4] c.l.c.a.gateway.filter.TwoGatewayFilter : pre-filter-2=========={}
2021-10-17 12:45:51.485 INFO 27332 --- [ctor-http-nio-4] c.l.c.a.g.filter.ThreeGatewayFilter : pre-filter-3=========={}
2021-10-17 12:45:51.491 INFO 27332 --- [ctor-http-nio-6] c.l.c.a.g.filter.ThreeGatewayFilter : filter 3 run time ======= 1634445951491
2021-10-17 12:45:51.491 INFO 27332 --- [ctor-http-nio-6] c.l.c.a.gateway.filter.TwoGatewayFilter : filter 2 run time ======= 1634445951491
2021-10-17 12:45:51.491 INFO 27332 --- [ctor-http-nio-6] c.l.c.a.gateway.filter.OneGatewayFilter : ========== post filter 1 =======
2021-10-17 12:45:51.491 INFO 27332 --- [ctor-http-nio-6] c.l.c.a.gateway.filter.OneGatewayFilter : filter 1 run time ======= 6
可以看到,如果多个过滤器,则顺序执行前置执行内容,然后再倒序输出后置内容。
2、自定义全局过滤器
@Component
public class URLValidateFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(StringUtils.isEmpty(token)){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
六、网关熔断&降级
使用Gateway熔断降级,可以使用Hystrix或者CircuitBreaker,其中使用Hystrix需要依赖spring-cloud-starter-netflix-hystrix。而netflix-hystrix可能以后不再有更新了,所以推荐使用Spring Cloud CircuitBreaker GatewayFilter Factory。
1、使用Hystrix配置如下:
spring:
cloud:
gateway:
routes:
- id: ingredients
uri: lb://ingredients
predicates:
- Path=//ingredients/**
filters:
- name: Hystrix
args:
name: fetchIngredients
fallbackUri: forward:/fallback
- id: ingredients-fallback
uri: http://localhost:9994
predicates:
- Path=/fallback
2、使用CircuitBreaker配置如下:
添加依赖
<!--resilience4j 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
<version>2.0.2</version>
</dependency>
添加降级方法
@RestController
public class GatewayFallBackController {
@GetMapping("/fb")
public String fallback(){
return "this is fallback";
}
}
配置文件方式
这个也是可以使用局部配置和全局配置,局部配置如下:
spring:
cloud:
gateway:
routes:
- id: circuitBreaker_filter
uri: http://localhost:9000
predicates:
- Path=/demo/time
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fb
全局配置:
spring:
cloud:
gateway:
default-filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fallback
API方式
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder locatorBuilder){
return locatorBuilder.routes()
.route(predicateSpec -> predicateSpec.path("/demo/time")
.filters(fs -> fs.circuitBreaker(config -> {
config.setName("myCircuitBreaker");
config.setFallbackUri("forward:/fb");}
))
.uri("http://localhost:9000").id("selfFilter"))
.build();
}
七、网关限流
该过滤器工厂会对进来的请求进行限流。这里采用的是令牌桶算法。另外,从这里也可以看出其是基于Redis 实现的,所以需要导入 Redis 的依赖。
(一)限流算法
做限流 (Rate Limiting/Throttling) 的时候,除了简单的控制并发,如果要准确的控制 TPS,简单的做法是维护一个单位时间内的 Counter,如判断单位时间已经过去,则将 Counter 重置零。此做法被认为没有很好的处理单位时间的边界,比如在前一秒的最后一毫秒里和下一秒的第一毫秒都触发了最大的请求数,也就是在两毫秒内发生了两倍的 TPS。常用的更平滑的限流算法有两种:漏桶算法和令牌桶算法。
1、漏桶算法
漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。
可见这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。
2、令牌桶算法
令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解。随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。
令牌桶的另外一个好处是可以方便的改变速度。一旦需要提高速率,则按需提高放入桶中的令牌的速率。一般会定时(比如 100 毫秒)往桶中增加一定数量的令牌,有些变种算法则实时的计算应该增加的令牌的数量。Guava 中的 RateLimiter 采用了令牌桶的算法。
(二)使用令牌桶算法实现网关限流
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
2、使用API的方式设置限流
@Configuration
public class RequestRateLimiterConfig {
@Bean
@Primary
KeyResolver apiKeyResolver() {
//按URL限流,即以每秒内请求数按URL分组统计,超出限流的url请求都将返回429状态
return exchange -> Mono.just(exchange.getRequest().getPath().toString());
}
@Bean
KeyResolver userKeyResolver() {
//按用户限流
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}
@Bean
KeyResolver ipKeyResolver() {
//按IP来限流
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
}
3、使用配置文件方式限流
spring:
redis:
password:
timeout: 1000
database: 0
lettuce:
pool:
max,active: 8
max,wait: -1
max,idle: 8
min,idle: 0
port: 6379
host: 172.20.10.14
cache:
redis:
use,key,prefix: true
key,prefix: dev
cache,null,values: false
time,to,live: 20s
cloud:
gateway:
routes:
- id: circuitBreaker_filter
uri: http://localhost:9000/demo/time
predicates:
- Path=/demo/time
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fb
# 限流过滤器,使用gateway内置令牌算法
- name: RequestRateLimiter
args:
# 令牌桶每秒填充平均速率,即行等价于允许用户每秒处理多少个请求平均数
redis-rate-limiter.replenishRate: 10
# 令牌桶的容量,允许在一秒钟内完成的最大请求数
redis-rate-limiter.burstCapacity: 20
# 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
key-resolver: "#{@apiKeyResolver}"
discovery:
locator:
enabled: true
------------------------------------------------------------------
-----------------------------------------------------------
---------------------------------------------
朦胧的夜 留笔~~