Spring Cloud Gateway入门
- 1. Spring Cloud Gateway入门
- 1.1 spring cloud gateway与Netflix Zuul的区别
- 1.2 入门案例
- 1.3 spring cloud gateway基本组成
- 1.4 断言类型
- 1.4.1 Before路由断言
- 1.4.2 After路由断言
- 1.4.3 Between路由断言
- 1.4.4 Cookie路由断言
- 1.4.5 Header路由断言
- 1.4.6 Host路由断言
- 1.4.7 Method路由断言
- 1.4.8 Path路由断言
- 1.4.9 Query路由断言
- 1.4.10 RemoteAddr路由断言
- 1.4.11 Weight路由断言
- 1.5 过滤器类型
- 1.5.1 AddRequestHeader过滤器
- 1.5.2 AddRequestParameter过滤器
- 1.5.3 AddResponseHeader过滤器
- 1.5.4 Retry过滤器
- 1.5.5 RequestRateLimiter过滤器
- 1.5.6 StripPrefix过滤器(除前缀过滤器)
- 1.5.7 RewritePath过滤器(重写路径过滤器)
- 1.5.8 自定义过滤器
1. Spring Cloud Gateway入门
1.1 spring cloud gateway与Netflix Zuul的区别
(1)zuul的执行方式
Zuul会为每一个请求分配一条线程,然后通过执行不同类型的过滤器来完成路由的功能。但是这条线程会等route过滤器去调用后端服务,这样可能因为业务复杂导致服务响应缓慢,使得Zuul中的线程就需要执行比较长的时间,容易造成线程积压,导致性能变慢。
(2)spring cloud gateway的执行方式
spring cloud gateway执行方式可以分为下面三步:
①创建一条线程,通过类似Zuul的过滤器拦截请求
②对源服务器转发请求,但Gateway并不会等待请求调用源服务器的过程,而是将处理线程挂起,这样便不再占用资源了
③等源服务器返回消息后,再通过寻址的方式来响应之前客户端发送的请求
因此:Gateway线程活动的时间更短,线程积压的概率更低,性能也更好
1.2 入门案例
(1)创建一个微服务,让其对外提供一个接口,例如为:http://localhost:8080/testRoute/hello可以对外提供服务
(2)创建网关微服务,只需要引入spring cloud gateway依赖和配置yml文件即可
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: testRoute # 路由的唯一id
uri: http://localhost:8080/ # 真正访问的URL
predicates:
- Path=/testRoute/** # 将以/testRoute开始的服务,转发到http://localhost:8080/中
server:
port: 8888
(3)访问http://localhost:8888/testRoute/hello即可实现相同的效果
注:本实例是采用直连的方式,企业级应用均是与注册中心结合起来使用。
1.3 spring cloud gateway基本组成
spring cloud gateway是通过netty服务器实现异步非阻塞功能的,原理如图:
由路由、断言和过滤器组成:
(1)路由(route)
路由是网关一个最基本的组件,它由ID、目标URI、断言集合和过滤器集合共同组成,当断言判定为true时,才会匹配到路由
(2)断言(predicate)
断言主要是判定当前请求如何匹配路由的。每个断言的入参都是Spring框架的ServerWebExchange对象类型。断言允许开发者匹配来自HTTP请求的任何内容,例如URL、请求头或请求参数,当这些断言都返回true时,才执行这个路由。
(3)过滤器(filter)
过滤器是使用特定工厂构造的SpringFrameworkGatewayFilter实例,其作用是在发送下游请求之前或者之后,修改请求和响应。过滤和断言均可以有多个。
1.4 断言类型
断言类必须实现RoutePredicateFactory接口,并且统一命名为:xxxRoutePredicateFactory
我们以java配置的方式为例结介绍各类断言(大家从官网看如何通过yml配置的方式配置断言)
1.4.1 Before路由断言
作用:路由在这个时间点之前有效,过了这个时间点无效
@Configuration
public class PredicateConfiguration {
//1.Before路由断言
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder){
ZonedDateTime dateTime = LocalDateTime.now()
//一分钟后路由失效
.plusMinutes(1)
//定义UTC时间
.atZone(ZoneId.systemDefault());
return builder.routes()
//匹配
.route("1", r ->
//使用断言
r.before(dateTime)
//转发到具体的url
.uri("http://localhost:8080"))
.build();
}
}
1.4.2 After路由断言
作用:路由在该时间点之后有效
//After路由断言
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder){
ZonedDateTime dateTime = LocalDateTime.now()
//一分钟后路由生效
.plusMinutes(1)
.atZone(ZoneId.systemDefault());
return builder.routes()
//匹配
.route("1", r ->
//使用断言
r.after(dateTime)
.uri("http://localhost:8010"))
.build();
}
1.4.3 Between路由断言
作用:在两个时间点之间有效
//Between路由断言
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder){
ZonedDateTime dateTime1 = LocalDateTime.now()
.atZone(ZoneId.systemDefault());
ZonedDateTime dateTime2 = LocalDateTime.now()
.plusMinutes(1)
.atZone(ZoneId.systemDefault());
return builder.routes()
//匹配
.route("1", r ->
//使用断言
r.between(dateTime1,dateTime2)
.uri("http://localhost:8010"))
.build();
}
1.4.4 Cookie路由断言
作用:判定某个Cookie参数是否满足某个正则式,满足则匹配路由(不常用,因为用户可以关闭cookie)
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder){
return builder.routes()
//匹配
.route("1", r ->
//使用断言
r.cookie("cookies_id","abcd.*")
.uri("http://localhost:8010"))
.build();
}
1.4.5 Header路由断言
作用:判断Header中的参数是否满足某个正则式,满足则匹配路由
//Header路由断言
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder){
return builder.routes()
//匹配
.route("1", r ->
//使用断言
r.header("id","^[0-9]*$")
.uri("http://localhost:8010"))
.build();
}
1.4.6 Host路由断言
作用:访问携带的host主机名称匹配才能访问路由
1.4.7 Method路由断言
作用:判断HTTP的请求类型是否匹配
//Method路由断言
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder){
return builder.routes()
//匹配
.route("1", r ->
//使用断言
r.method(HttpMethod.GET)
.uri("http://localhost:8010"))
.build();
}
1.4.8 Path路由断言
作用:通过URI路径判断是否匹配路由
//Path路由断言
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder){
String path1 = "/hello";
String path2 = "/hello/index";
return builder.routes()
//匹配
.route("1", r ->
//使用断言
r.path(path1,path2)
.uri("http://localhost:8010"))
.build();
}
1.4.9 Query路由断言
作用:①判断是否存在某些请求参数,存在则匹配路由
②对请求参数值进行验证,符合正则式则匹配路由
//Query路由断言
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder){
String regex = "^[0-9]*$";
return builder.routes()
//匹配
.route("1", r ->
//使用断言
//r.query("id") //存在id参数即可
r.query("id",regex)
.uri("http://localhost:8010"))
.build();
}
1.4.10 RemoteAddr路由断言
作用:判断访问的远程服务器是否匹配,匹配则使用该路由
//RemoteAddr路由断言
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder){
String addr1 = "10.33.68.18";
String addr2 = "10.33.68.19";
return builder.routes()
//匹配
.route("1", r ->
//使用断言
r.remoteAddr(addr1,addr2)
.uri("http://localhost:8010"))
.build();
}
1.4.11 Weight路由断言
作用:当后端服务器为集群时,可以为不同的机器配置不同的权重,可以用来做灰度发布和负载均衡
//Weight路由断言
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder){
String group_name = "group_one";
String path = "/hello/**";
return builder.routes()
//第一个路由
.route("1", r ->
r.path(path)
.and()
.weight(group_name,80)
.uri("http://localhost:8010"))
//第二个路由
.route("2", r ->
r.path(path)
.and()
.weight(group_name,20)
.uri("http://localhost:8011"))
.build();
}
1.5 过滤器类型
三点解释:
- 断言是为了路由的匹配,过滤器则是在请求源服务器之前或者之后对HTTP请求和响应的拦截,以便对请求和响应做出相应的修改,如请求头和响应头的修改。
- Gateway过滤器分为全局过滤器和局部过滤器,全局过滤器针对所有的路由均有效,而局部过滤器只针对某些路由有效。全局过滤器需要实现GlobalFilter接口,而局部过滤器则需要实现GatewayFilter接口。
- 在spring cloud Gateway中内置了不同的过滤器工厂(命名方式为:XXXGatewayFilterFactory)。
1.5.1 AddRequestHeader过滤器
作用:增加请求头的过滤器工厂,这样使得路由到资源服务器时就多了一个请求头参数
//AddRequestHeader过滤器
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder) {
return builder.routes()
.route("1", r ->
r.path("/hello/**")
.filters(f -> f.addRequestHeader("id", "1"))
.uri("http://localhost:8010")
).build();
}
1.5.2 AddRequestParameter过滤器
作用:增加请求参数过滤器,这样使得路由到资源服务器时就多了一个请求参数
//AddRequestParameter过滤器
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder) {
return builder.routes()
.route("1", r ->
r.path("/hello/**")
.filters(f -> f.addRequestParameter("id", "2"))
.uri("http://localhost:8010")
).build();
}
1.5.3 AddResponseHeader过滤器
作用:增加响应头参数,调完资源服务器时会添加响应头
//AddResponseHeader过滤器
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder) {
return builder.routes()
.route("1", r ->
r.path("/hello/**")
.filters(f -> f.addResponseHeader("id", "3"))
.uri("http://localhost:8010")
).build();
}
1.5.4 Retry过滤器
作用:当某些请求异常时,自定义重试策略。有5个参数:
①retries:重试次数
②statuses:根据HTTP响应状态来断定是否重试
③methods:请求方法
④重试的状态码系列。取响应码的第一位,按HTTP响应状态码规范取值范围为1 ~ 5,其中,1代表消息,2代表成功,3代表重定向,4代表客户端错误,5代表服务端错误
⑤exceptions:请求异常列表,默认的情况下包含IOException和TimeoutException两种异常
//Retry过滤器
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder) {
return builder.routes()
.route("1", r ->
r.path("/hello/**")
.filters(f -> f.retry(retryConfig -> {
//重试次数为2
retryConfig.setRetries(2);
//重试方法GET
retryConfig.setMethods(HttpMethod.GET);
//状态码500
retryConfig.setStatuses(HttpStatus.INTERNAL_SERVER_ERROR);
//重试序列为服务器错误
retryConfig.setSeries(HttpStatus.Series.SERVER_ERROR);
}))
.uri("http://localhost:8010")
).build();
}
1.5.5 RequestRateLimiter过滤器
作用:限制请求流量,避免过大的流量进入系统,从而保证系统在一个安全的流量下可用。
使用方式:
(1)引入响应式redis包和连接池包(因为spring-boot-starter-data-redis-reactive不包含连接池)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.9.0</version>
</dependency>
(2)配置redis
redis:
timeout: 10000
host: 127.0.0.1
port: 6379
database: 1
lettuce:
pool:
max-active: 1024
max-wait: 10000
max-idle: 200
min-idle: 5
(3)定义RequestRateLimiter过滤器(使用配置文件方式较为简单)
spring:
application:
name: gateway-api
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: http://localhost:8010/ # 真正访问的URL
predicates:
- Path=/hello/**
filters:
- name: RequestRateLimiter
args:
# 每秒允许处理的请求数量 # 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 每秒最大处理的请求数量# 令牌桶总容量
redis-rate-limiter.burstCapacity: 2
这样就配置了对该Path的限流(令牌桶算法)
1.5.6 StripPrefix过滤器(除前缀过滤器)
作用:在网关中,为了区分路由到的资源服务器,我们需要在路径前加入前缀,但是在访问资源服务器时需要去掉前缀,就需要用该StripPrefix过滤器
//StripPrefix过滤器
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder) {
return builder.routes()
.route("1", r ->
r.path("/u/hello/**")
//去除一个前缀
.filters(f -> f.stripPrefix(1))
.uri("http://localhost:8010")
).build();
}
1.5.7 RewritePath过滤器(重写路径过滤器)
作用:和StripPrefix过滤器类似,但是功能更加强大,可以直接重写请求路径。
//RewritePath过滤器
@Bean
public RouteLocator customerRouters(RouteLocatorBuilder builder) {
return builder.routes()
.route("1", r ->
r.path("/u/hello/**")
//去除一个前缀
.filters(f -> f.rewritePath("/u/(?<segment>.*)","/$\\{segment}"))
.uri("http://localhost:8010")
).build();
}
1.5.8 自定义过滤器
假设需求为:请求过来的url中必须携带参数company_name且值为beike才能访问
自定义过滤器MyTestGatewayFilterFactory:
@Slf4j
@Component
public class MyTestGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return ((exchange, chain) -> {
System.out.println("进入自定义过滤器");
String companyName = exchange.getRequest().getQueryParams().getFirst("company_name");
//参数匹配上,放行
if(config.getValue().equals(companyName)){
return chain.filter(exchange);
}else {
//参数不匹配
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
return exchange.getResponse().setComplete();
}
});
}
public static class Config{
private String companyName;
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
}
}
正确的请求方式:
本文代码:
https://github.com/haiyang679/gatewayLearn