开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
gateway构建在Spring5、SpringBoot2和Reactor基础之上。
gateway 和zuul都是常用的网关,目前zuul已经进入维护阶段,spring官方开发gateway,和zuul整体比较有很大优势,具体自行了解,当然,这两个组件都很强大,具体不再介绍。但是gateway是基于Reactor的,所以在使用时和spring.*.web 组件冲突,这点需要注意。
前言
Spring Cloud中集成了很多常用的功能,如服务、降级和熔断等,如果要集成Spring Cloud的功能,首先要注意的就是版本问题:
Spring Cloud和Spring Boot的版本对应表
使用Spring Cloud Gateway
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
如果想禁用Gateway,可使用spring.cloud.gateway.enabled=false
基本组件
- Route: 路由,一个完整的Route由id、uri、predicates和filters构成,当predicates匹配当前请求时,该Route生效;
- Predicate: 谓词,判断某个请求是否命中该Route,是一个参数为 Spring Framework ServerWebExchange的类型Java 8 Function Predicate;
- Filter: 当匹配到Route后,这些过滤器将应用到当前请求,执行顺序用Ordered控制。
工作流程
- Gateway Client发起一个请求;
- 如果Gateway Handler Mapping匹配当前请求,会转发到 Gateway Web Handler进行处理;
- Gateway Web Handler会按顺序执行 filters,并把结果向上返回;
filters分pre和post,其中pre在实际业务服务之前执行,post是在实际业务服务之后执行。
注意事项
Gateway是构建在Spring Boot 2.x、Spring webflux和Reactor之上,这就意味着和我们常用的Spring-boot-starter-web并不兼容,所以像Spring data、Spring Security等组件也不兼容。
Predicate:谓词,用于判断当前请求是否命中该Route
定义方式
定义谓词有两种方式:完整定义和快捷定义。
完整定义
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://imdony.org
predicates:
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
predicates是一个数组,包括两个key:name和args:
- name表示该predicate的类型(即采用哪种匹配策略,示例中表示判断Cookie),
- args是判断的参数,是一个key-value映射表(示例中表示要包括Cookie键值是mycookie=mycookievalue才会命中该Route。
快捷定义
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Cookie=mycookie,mycookievalue
快捷方式省略了name和args两个key,直接用name=args的方式把两部分连接起来,其中args部分第一个表示args中的name,逗号(,)后面表示regexp。
预置的Predicate
Spring Cloud Gateway预置了很多可以直接使用的Predicate,而且可以组合使用。
After:在某个时间点(ZonedDateTime)之后的请求会命中该Route
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://imdony.org
predicates:
- After=2017-01-20T17:42:47[Asia/Shanghai]
2017-01-20 17:42:47之后所有的请求,都将转调 imdony.org,可用于控制规划新服务上线时间。
Before:在某个时间点(ZonedDateTime)之前的请求会命中该Route
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47[Asia/Shanghai]
2017-01-20 17:42:47之前所有的请求,都将转调 imdony.org,可用于控制服务下线时间。
Between:在某时间之间的请求会命中该Route
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47[Asia/Shanghai], 2017-01-21T17:42:47[Asia/Shanghai]
2017-01-20 17:42:47和2017-01-21 17:42:47之间所有的请求,都将转调 imdony.org,可用于控制服务临时扩容。
Cookie:匹配请求中Cookie内容
包括两个参数name和regexp,其中regexp是一个 Java regular expression。
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://imdony.org
predicates:
- Cookie=name,ch-dony
当Cookie中存在name=ch-dony时,将调用 imdony.org,可对客户端进行分批控制。
Header:匹配请求的Header参数
包括两个参数name和regexp,其中regexp参照Cookie。
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://imdony.org
predicates:
- Header=X-Request-Id, \d+
当Header中的X-Request-Id匹配\d+时,命中该Route。
Host:匹配请求Header中的Host参数
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://imdony.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
Host是一个列表,用,隔开,表示匹配其中一个就可以。
在该谓词中支持使用变量,如下:
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://imdony.org
predicates:
- Host={sub}.somehost.org,**.anotherhost.org
该谓词会提取其中的{sub}(如果Host=www.somehost.org,那么sub=www),并把sub匹配的值应用到GatewayFilter中去(该实例中未使用Filter),在GatewayFilter中使用ServerWebExchange.getAttributy("uriTemplateVariables")获取匹配的值,其中为了方便,官方提供了工具类ServerWebExchangeUtils,如ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE的值就是uriTemplateVariables。
写一个自定义 过滤器的 例子
//自定义一个GatewayFilterFactory,可是使用AbstractNameValueGatewayFilterFactory ,方便
//要添加@component,注册为Bean,启动时会调用该Factory并生成一个GatewayFilter加入Failter chain
@Component
public class PathTestFilter extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String val= ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
return chain.filter(exchange);
}
};
}
}
# 把上面的Filter添加到某路由的Filter chain
routes:
- id: user-route
uri: http://localhost:8080
predicates:
- Path=/api/{sub}
filters:
- PathTestFilter=args,{sub}
RemoteAddr:匹配IP地址
和Host功能差不多,一个对应主机一个对应IP(例如192.168.0.1/16,16是子网掩码)。
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://imdony.org
predicates:
- RemoteAddr=192.168.1.1/24
如果客户端使用了代理服务器,那么gateway无法获取到代理服务器后面的客户端IP,gateway提供了解决方案:基于 X-Forwarded-For header的RemoteAddressResolver:XForwardedRemoteAddressResolver,详细的稍后介绍。
Method:匹配请求的类型
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://imdony.org
predicates:
- Method=GET,POST
当使用GET或POST访问时,命中该Route。
Path:匹配请求的Path(Uri)
包括两个参数:PathMatcher列表和matchTrailingSlash(以斜线结尾,可选,默认true),以下示例会匹配/red/1 或/red/1/(如果matchTrailingSlash=false,该项不会被匹配) 或 /red/blue 或 /blue/green:
追加说明:
- 配置uri时,一定要完整,如http://localhost:8080,不能省略schema部分,即localhost:8080会路由失败,具体可见RouteToRequestUrlFilter源码中的filter方法;
- 在新版(测试版本cloud 2021.0.1和boot 2.6.7)中,Path=/api/**匹配的前缀/api不再自动删除,需添加 filters:-StripPrefix=1手动删除;
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://imdony.org
predicates:
- Path=/red/{segment},/blue/{segment}
变量{segment}参照Host,另外还有一个简单的方法获取该参数:
Map<String, String> uriVariables = ServerWebExchangeUtils.getPathPredicateVariables(exchange);
String segment = uriVariables.get("segment");
Query:匹配GET请求的参数
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://imdony.org
predicates:
- Query=red, gree.
当请求中存在param是red并且值匹配gree.时命中,如green和greet。
Weight :分组匹配权重
weight有两个参数:group和weight,在每一个分组内计算权重,下面例子中分了一个组group1,其中第一个权重80%,第二个权重20%,请求量根据权重分配到不同的uri中。
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
如果你的服务器性价比比较明显,可以采用这种策略进行权重的划分,以减轻高负载服务器的压力。
整合Hystrix降级和限流
- 降级:被调用的方法出现异常时,调用方不用一直阻塞等着,直接调用降级方法,防止一直阻塞造成服务雪崩;
- 限流:根据不同的维度(如统一客户IP、同一用户ID等),限制调用频率,Hystrix采用令牌桶;
整合降级
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
添加配置
cloud:
gateway:
routes:
- id: user-route
uri: http://localhost:8080
predicates:
- Path=/api/**
filters:
- name: Hystrix
args:
name: fallbackCmdA
fallbackUri: forward:/fallbackA
在filters中添加name:Hystrix,就会把降级过滤器添加到当前router的过滤器链中。
- 规则1:name:Hystrix 激活的过滤器是HystrixGatewayFilterFactory
- 规则2:args:name:fallbackCmdA这个是随便写的名字
- 规则3:fallbaclUri:forward:/fallbackA,fallbackA是需要自定义的Bean
正常Hystrix默认超时时间是 1000ms,即1s,可以通过以下方式修改超时间:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
添加自定义Bean
@RestController
public class FallbackController {
// 这个名字就是fallbackUri中使用的名字,要对应,正常服务异常会被降级到该接口方法
@GetMapping("/fallbackA")
public String fallback(){
System.out.println("call fail:"+new Date());
return "call fail";
}
}
添加限流
概念性的内容,大家自己查查吧
添加依赖
参考 #整合降级 节点中的 依赖,是一样的
添加配置
gateway:
routes:
- id: user-route
uri: http://localhost:8080
predicates:
- Path=/api/**
filters:
- name: Hystrix
args:
name: fallbackCmdA
fallbackUri: forward:/fallbackA
- name: RequestRateLimiter
args:
key-resolver: '#{@keyResolver}'
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
在filters中添加name:RequestRateLimiter,即生效限流功能
- 规则1:name:RequestRateLimiter会把过滤器RequestRateLimiterGatewayFilterFactory(实际上是工厂)添加到过滤器链
- 规则2:key-resolver后面是引用的Bean,即采用哪个维度去限流,如用户ID,客户端IP之类,需要自定义
- 规则3:redis-rate-limiter.replenishRate:配置1s生成几个令牌
- 规则4:redis-rate-limiter.burstCapacity:令牌桶的容量是多少,用完了新请求就要等待了
添加维度/规则Bean
@Bean
KeyResolver keyResolver(){
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}