本文内容均来自​​SpringCloud官网​

Gateway核心概念

路由(Route): 路由是网关的基本组成部分,路由信息由ID、目标URL、一组断言和一组过滤器组成,如果断言为真,则说明请求的URL和配置匹配。
断言(Predicate): Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者自定义匹配来自于Http Request中的任何信息,比如请求头和参数等
过滤器(Flute): 一个标准的SpringWebFilter,Spring Cloud Gateway中的Filter分为两种类型,分别是Gateway Filter和Globa lFilter,过滤器将会对请求和响应进行处理。

Gateway工作原理

下面的图表提供了关于SpringCloud网关如何工作的高级概述

Gateway一文详解_限流

客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。此处理程序通过特定于请求的过滤器链运行请求。过滤器被虚线分隔的原因是过滤器可以在发送代理请求之前和之后运行逻辑。执行所有“pre”筛选逻辑。然后发出代理请求。发出代理请求后,运行“post”筛选逻辑

Gateway入门案例

前提准备:
注册中心:nacos或者eureka
两个业务服:任意web服务即可
网关服:gateway

关于服务注册发现还有业务服我这里就不搭建了。核心关注Gateway的环境搭建

Gateway一文详解_spring_02

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>

Gateway一文详解_spring_03

关于路由配置application.yml

