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”集合,可以实现一系列与核心业务无关的横切面功能,如安全认证限流熔断日志监控

网关在系统中所处的位置:

二、Zuul 与 Spring Cloud Gateway 比较


  优点 缺点
Gateway

1、线程开销小

2、使用轻量级 Netty 异步IO实现通信

3、支持各种长连接,WebSocket

4、Spring 官方推荐,重点支持,功能较 Zuul更丰富,支持限流监控等

1、源码复杂

2、运维复杂

3、目前资料、实践较少,出现问题排查难度大

Zuul

1、编码模型简单

2、开发调试运维简单

1、线程上下文切换开销

2、线程数限制

3、延迟堵塞耗尽线程链接资源

Zuul 与 Gateway 压测结果:休眠时间模仿后端请求时间,线程数 2000,请求时间 360秒=6分钟。配置情况:Gateway 默认配置,Zuul网关的 Tomcat 最大线程数为 400,hystrix 超时时间为 100000。

休眠时间

测试样本,单位=个

Zuul/Gateway

平均响应时间,单位=毫秒

Zuul/Gateway

99%响应时间,单位=毫秒
Zuul/Gateway

错误次数,单位=个

Zuul/Gateway

错误比例

Zuul/Gateway

休眠100ms 294134/1059321 2026/546 6136/1774 104/0 0.04%/0%
休眠300ms 101194/399909 5595/1489 15056/1690 1114/0 1.10%/0%
休眠600ms 51732/201262 11768/2975 27217/3203 2476/0 4.79%/0%
休眠1000ms 31896/120956 19359/4914 46259/5115 3598/0 11.28%/0%

测试结果:Gateway在高并发和后端服务响应慢的场景下比 Zuul的表现要好

三、Spring Cloud GateWay 架构图


客户端向 Spring Cloud Gateway发出请求。 在 Gateway Handler Mapping中找到请求相的匹配路由(这个时候就用到 predicate),则将其发送到 Gateway web handler处理。 handler处理请求时会经过一系列的过滤器链。 过滤器链被虚线划分的原因是过滤器链可以在发送代理请求之前或之后执行过滤逻辑。 先执行所有 “pre”过滤器逻辑,然后进行代理请求。 在发出代理请求之后,收到代理服务的响应之后执行 “post”过滤器逻辑。这跟 Zuul的处理过程很类似。在执行所有 “pre”过滤器逻辑时,往往进行了鉴权限流日志输出等功能,以及请求头的更改、协议的转换;转发之后收到响应之后,会执行所有“post”过滤器的逻辑,在这里可以响应数据进行了修改,比如响应头、协议的转换等。在上面的处理过程中,有一个重要的点就是将请求和路由进行匹配,这时候就需要用到predicate,它是决定了一个请求走哪一个路由。

四、Spring Cloud Gateway 的几个概念


【1】Route 路由:Gateway的基本构建模块,它由ID目标URL断言集合过滤器集合组成。如果聚合断言结果为真,则匹配到该路由。Gateway 依赖如下:

Route 路由-动态路由实现:网关管理平台实现增删改查路由信息到 mysql。网关管理平台发布,发布后将路由信息通过配置中心 openapi 保存到配置中心服务中。网关监听配置中心配置变化,动态刷新到内存中。

【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 断言配置:

 1 server:
2 port: 8080
3 spring:
4 application:
5 name: api-gateway
6 cloud:
7 gateway:
8 routes:
9 - id: gateway-service
10 uri: https://www.baidu.com
11 order: 0
12 predicates:
13 - After=2017-01-20T17:42:47.789-07:00[America/Denver]
14 - Host=**.foo.org
15 - Path=/headers
16 - Method=GET
17 - Header=X-Request-Id, \d+
18 - Query=foo, ba.
19 - Query=baz
20 - 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 过滤器:方案一:写死在代码中

1 @Bean
2 public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
3 return builder.routes()
4 //openapi路由转发
5 .route("openapi_route", p -> p.path( "/openapi/**").filters(f->f.removeRequestHeader("Expect"))
6 .uri("lb://order-openapi-service"))
7 .build();
8 }

方案二:配置文件(yml)

