优质博文:IT-BLOG-CN
Spring Cloud Gateway
作为
Spring Cloud
框架的第二代网关,在功能上要比
Zuul
更加的强大,性能也更好。随着
Spring Cloud
的版本迭代,
Spring Cloud
官方有打算弃用
Zuul
的意思。在笔者调用了
Spring Cloud Gateway
的使用和功能上,
Spring Cloud Gateway
替换掉
Zuul
的成本上是非常低的,几乎可以无缝切换。
Spring Cloud Gateway
几乎包含了
Zuul
的所有功能。
一、网关定义
**
API
网关是一个反向路由,屏蔽内部细节,为调用者提供统一入口,接收所有调用者请求,通过路由机制转发到服务实例。
API
网关是一组“过滤器
Filter
”集合,可以实现一系列与核心业务无关的横切面功能,如安全认证、限流熔断、日志监控**。
网关在系统中所处的位置:
二、快速开始
网关启动步骤(代码演示):
【1】添加依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>
【2】配置文件
spring:cloud:gateway:discovery:# 启用通过服务发现自动创建路由。locator:enabled:trueroutes:-id: example_route #路由ID,根据业务自行定义uri: lb://example-service #目标服务的地址,这里使用 lb:// 前缀来表示负载均衡。可以是 HTTP(s) URI 或其他协议的 URI - http://bin.org:80/get。predicates:- Path=/example/**#predicates: 谓词数组,用于匹配请求。常见的谓词包括 Path、Method、Header 等。filters:#过滤器数组,用于在请求被转发到目标服务之前和之后进行处理。常见的过滤器包括 AddRequestHeader、StripPrefix、RewritePath 等。- AddRequestHeader=X-Example, ExampleValue #过滤器会在请求头中添加 X-Example,值为 ExampleValue。- StripPrefix=1 #过滤器会移除路径中的第一个前缀。例如,请求路径 /example/test 会变成 /test。-id: rate_limited_route
uri: http://ratelimited.org
predicates:- Path=/ratelimited/**filters:- RequestRateLimiter=redis-rate-limiter # 限流:通过 RequestRateLimiter 过滤器实现-id: retry_route
uri: http://retry.org
predicates:- Path=/retry/**filters:- Retry=5 #重试:通过 Retry 过滤器实现。default-filters:#default-filters 是全局过滤器数组,适用于所有路由。这个过滤器会在所有响应中添加 X-Response-Default 头,值为 Default。- AddResponseHeader=X-Response-Default, Default
globalcors:#globalcors 用于配置全局的 CORS(跨域资源共享)设置。corsConfigurations:#corsConfigurations: 定义 CORS 配置的路径模式。'[/**]':#匹配所有路径。allowedOrigins:"*"#允许的源,* 表示允许所有源。allowedMethods:#允许的 HTTP 方法,包括 GET、POST、DELETE 和 PUT。- GET
- POST
- DELETE
- PUT
三、Spring Cloud GateWay 架构图
客户端向
Spring Cloud Gateway
发出请求。 在
Gateway Handler Mapping
中找到请求相对匹配路由(这个时候就用到
predicate
),则将其发送到
Gateway web handler
处理。
handler
处理请求时会经过一系列的过滤器链。 过滤器链被虚线划分的原因是过滤器链可以在发送代理请求之前或之后执行过滤逻辑。 先执行所有
pre
过滤器逻辑,然后进行代理请求。 在发出代理请求之后,收到代理服务的响应之后执行
post
过滤器逻辑。这跟
Zuul
的处理过程很类似。在执行所有
pre
过滤器逻辑时,往往进行了鉴权、限流、日志输出等功能,以及请求头的更改、协议的转换;转发之后收到响应之后,会执行所有
post
过滤器的逻辑,在这里可以响应数据进行了修改,比如响应头、协议的转换等。在上面的处理过程中,有一个重要的点就是将请求和路由进行匹配,这时候就需要用到
predicate
,它是决定了一个请求走哪一个路由。
四、SpringColoud GateWay 核心组件
集合上面的配置和架构图进行说明
**【1】
Route
路由:**
Gateway
的基本构建模块,它由
ID
、目标
URL
、断言集合和过滤器集合组成。如果聚合断言结果为真,则匹配到该路由。
**
Route
路由-动态路由实现原理:**配置变化
Apollo
- 服务地址实例变化
。Nacos
通过Spring Cloud Gateway
和RouteDefinitionLocator
等组件实现动态路由。RouteRefreshListener
先看下配置信息,方便后面原理的理解:
SpringCloudGateway bootstrap.yml
的配置如下:
spring:application:name: gateway-service
cloud:nacos:discovery:server-addr: ${NACOS_SERVER_ADDR:localhost:8848}apollo:bootstrap:enabled:truemeta: ${APOLLO_META:localhost:8080}
application.yml
的配置如下:
spring:cloud:gateway:discovery:locator:enabled:truelower-case-service-id:trueapollo:bootstrap:namespaces: application # 1、登录 Apollo 控制台。 2、创建一个新的配置,例如 application.yml。 3、内容就是上看配置的SpringCloud Gateway 配置的路由信息
1、
RouteDefinitionLocator
:
Spring Cloud Gateway
启动时,会通过
RouteDefinitionLocator
从
Apollo
加载初始的路由定义。
2、
DiscoveryClientRouteDefinitionLocator
:使用
Nacos
进行服务发现,从
Nacos
获取动态路由定义。
3、
RouteDefinitionRepository
:加载的路由定义会存储在
RouteDefinitionRepository
中,供后续路由匹配使用。
4、
RouteRefreshListener
:监听路由定义的变化事件(如配置更新、服务实例变化等)。当监听到路由定义变化事件时,触发路由刷新操作,更新网关的路由规则,重新加载并应用新的路由配置。
GatewayHandlerMapping
根据预先配置的路由信息和请求的属性(如路径、方法、头部信息等)来确定哪个路由与请求匹配。它使用谓词
Predicates
来进行匹配判断。
**【2】
Predicate
断言:** 这是一个
Java 8 Function Predicate
。输入类型是
Spring Framework ServerWebExchange
。允许开发人员匹配来自
HTTP
请求的任何内容,例如
Header
或参数。
Predicate
接受一个输入参数,返回一个布尔值结果。
Spring Cloud Gateway
内置了许多
Predict
,这些
Predict
的源码在
org.springframework.cloud.gateway.handler.predicate
包中,如果读者有兴趣可以阅读一下。现在列举各种 Predicate如下图:
在上图中,有很多类型的
Predicate
,比如说时间类型的
Predicated
[
AfterRoutePredicateFactory BeforeRoutePredicateFactory BetweenRoutePredicateFactory
],当只有满足特定时间要求的请求会进入到此
Predicate
中,并交由
Router
处理;
Cookie
类型的
CookieRoutePredicateFactory
,指定的
Cookie
满足正则匹配,才会进入此
Router
。以及
host
、
method
、
path
、
querparam
、
remoteaddr
类型的
Predicate
,每一种
Predicate
都会对当前的客户端请求进行判断,是否满足当前的要求,如果满足则交给当前请求处理。如果有很多个
Predicate
,并且一个请求满足多个
Predicate
,则按照配置的顺序第一个生效。
Predicate 断言配置:
server:port:8080spring:application:name: api-gateway
cloud:gateway:routes:-id: gateway-service
uri: https://www.baidu.com
order:0predicates:- After=2017-01-20T17:42:47.789-07:00[America/Denver]- Host=**.foo.org- Path=/headers
- Method=GET
- Header=X-Request-Id, \d+
- Query=foo, ba.
- Query=baz
- Cookie=chocolate, ch.p
在上面的配置文件中,配置了服务的端口为
8080
,配置
spring cloud gateway
相关的配置,**
id
标签配置的是
router
的
id
**,每个
router
都需要一个唯一的
id
,**
uri
配置的是将请求路由到哪里**,本案例全部路由到
https://www.baidu.com
。
**
Predicates
:**
After=2017-01-20T17:42:47.789-07:00[America/Denver]
会被解析成
PredicateDefinition
对象
name =After ,args= 2017-01-20T17:42:47.789-07:00[America/Denver]
。需要注意的是
Predicates
的
After
这个配置,遵循契约大于配置的思想,它实际被
AfterRoutePredicateFactory
这个类所处理,这个
After
就是指定了它的
Gateway web handler
类为
AfterRoutePredicateFactory
,同理,其他类型的
Predicate
也遵循这个规则。当请求的时间在这个配置的时间之后,请求会被路由到指定的
URL
。跟时间相关的
Predicates
还有
Before Route Predicate Factory
、
Between Route Predicate Factory
,读者可以自行查阅官方文档,再次不再演示。
**
Query=baz
:**
Query
的值以键值对的方式进行配置,这样在请求过来时会对属性值和正则进行匹配,匹配上才会走路由。经过测试发现只要请求汇总带有
baz
参数即会匹配路由[
localhost:8080?baz=x&id=2
],不带
baz
参数则不会匹配。
Query=foo, ba.
:这样只要当请求中包含
foo
属性并且参数值是以
ba
开头的长度为三位的字符串才会进行匹配和路由。使用
curl
测试,命令行输入:
curl localhost:8080?foo=bab
测试可以返回页面代码,将
foo
的属性值改为
babx
再次访问就会报
404
,证明路由需要匹配正则表达式才会进行路由。
Header=X-Request-Id
,
\d+
:使用
curl
测试,命令行输入:
curl http://localhost:8080 -H "X-Request-Id:88"
则返回页面代码证明匹配成功。将参数
-H "X-Request-Id:88"改为-H "X-Request-Id:spring"
再次执行时返回
404
证明没有匹配。
【3】
Filter
过滤器:方案一:写死在代码中
@BeanpublicRouteLocatorcustomRouteLocator(RouteLocatorBuilder builder){return builder.routes()//openapi路由转发.route("openapi_route", p -> p.path("/openapi/**").filters(f->f.removeRequestHeader("Expect")).uri("lb://order-openapi-service")).build();}
方案二:配置文件
yml
# gateway 的配置形式routes:-id: order-service #路由ID,没有规定规则但要求唯一,建议配合服务名。uri: lb://order-service
predicates:- Path=/order/**filters:- ValidateCodeGatewayFilter
**
Filter
过滤器:
Filter
按处理顺序
Pre Filter / Post Filter
**
**
Filter
按作用范围分为:**
GlobalFilter
全局过滤器。
GatewayFilter
指定路由的过滤器。
**
Filter
过滤器-扩展自定义
Filter
:**
Filter
支持通过
spi
扩展。实现
GatewayFilter
,
Ordered
接口。
**
Filter
方法:** 过滤器处理逻辑。
getOrder
:定义优先级,值越大优先级越低。
全局过滤器示例: 创建一个全局过滤器类,实现
GlobalFilter
接口:
publicclassTokenFilterimplementsGlobalFilter,Ordered{Logger logger=LoggerFactory.getLogger(TokenFilter.class);@OverridepublicMono<Void>filter(ServerWebExchange exchange,GatewayFilterChain chain){String token = exchange.getRequest().getQueryParams().getFirst("token");if(token ==null|| token.isEmpty()){
logger.info("token is empty...");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}return chain.filter(exchange);}@OverridepublicintgetOrder(){// // 过滤器的执行顺序,值越小优先级越高return-100;}}
自定义路由过滤器示例: 创建自定义的路由过滤器,可以实现
GatewayFilter
接口:
importorg.springframework.cloud.gateway.filter.GatewayFilter;importorg.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;importorg.springframework.stereotype.Component;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;@ComponentpublicclassMyCustomFilterextendsAbstractGatewayFilterFactory<MyCustomFilter.Config>{publicMyCustomFilter(){super(Config.class);}@OverridepublicGatewayFilterapply(Config config){return(exchange, chain)->{// 前置过滤逻辑System.out.println("Custom Pre Filter executed");return chain.filter(exchange).then(Mono.fromRunnable(()->{// 后置过滤逻辑System.out.println("Custom Post Filter executed");}));};}publicstaticclassConfig{// 配置属性}}
在配置文件中使用自定义过滤器:
spring:cloud:gateway:routes:-id: my_route
uri: http://httpbin.org:80predicates:- Path=/get
filters:-name: MyCustomFilter
五、Gateway 限流
在
Spring Cloud Gateway
中,有
Filter
过滤器,因此可以在
“pre”
类型的
Filter
中自行实现上述三种过滤器。但是限流作为网关最基本的功能,
Spring Cloud Gateway
官方就提供了
RequestRateLimiterGatewayFilterFactory
这个类,适用
Redis
和
Lua
脚本实现了令牌桶链接的方式。具体实现逻辑在
RequestRateLimiterGatewayFilterFactory
类中,
Lua
脚本在如下图所示的文件夹中:
以案例的形式来讲解如何在
SpringCloud Gateway
中使用内置的限流过滤器工厂来实现限流。首先在工程的
pom
文件中引入
Gateway
的起步依赖和
Redis
的
Reactive
依赖,代码如下:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifatId>spring-boot-starter-data-redis-reactive</artifactId></dependency>
在配置文件中做以下的配置:
server:port:8081spring:cloud:gateway:routes:-id: limit_route
uri: lb://PRODUCTCENTOR # PRODUCTCENTOR是注册到注册中心的服务名,格式为 lb://服务名predicates:- Path=/**filters:- StripPrefix=1
-name: RequestRateLimiter #拦截器,会对上述的请求进行拦击args:key-resolver:'#{@hostAddrKeyResolver}'redis-rate-limiter.replenishRate:1redis-rate-limiter.burstCapacity:3application:name: gateway-limiter
redis:host: localhost
port:6379database:0
过滤器
StripPrefix
,作用是去掉请求路径的最前面
n
个部分截取掉。
StripPrefix=1
就代表截取路径的个数为
1
,比如前端过来请求
/test/good/1/view
,匹配成功后,路由到后端的请求路径就会变成
http://localhost:8888/good/1/view
。
在上面的配置文件,指定程序的端口为
8081
,配置了
Redis
的信息,并配置了
RequestRateLimiter
的限流过滤器,该过滤器需要配置三个参数:
【1】
burstCapacity
:令牌桶总容量。
【2】
replenishRate
:令牌桶每秒填充平均速率。
【3】
key-resolver
:用于限流的键的解析器的
Bean
对象的名字。它使用
SpEL
表达式根据
#{@beanName}
从
Spring
容器中获取
Bean
对象。
KeyResolver
需要实现
resolve
方法,比如根据
Hostname
进行限流,则需要用
hostAddress
去判断。实现完
KeyResolver
之后,需要将这个类的
Bean
注册到
Ioc
容器中。
publicclassHostAddrKeyResolverimplementsKeyResolver{@OverridepublicMono<String>resolve(ServerWebExchange exchange){returnMono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());}}@BeanpublicHostAddrKeyResolverhostAddrKeyResolver(){returnnewHostAddrKeyResolver();}
可以根据
URL
去限流,这时
KeyResolver
代码如下:
publicclassUriKeyResolverimplementsKeyResolver{@OverridepublicMono<String>resolve(ServerWebExchange exchange){returnMono.just(exchange.getRequest().getURI().getPath());}}@BeanpublicUriKeyResolveruriKeyResolver(){returnnewUriKeyResolver();}
也可以以用户的维度去限流:
// 省略部分代码@BeanKeyResolveruserKeyResolver(){return exchange ->Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));}
用
jmeter
进行压测,配置
10 thread
去循环请求
lcoalhost:8081
,循环间隔
1s
。从压测的结果上看到有部分请求通过,由部分请求失败。通过
redis
客户端去查看
redis
中存在的
key
。如下:
127.0.0.1:6379> keys *
1>"request_rate_limiter.<127.0.0.1>.timestamp"2>"request_rate_limiter.<127.0.0.1>.tokens"
可见,
RequestRateLimiter
是使用
Redis
来进行限流的,并在
redis
中存储了
2
个
key
。关注这两个
key
含义可以看
lua
源代码。
六、Zuul 与 Spring Cloud Gateway 比较
优点缺点Gateway1、线程开销小2、使用轻量级 Netty 异步IO实现通信3、支持各种长连接,WebSocket4、Spring 官方推荐,重点支持,功能较 Zuul更丰富,支持限流监控等Zuul1、编码模型简单2、开发调试运维简单
**
Zuul
与
Gateway
压测结果:** 休眠时间模仿后端请求时间,**线程数
2000
**,请求时间
360
秒=
6
分钟。配置情况:
Gateway
默认配置,
Zuul
网关的
Tomcat
最大线程数为
400
,
hystrix
超时时间为
100000
。
休眠时间测试样本,单位=个Zuul/Gateway平均响应时间,单位=毫秒Zuul/Gateway99%响应时间,单位=毫秒Zuul/Gateway错误次数,单位=个Zuul/Gateway错误比例Zuul/Gateway休眠100ms294134/10593212026/5466136/1774104/00.04%/0%休眠300ms101194/3999095595/148915056/16901114/01.10%/0%休眠600ms51732/20126211768/297527217/32032476/04.79%/0%休眠1000ms31896/12095619359/491446259/51153598/011.28%/0%
测试结果:
Gateway
在高并发和后端服务响应慢的场景下比
Zuul
的表现要好
七、SpringCloud GateWay 与 Nginx 组合使用
因为和
GateWay
相关所以这里介绍一下
Nginx
和
Spring Cloud Gateway
可以组合使用,以实现高效的负载均衡和网关功能。
Nginx
通常用于处理静态内容、
SSL
终止、负载均衡等,而
Spring Cloud Gateway
主要用于动态路由、过滤和服务网关功能。下面是一个基本的配置示例,展示了如何将
Nginx
和
Spring Cloud Gateway
结合使用。这里主要说下
Nginx
中的配置:
配置
Nginx
作为反向代理,将外部请求转发到
Spring Cloud Gateway
。
**
nginx.conf
配置示例**
http {
upstream gateway {# 定义一个名为 gateway 的上游服务器组,包含 Spring Cloud Gateway 的地址(localhost:8080)。
server localhost:8080;
}
server {# 配置 Nginx 服务器块。
listen 80; # 监听80端口。
location / {# 匹配所有请求。
proxy_pass http://gateway; # http://gateway: 将请求转发到上游服务器组 gateway。
proxy_set_header Host $host; # 设置一些头信息,用于保持客户端信息。
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}}}
工作流程:
【1】客户端请求:客户端发送请求到
Nginx
。
【2】
Nginx
转发:
Nginx
接收到请求后,将其转发到
Spring Cloud Gateway
。
【3】
Spring Cloud Gateway
路由:
Spring Cloud Gateway
根据配置文件中的路由规则,将请求转发到对应的微服务。
【4】微服务响应:微服务处理请求并返回响应,通过
Spring Cloud Gateway
和
Nginx
返回给客户端。
SpringCloud GateWay 与 Nacos 组合使用
bootstrap.yml
用于配置
Nacos
的基本信息:
spring:application:name: gateway-service #设置网关服务的名称。cloud:nacos:discovery:server-addr: ${NACOS_SERVER_ADDR:localhost:8848}#Nacos 服务发现的地址。 ${NACOS_SERVER_ADDR}: 使用环境变量配置 Nacos 服务器地址,方便在不同环境中切换。config:server-addr: ${NACOS_SERVER_ADDR:localhost:8848}#Nacos 配置管理的地址。file-extension: yaml
可以将
Spring Cloud Gateway
的配置放在
Nacos
配置中心,这样可以实现配置的集中管理和动态更新。在
Nacos
中创建配置:
【1】登录
Nacos
控制台。
【2】创建一个新的配置,例如
gateway-service.yaml
。
【3】在配置文件中添加路由规则,例如:
spring:cloud:gateway:routes:-id: example-service
uri: lb://example-service
predicates:- Path=/example/**-id: another-service
uri: lb://another-service
predicates:- Path=/another/**
版权归原作者 程序猿进阶 所有, 如有侵权,请联系我们删除。