spring:
cloud:
gateway:
routes:
- id: test_route
uri: https://www.baidu.com
predicates: #断言如果请求路径中符合下面规则那么将请求交给uri中的服务处理
- Query=url,baidu
- id: auth #中央授权服
uri: lb://yy-auth
predicates:
- Path=/auth/**
- id: user #APP端用户服务
uri: lb://yy-user
predicates:
- Path=/user/**
- id: admin #管理后台服务
uri: lb://yy-admin
predicates:
- Path=/admin/**
- id: test #测试服
uri: lb://yy-test
predicates:
- Path=/test/**

以上配置就是gateway的基本路由配置下面会详细讲解各种路由配置规则!

Gateway路由规则

Spring Cloud Gateway创建Router对象时,使用Router对象时,使用RouterPredicateFactory创建Predicate对象可以赋值给Router

  • Spring Cloud Gateway包含许多内置的Router Predicate Factories。
  • 所有这些断言都匹配HTTP请求的不同属性
  • 多个Router Predicate Factories可以通过逻辑与(and)结合起来一起使用

路由断言工厂RouterPredicateFactory包含的主要实现类如图所示,包括Datetime,请求远程地址、路由权重、请求头、Host地址、请求方法、请求路径和请求参数等类型的路由断言。

Gateway一文详解_spring_04

Path

spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}

如果请求路径为,则其路径匹配,例如:/red/1 or /red/1/ or /red/blue or /blue/green

Query
不指定值

spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green

如果请求包含green则匹配

指定值写法

spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.

如果请求包含一个red查询参数,其值与gree匹配,则前面的路由将匹配。regexp,所以green和greet是匹配的

Method

spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST

如果请求方法是GET或POST,则此路由匹配。

After

spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2020-02-02T20:20:20.000+08:00[Asia/shanghai]

此路由符合2020年2月2日20:20:20时间(亚洲/上海)后的任何请求。

RemoteAddr

spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24

如果请求的远程地址是192.168.1.10,则此路由匹配。

Header

spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+

如果请求有一个名为X-Request-Id的头,它的值与\d+正则表达式匹配(也就是说,它的值是一个或多个数字),则此路由匹配。

以上路由的匹配规则我这里就只搬运这么多,如有其它的规则请查看Gateway的官网

Gateway动态路由

什么是动态路由,其实也就是相对于上面配置中农url的值直接写的是固定的请求地址,这在实际微服务架构中是无法满足的,那么就需要整合服务注册/发现中心,nacos或者eureka都行!在url上不写固定的请求地址了,改成为服务注册的名字!这也就是所谓的动态路由罢了

spring:
cloud:
gateway:
routes:
- id: auth #中央授权服
uri: lb://yy-auth
predicates:
- Path=/auth/**
- id: user #APP端用户服务
uri: lb://yy-user
predicates:
- Path=/user/**
- id: admin #管理后台服务
uri: lb://yy-admin
predicates:
- Path=/admin/**
- id: test #测试服
uri: lb://yy-test
predicates:
- Path=/test/**

在正在的微服务开发过程中,可能有成千上百的微服务,如果都这样配置起来,那可太费劲了,那么Gateway还提供服务名称转发的配置规则!

Gateway服务名称转发

spring:
cloud:
gateway:
discovery:
locator:
# 是否与服务发现组件进行结合,通过serviceId转发到具体服务实例
enabled: true #是否开启基于服务发现的路由规则
lower-case-service-id: true #是否将服务名称转换小写

那么这里请求的路径格式为http://gateway的id:prot/服务注册名称/访问路径,我那管理后台的微服务访问测试
​​​ http://localhost:5000/yy-admin/login​

过滤器

Spring Cloud Gateway根据范围划分为Gateway Filter和Global Filter,二者区别如下:

  • Gateway Filter:网关过滤器需要通过spring.cloud.routes.filters配置在具体路由下,只做用在当前路由或者通过spring.cloud.default-filters 配置在全局,作用在所有路由上。
  • Global Filter:全局过滤器,不需要在配置文件中配置,作用在所有路由上,最终通过GatewayFilterAdapter 包装成GatewayFilterChain 可识别的过滤器,它为请求业务以及路由的URL转换为真实业务请求的地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。

网关过滤器用于拦截并链式处理Web请求,可以实现横切面与应用无关的需求,比如安全,超时访问的设置等。修改传入的HTTP请求或者传出的HTTP响应。Spring
Cloud Gateway
包含许多内置的网关过滤器工厂一共22个,包括头部过滤器,路径类过滤器,Hystrix过滤器和重写请求URL的过滤器,还有参数和状态码其他的过滤器,根据过滤器工程的用途来划分,可以分为以下几种:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter和Hystrix

Gateway一文详解_限流_05

Path路径过滤器

路径过滤器可以实现URL重写,通过重写URL可以实现隐藏实际路径提高安全性,易于用户记忆和输入,易于被搜索引擎收录等优点,实现方式如下!

​RewritePathGatewayFilterFactory-重写path过滤器​​​:
RewritePath网关过滤器工厂采用路径正则表达式参数和替换参数,使用Java正则表达式来灵活重写请求路径

spring:
cloud:
gateway:
routes:
- id: admin
uri: lb://yy-admin
predicates:
- Path=/admin/**
filters:
- RewritePath=/admin(?<segment>.*), /$\{segment}

对于/admin/**的请求路径,这将在发出下游请求之前设置/**的路径。注意,由于YAML规范,应该用$\替换$。那么当我们请求为
admin/login是,通过这个过滤器后那么实际请求为/login

​PrefixPathGatewayFilterFactory-前缀Path过滤器​​​:
PrefixPath网关过滤器工厂为匹配的URL添加指定的前缀

spring:
cloud:
gateway:
routes:
- id: admin
uri: lb://yy-admin
filters:
- PrefixPath=/mypath

​StripPrefixGatewayFilterFactory-请求分割过滤器​​​:
StripPrefix网关过滤器工厂采用一个参数StripPrefix,该参数表示在将请求发送到下游之前从请求中剥离的路径个数

spring:
cloud:
gateway:
routes:
- id: admin
uri: lb://yy-admin
predicates:
- Path=/**
filters:
- StripPrefix=2

如果请求路径为/api/123/admin/1那么通过StripPrefix过滤器后实际到达下游请求为/admin/1,这里的2是通过/进行拆分

​SetPathGatewayFilterFactory​​​:
SetPath网关过滤器工厂采用路径模板参数。它提供了一种通过允许模板话路径段来操作请求的简单方法,使用了SpringFramework中的URL模板,允许多个匹配段。

spring:
cloud:
gateway:
routes:
- id: admin
uri: lb://yy-admin
predicates:
- Path=/api/admin/{segment}
filters:
- SetPath=/admin/{segment}
匹配对应的URL的请求,将匹配到的请求追加在目标URL之后,将/api/admin/111重写为/admin/111

Parameter参数过滤器

​AddRequestParameter​​ 网关过滤器工厂会将指定参数添加至匹配到的下游请求中。

spring:
cloud:
gateway:
routes:
- id: admin
uri: lb://yy-admin
predicates:
- Path=/admin/**
filters:
- RewritePath=/admin(?<segment>.*), /$\{segment}
- AddRequestParameter=flag,1

对于/admin/**的请求路径,这将在发出下游请求之前设置/**的路径。注意,由于YAML规范,应该用$\替换$。那么当我们请求为
admin/login是,通过这个过滤器后那么实际请求为/login,然后将/login参数上添加flag,并且值为1,注意这里是可以组合使用的!

Status状态过滤器

​SetStatus​​网关过滤器工厂采用单个状态参数,他必须是有效的Spring HttpStatus。它可以是整数404或者枚举NOT_FOUND的字符串表示。

spring:
cloud:
gateway:
routes:
- id: admin
uri: lb://yy-admin
predicates:
- Path=/admin/**
filters:
- SetStatus=404

对于/admin/**的请求路径,同一返回状态码为404

全局过滤器GlobalFilter

全局过滤器不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它是请求业务以及路由的URL转换为真实业务服务请求地址的核心过滤器,不需要系统化时加载,并作用在每个路由上。

Gateway一文详解_限流_06

自定义过滤器

即使Spring Cloud Gateway自带许多实用的GatewayFilter Factory、Gateway
Filter、Global Filter、但是很多场景下我们任然希望可以自定义自己的过滤器,实现以下骚操作!
自定义网关过滤器需要实现两个接口:GatewayFilter、Ordered

@Component
public class CustomGatewayFilter implements GatewayFilter, Ordered {

/**
*过滤器业务逻辑
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定义网关过滤器被执行");
return chain.filter(exchange);//继续往下执行
}

/**
*过滤器执行顺序,值约小,优先级越高
*/
@Override
public int getOrder() {
return 0;
}
}
/**
* @description: 网关路由配置类
* @author TAO
* @date 2021/4/21 0:37
*/
@Configuration
public class GatewayRoutesConfiguration {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes().route(r ->r
//断言 ()判断条件
.path("/admin/**")
//目标URL,路由到微服务的地址
.uri("lb://yy-admin")
//注册自定义网关过滤器
.filter(new CustomGatewayFilter())
//路由唯一id
.id("admin"))
.build();
}
}

