路由(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,过滤器将会对请求和响应进行处理。
下面的图表提供了关于SpringCloud网关如何工作的高级概述
客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。此处理程序通过特定于请求的过滤器链运行请求。过滤器被虚线分隔的原因是过滤器可以在发送代理请求之前和之后运行逻辑。执行所有“pre”筛选逻辑。然后发出代理请求。发出代理请求后,运行“post”筛选逻辑
前提准备:
注册中心:nacos或者eureka
两个业务服:任意web服务即可
网关服:gateway
关于服务注册发现还有业务服我这里就不搭建了。核心关注Gateway的环境搭建
<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>
关于路由配置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的基本路由配置下面会详细讲解各种路由配置规则!
Spring Cloud Gateway创建Router对象时,使用Router对象时,使用RouterPredicateFactory创建Predicate对象可以赋值给Router
路由断言工厂RouterPredicateFactory包含的主要实现类如图所示,包括Datetime,请求远程地址、路由权重、请求头、Host地址、请求方法、请求路径和请求参数等类型的路由断言。
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的官网
什么是动态路由,其实也就是相对于上面配置中农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还提供服务名称转发的配置规则!
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,二者区别如下:
网关过滤器用于拦截并链式处理Web请求,可以实现横切面与应用无关的需求,比如安全,超时访问的设置等。修改传入的HTTP请求或者传出的HTTP响应。Spring
Cloud Gateway
包含许多内置的网关过滤器工厂一共22个,包括头部过滤器,路径类过滤器,Hystrix过滤器和重写请求URL的过滤器,还有参数和状态码其他的过滤器,根据过滤器工程的用途来划分,可以分为以下几种:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter和Hystrix
路径过滤器可以实现URL重写,通过重写URL可以实现隐藏实际路径提高安全性,易于用户记忆和输入,易于被搜索引擎收录等优点,实现方式如下!
RewritePathGatewayFilterFactory-重写path过滤器
:
RewritePath网关过滤器工厂采用路径正则表达式参数和替换参数,使用Java正则表达式来灵活重写请求路径
spring:
cloud:
gateway:
routes:
- id: admin
uri: lb://yy-admin
predicates:
- Path=/admin/**
filters:
- RewritePath=/admin(?>.*), /$\{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
AddRequestParameter
网关过滤器工厂会将指定参数添加至匹配到的下游请求中。
spring:
cloud:
gateway:
routes:
- id: admin
uri: lb://yy-admin
predicates:
- Path=/admin/**
filters:
- RewritePath=/admin(?>.*), /$\{segment}
- AddRequestParameter=flag,1
对于/admin/**的请求路径,这将在发出下游请求之前设置/**的路径。注意,由于YAML规范,应该用$\替换$。那么当我们请求为
admin/login是,通过这个过滤器后那么实际请求为/login,然后将/login参数上添加flag,并且值为1,注意这里是可以组合使用的!
SetStatus
网关过滤器工厂采用单个状态参数,他必须是有效的Spring HttpStatus。它可以是整数404或者枚举NOT_FOUND的字符串表示。
spring:
cloud:
gateway:
routes:
- id: admin
uri: lb://yy-admin
predicates:
- Path=/admin/**
filters:
- SetStatus=404
对于/admin/**的请求路径,同一返回状态码为404
全局过滤器不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它是请求业务以及路由的URL转换为真实业务服务请求地址的核心过滤器,不需要系统化时加载,并作用在每个路由上。
即使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;
}
}
顾名思义,限流就是限制流量,也就是限制服务器的QPS,就像你流量包只有1个G流量,用完就没了,通过限流,我们可以很好的控制系统的QPS,从而达到保护系统的目的。
为什么需要限流
比如web服务、对外暴露的API这种类型的服务有一下几种可能导致机器被拖垮
这些情况都是无法预知的,不知道设么时候会用10倍设置20倍的流量打进来,如果真碰上这种情况,扩容时根本来不及的
从上图可以看出,对内而言:上游A、B服务直接依赖下游基础服务C对于A、B服务都依赖C这种场景服务A、B其实存于某种竞争关系,如果A服务并发阈值设置过大,当流量高峰期来临时,有可能直接拖垮基础服务C并影响B服务,即服务雪崩效应!
常见限流算法有:
计数器算法
计数器双发是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟访问次数不能超过100个那么我们可以这么做:在一开始的时候,我们可以设置一个计时器counter,当每一个请求过来的时候,counter就加1,如果counter的值大于100且该请求的时间间隔还在1分钟之内,触发限流;如果该请求于第一个请求大于1分钟,重置counter从新计数,具体算法的示意图如下;
这个算法虽然简单,但是存在一个十分致命的问题,那就是灵界问题,如下图!
从上图中我们可以看出,假设有一个恶意用户,他在0:59时,瞬间发送100个请求,并且1:00又发送100个请求,那么这个用户在1秒的时间里面,瞬间发送了200个请求,我们刚才规定的是1分钟最多100个请求,也就是每秒最多1.7个请求,用户通过在时间窗口的重要节点出突发请求,可以瞬间超过我们的速率限制,用户有可能通过算法的这个漏洞,瞬间压垮我们的服务器!然而这并不是计数器算法唯一的漏洞,还有另外一个问题,就是极大的造成服务器资源浪费,如下图!
我们的预期想法是希望100个请求可以均匀的分布在这1分钟内,假设30S以内我们就请求上线了,那么剩余半分钟服务器就处于闲置状态!
漏铜算法
漏铜算法其实也很简单,可以初略的认为就是注水漏水的过程,往桶中任意速率流入水,以一定速率流出水,当水超过桶流量则丢弃,因为桶的容量是不变的,保证了整体的速率!
漏桶算法是使用队列机制实现的!
当然咯,漏桶算法也是存在问题的,假设我们桶的容量是100,然后我们流出为1秒一个,那么当我们瞬间请求100,那么就会造成请求堆积,会让网关压力过大,甚至导致网关服务崩溃,我们网关背后的微服务,请求处理能力只要每秒大于1,那么都会造成微服务的资源浪费,漏桶算法就是牺牲自己,保护别人,就是把压力都集中在网关自己身上,这种限流算法也是不完美的!
令牌桶算法
令牌桶算法是对漏铜算法的一种改进,漏铜算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用,在令牌桶算法中,存在一个桶,用来存放固定数量的令牌,算法中存在一种机制,以一定的速率往桶中放令牌,每次请求调用需要先获取令牌,只管拿到令牌,才有机会继续执行,否则选择等待可用的令牌,或者直接拒绝,放令牌的这个动作是持续不断的进行,如果桶中令牌达到上限,就丢弃令牌
场景大致是这样的:桶中一直有大量可用的令牌,这是进来请求可以直接拿到令牌执行,比如QPS为100/S,那么限流器初始化完成1秒后,桶中就已经有了100个令牌了,等服务启动完成对外提供服务时,该限流器可以抵挡瞬间的100个请求,当桶中没有令牌时,请求会进行等待,最后相当于一定的速率执行
Spring Cloud Gateway内部就是使用该算法,大概描述如下: