Spring Could新一代网关—Spring Cloud Gateway
一、Gateway的概述
依据官网说法:Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到 ApI,并提供交叉切割问题,例如:安全性、监控/指标和弹性。简单理解其实它是,我们访问微服务的一个道大门,负责处理一些路由,类似于医院大厅的质询台,指引我们具体的科室,当然也可以判断改路由是否符合断言,是否允许通过,其实也是保安加咨询。
可以看微服务的架构图,了解它扮演的角色:
二、Gateway的功能(特点)
- 建立在Spring 5,响应式编程以及Spring boot 2.x上
- 动态路由:能够匹配任何请求属性
- 用断言和过滤来特定于路由(每一个路由可以有相应的断言和过滤器)
- 集成了断路器(可以实现相关的断路器的功能)
- 集成了服务发现DiscoveryClient
- 提供可有轻松书写断言和过滤器
- 请求频率的限制(限流)
- 路径重写
三、如何引入Gateway
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
四、Gateway的工作流程
客户端发送请求但Gateway,Gateway Handler Mapping会根据是否符合断言来决定是否将请求交给指定路由,Gateway Web Handler按照配置给该路由的过滤器交给指定的过滤器链执行,然后执行一系列过滤器,有虚线分开是因为请求和响应都会经过过滤器链的处理,所以可以修改请求和响应。
五、Gateway的重要概念
- 路由(Route):网关的基本构建基块。它由 ID、目的地 URI、谓词集合和过滤器集合定义。如果断言是真的,则与路由相匹配。
- 断言(Predicate):指的是Java 8 的 Function Predicate。 输入类型是Spring框架中的ServerWebExchange。这允许您匹配来自 HTTP 请求的任何内容,例如标题或参数。
- 过滤器(Filter):这些是通过特定工厂构建的GatewayFilter实例。在这里,您可以在发送下游请求之前或之后修改请求和响应。
六、配置路由 Predicate Factories and Gateway Filter Factories
Predicate Factories和Filter Factories分别创建断言和过滤器,我们就应该告诉他们我们应该给哪个请求和路由配置,所以应该通过配置实现,这里有两种方式:
- 简写配置(快捷配置):
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Cookie=mycookie,mycookievalue
上述配置中,https://example.org这个路径的请求必须要带Cookie,且名字为mycookie=mycookievalue
否则段位为false,将不会和该路由匹配。
- 全称配置:
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
其实就将predicates下补全了name ,args,这些可以省略,所以不常用。
可以通过yml配置,也可以通过Java Bean的方式,本质yml也是转成Java Bean嘛
- 通过Java Bean配置:
@SpringBootApplication
public class DemogatewayApplication {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("after_route", r -> r.cookie("mycookie","mycookievalue")
.uri("https://example.org"))
.build();
}
}
这个和上面yml的配置一样
七、断言(predicates)的使用
1.The After Route Predicate Factory
使用格式:After= datetime ZonedDateTime(参数是要符合java的)
这个断言匹配访问的时间后于这个datetime的请求:
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
这个路由匹配晚于2017-01-20T17:42:47.789-07:00[America/Denver]的请求
2.The Before Route Predicate Factory
使用格式:Before = datetime ZonedDateTime(参数是要符合java的)
这个断言匹配访问的时间先于这个datetime的请求
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
这个路由匹配先于2017-01-20T17:42:47.789-07:00[America/Denver]的请求
3.The Between Route Predicate Factory
使用格式:Between=datetime1 ,datetime2(参数是符合java)
这个断言匹配访问的时间在这个datetime之间的请求
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
这个路由匹配在2017-01-20T17:42:47.789-07:00[America/Denver]和2017-01-21T17:42:47.789-07:00[America/Denver]之间的请求
4. The Cookie Route Predicate Factory
使用格式:Cookie=name,regexp(正则表达式)
这个断言匹配访问的请求中Cookie名为name=regexpde的请求
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
这个路由匹配 https://example.org --cookie "chocolate=ch.p"的请求
5.The Header Route Predicate Factory
使用格式:Header=name,regexp(正则表达式)
这个断言匹配请求中带着请求头满足名字为name,且内容符合正则表示的请求
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
https://example.org --Header X-Request-Id=123可以匹配路由,因为\d+即其值为一个或多个数字
6.The Host Route Predicate Factory
使用格式:Host=patterns.Host
这个断言匹配请求头中有名为Host并且满足ant表达式的请求
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
https://example.org --Header host "Host=abc.somehost.org"可以匹配路由
7.The Method Route Predicate Factory
使用格式:Method=method(post,get等)
这个断言匹配请求方式是否为指定请求方式的请求。
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
这里Get和post的https://example.org 请求都匹配
7. The Path Route Predicate Factory
使用格式:Path=/patterns/PathMatcher
这个断言匹配请求路径中有/patterns/PathMatcher的请求
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
https://example.org/red/1 或 https://example.org/blue/1请求被匹配
8.The Query Route Predicate Factory
使用格式:Query=queryParam(正则表达式)
可以匹配请求中有名为queryParam的参数的请求
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green
https://example.org?green=1中有请求参数green,可以匹配
9.The RemoteAddr Route Predicate Factory
使用格式:RemoteAdd=rsources
这个断言匹配发起请求的主机地址为resources的请求
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
利用192.168.1.1这台机子发起请求https://example.org可以匹配,其他主机地址不行
10.The Weight Route Predicate Factory
使用格式:Weight=group weight(权重值)
这个断言可以给不同的请求分配权重,在一定次数内,这些请求被访问的次数由权重决定
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
上述配置80%的请求会匹配到https://weighthigh.org,20%会匹配到https://weightlow.org
八、GatewayFilter Factories
路由过滤器允许以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应。但是只能对指定的路由起作用。Spring Could内置了很多路由过滤器,这些过滤器都GatewayFilter Factories产生。
1.The AddRequestHeader GatewayFilter Factory
为请求头中添加信息的路由过滤器
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
以上的配置将为请求的请求头中添加X-Request-red=blue属性
用curl测试,发送请求:
https://example.org
经过滤器处理后请求:
https://example.org --Header "X-Request-red=blue"
2. The AddRequestParameter GatewayFilter Factory
为请求中添加请求参数
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
filters:
- AddRequestParameter=red, blue
以上配置为请求中添加red=blue的参数
原本请求:
https://example.org
经过滤器处理后请求:
https://example.org?red=blue
3.The PrefixPath GatewayFilter Factory
为请求中添加路径的前缀
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://example.org
filters:
- PrefixPath=/mypath
以上配置为请求中添加/mypath的前缀
原本请求:
https://example.org
经过滤器处理后请求:
https://example.org/mypath
4.The StripPrefix GatewayFilter Factory
剔除请求中的指定位数的前缀
spring:
cloud:
gateway:
routes:
- id: nameRoot
uri: https://nameservice
predicates:
- Path=/name/**
filters:
- StripPrefix=2
以上配置将会剔除前面两位的前缀
原本请求:
https://example.org/name/abc
经过滤器处理后请求:
https://example.org
5.The SetStatus GatewayFilter Factory
设置响应的状态码
spring:
cloud:
gateway:
routes:
- id: setstatusstring_route
uri: https://example.org
filters:
- SetStatus=BAD_REQUEST
- id: setstatusint_route
uri: https://example.org
filters:
- SetStatus=401
以上配置将该请求的响应状态码改为401
6.Spring Cloud CircuitBreaker GatewayFilter Factory
这个过滤器支持和熔断器一起使用,提供熔断和fallback功能。SpringCould的支持多种熔断器,要是使用,将其依赖引入即可,这里我们是Hystrix来作为熔断器结合该过滤器使用,所以引入其依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
路由过滤器配置:
spring:
cloud:
gateway:
routes:
- id: circuitbreaker_route
uri: lb://backing-service:8088
predicates:
- Path=/consumingServiceEndpoint
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fallback
fallbackUri: forward:/fallback指发生了异常等,用/fallback对应的controller这个方法兜底,配置这个controller:
@RestController
public class FallbackController {
@GetMapping("/fallback")
public Object fallback() {
Map<String,Object> result = new HashMap<>();
result.put("data",null);
result.put("message","Get request fallback!");
result.put("code",500);
return result;
}
}
当然也可之间利用该过滤器修改返回状态码和信息,而不用在fallback中配置:
spring:
cloud:
gateway:
routes:
- id: circuitbreaker_route
uri: lb://backing-service:8088
predicates:
- Path=/consumingServiceEndpoint
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fallback
statusCodes:
- 500
- "NOT_FOUND"
7.The RequestRateLimiter GatewayFilter Factory
路由过滤器也可以进行流量控制,当请求频繁访问,可以对其进行控制,提高可用性,如果请求太大默认会返回HTTP 429-太多请求状态。
我们可以针对不同的情况自定义我们的限流策略,通过实现KeyResolver接口
KeyResolver.java
public interface KeyResolver {
Mono<String> resolve(ServerWebExchange exchange);
}
自定义我们的限流策略:
@Configuration
public class RedisRateLimiterConfig {
//根据请求参数中的username进行限流,当相同的username访问达到限流阈值,限流
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));
}
//根据访问IP进行限流
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
}
要达到限流,我们还要结合redis实现,当然这里redis的配置就不详述了。这里运用的算法是Token Bucket Algorithm。
接下来就是像上面配置过滤一样:
```yaml
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: https://example.org
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
key-resolver: "#{@userKeyResolver}"
这里有几个参数:
- redis-rate-limiter.replenishRate:允许用户每秒进行多少请求
- redis-rate-limiter.burstCapacity:允许用户在一秒内进行的最大请求数,将此值设置为零阻止所有请求
- redis-rate-limiter.requestedTokens:这是每个请求从存储桶中取出的token数量,并默认为1
- key-resolver: “#{@userKeyResolver}”:主义这里可以使用EL表达式将我们自定义的限流策略配置进来。
还有其他很多路由过滤器,详情可以参考官网:Spring Could Gateway文档
注意:这些过滤器都是针对某一个具体的路由,当然我们也可以针对全局的配置
九、全局的过滤器
配置全局过滤器:
@Configuration
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("custom global filter");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
只要实现接口GlobalFilter和Ordered,在filter方法中我们可以些代码,如实现全局的鉴权等功能,而getOder方法是规定该过滤执行的顺序,数字越小优先级越高。
十、结合服务注册与发现中心Eureka实现动态路由
我们知道,在微服务中一个服务是不可能只有一个实例的,所以我们在访问这些服务的时候也不可能讲过他们的路由写死,比如直接写某个服务的ip+端口号访问,这样既麻烦,而且也不好做负载均衡,我们要写成动态的,就要写入一个服务的服务名,然后通过服务名动态的访问这个服务名下的多个实例,那么我们怎么通过服务名就得到对应的路由,这就是Eureka的作用,所以就要引入服务注册与发现中心Eureka
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
然后进行配置:
pring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-provider-service #匹配后提供服务的路由地址,lb后跟提供服务的微服务的名,不要写错
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-provider-service #匹配后提供服务的路由地址,lb后跟提供服务的微服务的名,不要写错
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
重点注意gateway.discovery.locator.enabled=true,让gateway能够发现服务,获得其访问的路由等信息,然后注意 uri: lb://cloud-provider-service 我们写的是微服务的名字,而不是具体的IP和端口了,另外,加了lb:这个是实现一个负载均衡的效果。
十一、总结
Spring Could Gateway是一个提供网关服务的技术,同样也有网关作用的还有zuul以及zuul2,但是毫无疑问,Gateway是常用的,因为它也是Spring家族的一员。