自定义全局过滤器

自定义全局过滤器实现指定接口,GlobalFilter、Ordered ,然后添加@Component即可,无需手动绑定

@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

/**
*过滤器业务逻辑
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定义网关过滤器被执行");
return chain.filter(exchange);//继续往下执行
}
/**
*过滤器执行顺序,值约小,优先级越高
*/
@Override
public int getOrder() {
return 0;
}

}

使用全局过滤器实现鉴权

Gateway一文详解_spring cloud_07

网关限流

顾名思义,限流就是限制流量,也就是限制服务器的QPS,就像你流量包只有1个G流量,用完就没了,通过限流,我们可以很好的控制系统的QPS,从而达到保护系统的目的。

为什么需要限流
比如web服务、对外暴露的API这种类型的服务有一下几种可能导致机器被拖垮

  1. 用户增长过快
  2. 应某个热点事件()微博热搜
  3. 竞争对象爬虫
  4. 恶意请求

​这些情况都是无法预知的,不知道设么时候会用10倍设置20倍的流量打进来,如果真碰上这种情况,扩容时根本来不及的​

Gateway一文详解_spring cloud_08

从上图可以看出,对内而言:上游A、B服务直接依赖下游基础服务C对于A、B服务都依赖C这种场景服务A、B其实存于某种竞争关系,如果A服务并发阈值设置过大,当流量高峰期来临时,有可能直接拖垮基础服务C并影响B服务,即服务雪崩效应!