1  # gateway 的配置形式
2 routes:
3 - id: order-service #路由ID,没有规定规则但要求唯一,建议配合服务名。
4 uri: lb://order-service
5 predicates:
6 - Path=/order/**
7 filters:
8 - ValidateCodeGatewayFilter

Filter 过滤器:Filter 按处理顺序 Pre Filter / Post Filter
Filter 按作用范围分为:GlobalFilter 全局过滤器。GatewayFilter 指定路由的过滤器。
Filter 过滤器-扩展自定义Filter:Filter 支持通过 spi 扩展。实现 GatewayFilter,Ordered接口。Filter 方法:过滤器处理逻辑。getOrder:定义优先级,值越大优先级越低。

 1 public class TokenFilter implements GlobalFilter, Ordered {
2
3 Logger logger=LoggerFactory.getLogger( TokenFilter.class );
4 @Override
5 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
6 String token = exchange.getRequest().getQueryParams().getFirst("token");
7 if (token == null || token.isEmpty()) {
8 logger.info( "token is empty..." );
9 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
10 return exchange.getResponse().setComplete();
11 }
12 return chain.filter(exchange);
13 }
14
15 @Override
16 public int getOrder() {
17 return -100;
18 }
19 }

五、快速开始


网关启动步骤(代码演示):
【1】添加依赖

1  <dependency>
2 <groupId>org.springframework.cloud</groupId>
3 <artifactId>spring-cloud-starter-gateway</artifactId>
4 </dependency>

【2】配置文件

 1 spring:
2 cloud:
3 gateway:
4 routes:
5 - id: product #路由ID,根据业务自行定义
6 uri: http://bin.org:80/get #路由的地址
7 predicates:
8 - Path=/**
9 #开启根据服务名进行转发,需要将服务注册到注册中心
10 discovery:
11 locator:
12 enabled: true

六、Gateway 限流


在 Spring Cloud Gateway中,有 Filter过滤器,因此可以在“pre”类型的 Filter中自行实现上述三种过滤器。但是限流作为网关最基本的功能,Spring Cloud Gateway官方就提供了 RequestRateLimiterGatewayFilterFactory这个类,适用 Redis和 Lua脚本实现了令牌桶【链接】的方式。具体实现逻辑在 RequestRateLimiterGatewayFilterFactory类中,Lua脚本在如下图所示的文件夹中:
具体源码不打算在这里讲述,读者可以自行查看,代码量较少,先以案例的形式来讲解如何在 Spring Cloud Gateway中使用内置的限流过滤器工厂来实现限流。首先在工程的 pom文件中引入 Gateway的起步依赖和 Redis的 Reactive依赖,代码如下:

1 <dependency>
2 <groupId>org.springframework.cloud</groupId>
3 <artifactId>spring-cloud-starter-gateway</artifactId>
4 </dependency>
5
6 <dependency>
7 <groupId>org.springframework.boot</groupId>
8 <artifatId>spring-boot-starter-data-redis-reactive</artifactId>
9 </dependency>

在配置文件中做以下的配置:

server:
port: 8081
spring:
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: 1
redis-rate-limiter.burstCapacity: 3
application:
name: gateway-limiter
redis:
host: localhost
port: 6379
database: 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容器中。

 1 public class HostAddrKeyResolver implements KeyResolver {
2
3 @Override
4 public Mono<String> resolve(ServerWebExchange exchange) {
5 return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
6 }
7
8 }
9
10 @Bean
11 public HostAddrKeyResolver hostAddrKeyResolver() {
12 return new HostAddrKeyResolver();
13 }

可以根据 URL去限流,这时 KeyResolver代码如下:

 1 public class UriKeyResolver  implements KeyResolver {
2
3 @Override
4 public Mono<String> resolve(ServerWebExchange exchange) {
5 return Mono.just(exchange.getRequest().getURI().getPath());
6 }
7
8 }
9
10 @Bean
11 public UriKeyResolver uriKeyResolver() {
12 return new UriKeyResolver();
13 }

也可以以用户的维度去限流:

1 @Bean
2 KeyResolver userKeyResolver() {
3 return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
4 }

用 jmeter进行压测,配置 10thread去循环请求lcoalhost:8081,循环间隔1s。从压测的结果上看到有部分请求通过,由部分请求失败。通过 redis客户端去查看 redis中存在的key。如下: 
可见,RequestRateLimiter是使用Redis来进行限流的,并在redis中存储了2个key。关注这两个key含义可以看 lua源代码。

Gateway 网关的更多相关文章

  1. .net core下,Ocelot网关与Spring Cloud Gateway网关的对比测试

    有感于 myzony 发布的 针对 Ocelot 网关的性能测试 ,并且公司下一步也需要对.net和java的应用做一定的整合,于是对Ocelot网关.Spring Cloud Gateway网关做个 ...

  2. Spring Cloud gateway 网关服务二 断言、过滤器

    微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...

  3. Spring Cloud gateway 网关四 动态路由

    微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...

  4. 0.9.0.RELEASE版本的spring cloud alibaba sentinel+gateway网关实例

    sentinel除了让服务提供方.消费方用之外,网关也能用它来限流.我们基于上次整的网关(参见0.9.0.RELEASE版本的spring cloud alibaba nacos+gateway网关实 ...

  5. SpringCloud + Consul服务注册中心 + gateway网关

    1  启动Consul 2  创建springcloud-consul项目及三个子模块 2.1 数据模块consul-producer 2.2 数据消费模块consul-consumer 2.3 ga ...

  6. 实战四:Gateway网关作全局路由转发

    Gateway网关的作用主要是两个:路由转发,请求过滤.此篇讲的是路由转发,下篇介绍请求过滤. 一,创建网关module,添加依赖 1,new -> module -> maven 或直接 ...

  7. 引入 Gateway 网关,这些坑一定要学会避开!!!

    Spring cloud gateway是替代zuul的网关产品,基于Spring 5.Spring boot 2.0以上.Reactor, 提供任意的路由匹配和断言.过滤功能.上一篇文章谈了一下Ga ...

  8. Spring Cloud实战 | 第十一篇:Spring Cloud Gateway 网关实现对RESTful接口权限控制和按钮权限控制

    一. 前言 hi,大家好,这应该是农历年前的关于开源项目 的最后一篇文章了. 有来商城 是基于 Spring Cloud OAuth2 + Spring Cloud Gateway + JWT实现的统 ...

  9. Gateway网关

    前提要在注册中心把网关和服务都进行注册 通俗来说,网关就是指在客户端和服务端的一面墙,这面墙有请求转发,负载均衡,权限控制,跨域,熔断降级,限流保护等功能. 客户端发送请求,请求先通过网关,网关根据特 ...

  10. SpringCloud(四)GateWay网关

    GateWay网关 概述简介 Gateway是在 Spring生态系统之上构建的AP网关服务,基于 Spring5, Spring Boot2和 Project Reactor等技术. Gateway ...

随机推荐

  1. 简述ECMAScript6的新特性

    1.增加块级作用域 2.增加let const 3.解构赋值 4.函数参数拓展(函数参数可以使用默认值,不定参数及拓展参数) 5.增加class类支持 6.增加箭头函数 7.增加模块和模块加载(ES6 ...

  2. ADC参悟

    SFDR比信噪比大15dB,怎么跟应用联系起来,分别决定了啥?

  3. 2020第十一届蓝桥杯大赛软件类国赛题目 C/C++ B 组

    试题 A: 美丽的 2 本题总分:5 分 问题描述:在公元 1 年到公元 2020 年(包含)中,有多少个年份的数位中包含数字 2? #include <stdio.h> #include ...

  4. C的基础常识

    C是可移植性语言,因此可以在许多环境中使用,包括UNIX.Linux.MS-DOS.Windows和Macintosh OS. 使用C语言编写的内容的文本,称为源代码文件(source code fi ...

  5. 2月23日javaweb之Maven

    Maven常用命令 compile:编译 clean:清理 test:测试 package:打包 install:安装 Maven生命周期 Maven对项目构建的生命周期描述是一次构建过程经历了多少个 ...

  6. 下载Vue.js输入Vue -V报错解决办法

    报错如图所示 解决办法: 1. 以管理员身份运行vscode; 2. 执行:get-ExecutionPolicy,显示Restricted,表示状态是禁止的; 3. 执行:set-Execution ...

  7. pandas数据增删改查+拼接

    pandas数据的拼接+增删改查 def lengthways_joint(df,df1): #纵向拼接 if list(df.columns) == list(df1.columns): leb_j ...

  8. 纯前端实现后端给数据进行文件导出——angular里面的使用

    interface dataList { cmd_cnt: number; risk_name: string; user_cnt: number; risk_type:string; } listO ...

  9. SQLServer 远程链接MySql数据库

    第一步:安装MySQL odbc driver 在SQL SERVER所在主机上安装MYSQL ODBC Driver; 下载地址:http://dev.mysql.com/downloads/con ...

  10. 【Unity】关于VS条件编译符号

    写在前面 起因:我在回顾LuaFramework_UGUI(作者Jarjin Lee)代码时,看到了C#代码中的条件编译符号,比如下图的ASYNC_MODE.虽然字面上知道是什么意思,但我对VS的条件 ...