限流算法

常见限流算法有:

  • 计数器算法
  • 漏铜算法
  • 令牌桶算法

计数器算法

计数器双发是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟访问次数不能超过100个那么我们可以这么做:在一开始的时候,我们可以设置一个计时器counter,当每一个请求过来的时候,counter就加1,如果counter的值大于100且该请求的时间间隔还在1分钟之内,触发限流;如果该请求于第一个请求大于1分钟,重置counter从新计数,具体算法的示意图如下;

Gateway一文详解_spring_09


这个算法虽然简单,但是存在一个十分致命的问题,那就是灵界问题,如下图!


Gateway一文详解_限流_10


从上图中我们可以看出,假设有一个恶意用户,他在0:59时,瞬间发送100个请求,并且1:00又发送100个请求,那么这个用户在1秒的时间里面,瞬间发送了200个请求,我们刚才规定的是1分钟最多100个请求,也就是每秒最多1.7个请求,用户通过在时间窗口的重要节点出突发请求,可以瞬间超过我们的速率限制,用户有可能通过算法的这个漏洞,瞬间压垮我们的服务器!然而这并不是计数器算法唯一的漏洞,还有另外一个问题,就是极大的造成服务器资源浪费,如下图!


Gateway一文详解_java_11


我们的预期想法是希望100个请求可以均匀的分布在这1分钟内,假设30S以内我们就请求上线了,那么剩余半分钟服务器就处于闲置状态!

漏铜算法

漏铜算法其实也很简单,可以初略的认为就是注水漏水的过程,往桶中任意速率流入水,以一定速率流出水,当水超过桶流量则丢弃,因为桶的容量是不变的,保证了整体的速率!

Gateway一文详解_spring cloud_12


漏桶算法是使用队列机制实现的!

Gateway一文详解_java_13

当然咯,漏桶算法也是存在问题的,假设我们桶的容量是100,然后我们流出为1秒一个,那么当我们瞬间请求100,那么就会造成请求堆积,会让网关压力过大,甚至导致网关服务崩溃,我们网关背后的微服务,请求处理能力只要每秒大于1,那么都会造成微服务的资源浪费,漏桶算法就是牺牲自己,保护别人,就是把压力都集中在网关自己身上,这种限流算法也是不完美的!

令牌桶算法

令牌桶算法是对漏铜算法的一种改进,漏铜算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用,在令牌桶算法中,存在一个桶,用来存放固定数量的令牌,算法中存在一种机制,以一定的速率往桶中放令牌,每次请求调用需要先获取令牌,只管拿到令牌,才有机会继续执行,否则选择等待可用的令牌,或者直接拒绝,放令牌的这个动作是持续不断的进行,如果桶中令牌达到上限,就丢弃令牌​​场景大致是这样的:桶中一直有大量可用的令牌,这是进来请求可以直接拿到令牌执行,比如QPS为100/S,那么限流器初始化完成1秒后,桶中就已经有了100个令牌了,等服务启动完成对外提供服务时,该限流器可以抵挡瞬间的100个请求,当桶中没有令牌时,请求会进行等待,最后相当于一定的速率执行​​ Spring Cloud Gateway内部就是使用该算法,大概描述如下:

  • 所有的请求在处理之前都需要拿到一个可用的令牌才会被处理
  • 根据限流大小,设置按照一定的速率往桶里添加令牌
  • 桶设置最大的放置令牌限制,当桶满时,新添加的令牌就丢弃或者拒绝
  • 请求到达后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他业务逻辑,处理完业务逻辑之后,将令牌直接删除;
  • 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完后将不会删除令牌,以此保证足够的限流

    漏桶算法主要的用途在于保护它人,而令牌桶算法主要的目的在于保护自己,将请求压力交于目标服务处理,假设突然进来很多请求,只要拿到令牌这些请求会瞬时被处理调用目标服务。