Gateway是在Spring生态系统之上构建的API网关服务,基于Spring5、SpringBoot2和Project Reactor等技术。
Gateway意在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。
SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring5.0+SpringBoot2.0和Project Reactor等技术开发的网关,它意在为微服务架构提供一种简单有效的统一的API路由管理方式。
SpringCloud Gateway作为SpringCloud生态系统中的网关,目标是替代Zuul,在SpringCloud2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模型通信框架Netty。
Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
一句话:Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。
在SpringCloud Finchley正式版之前,Spring Cloud 推荐的网关是Netflix提供的Zuul。
SpringCloud中所集成的Zuul版本,采用的是Tomacat容器,使用的是传统的Servlet IO 处理模型。
servlet由servlet container进行生命周期管理。
上述模式的缺点:
servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程。在并发不高的场景下这种模型是适用的。但是一旦高并发(比如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势。
所以Zuul1.X是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet(DispatcherServlet),并由该servlet阻塞式处理,所以Springcloud Zuul无法摆脱servlet模型的弊端。
是一个非阻塞的web框架, 类似springmvc这样的。
传统的Web框架,比如说:struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的。
在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8)。
SpringWebFlux 是Spring5.0引入的新的响应式框架,区别于SpringMVC,它不需要依赖Servlet API,它是完成异步非阻塞的,并且基于Reactor来实现响应式流规范。
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
就是根据某些规则,将请求发送到指定服务上。
参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
客户端向Spring Cloud Gateway发出请求。然后再Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到Gateway Web Handler。
Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter在"pre" 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在"post"类型的过滤器中可以做响应内容、响应头的修改、日志的输出,流量监控等有着非常重要的作用。
1.新建cloud-gateway-gateway9527 Module
2.Pom.xml
<dependencies>
<!--新增gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.application.yml
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
4.主启动类
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class,args);
}
}
5.针对pay8001模块,设置路由
我们目前不想暴露8001端口,希望在8001外面套一层网关(9527)
修改GateWay模块(9527)的配置文件
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
这里表示,当访问localhost:9527/payment/get/1时,路由到localhost:8001/payment/get/1
6.测试,启动7001、8001、9527
如果启动GateWay报错,可能是GateWay模块引入了web和监控的starter依赖,需要移除。
访问:localhost:9527/payment/get/1
1.在配置文件yml中配置,前面已经说过。
2.使用硬编码的方式
需求:通过9527网关访问到外网的百度新闻网址。
9527中创建配置类,代码如下:
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
//对应配置文件里的routes
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//进行路由配置 当访问localhost:9527/guonei 会转发到http://news.baidu.com/guonei
routes.route("path_rote_donggua",r->r.path("/guonei")
.uri("http://news.baidu.com/guonei"))
.build();
return routes.build();
}
@Bean
public RouteLocator customRouteLocator2(RouteLocatorBuilder routeLocatorBuilder){
//对应配置文件里的routes
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//进行路由配置 当访问localhost:9527/guoji会转发到http://news.baidu.com/guoji
routes.route("path_rote_donggua",r->r.path("/guoji")
.uri("http://news.baidu.com/guoji"))
.build();
return routes.build();
}
}
默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。
需要: 一个eureka7001+两个服务提供者8001/8002
修改9572模块的application.yml
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
需要注意的是uri的协议为lb,表示启动Gateway的负载均衡功能。
lb://serviceName是spring cloud gateway 在微服务中自动为我们创建的负载均衡uri。
测试:访问 http://localhost:9527/payment/lb 可以发现,8001/8002两个端口切换。
SpringCloud Gateway将路由匹配作为Spring WebFlux Handler Mapping基础架构的一部分。
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合。
SpringCloud Gateway 创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route。SpringCloud Gateway包含许多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。
我们之前在配置文件中已经配置过断言
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
这个断言表示,如果外部访问路径是指定路径,就路由到指定微服务上。
path只是断言类型的一种,断言的类型如下:
只有在指定时间后,才可以路由到指定微服务。
- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
- After=2021-01-04T21:44:10.007253800+08:00[Asia/Shanghai]
这里表示,只有在2021年1月4号21点44分10秒之后访问,才可以进行路由。 在此之前的访问,都会报404。
那么问题来了,如何获取当前时区?
public class TC {
public static void main(String[] args) {
ZonedDateTime zonedDateTime = ZonedDateTime.now(); //默认时区
System.out.println(zonedDateTime); //获取时区
}
}
只有在指定时间前,才可以路由到指定微服务。
- Before=2021-01-04T21:44:10.007253800+08:00[Asia/Shanghai]
这里表示,只有在2021年1月4号21点44分10秒之前访问,才可以进行路由。 在此之前的访问,都会报404。
需要指定两个时间,在这两个时间之间的时间才可以进行路由访问。
- Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
只有包含某些指定cookie(key,value)的请求才可以路由。
Cookie Route Predicate 需要两个参数,一个是Cookie name,一个是正则表达式。 路由规则会通过获取对应的Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。
- Cookie=username,donggua #并且Cookie是username=donggua才能访问
只有包含指定请求头的请求,才可以路由。
两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。
- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
测试:
可以发现,请求中必须带有请求头X-Request-Id属性并且值为整数,如果不符合要求,就会报404错误。
只有指定主机的才可以访问,比如我们当前的网站的域名是www.donggua.com 那么这里就可以设置,只有用户是www.donggua.com的请求,才可以进行路由。
Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个ant分割的模板,用逗号作为分隔符。它通过参数中的主机地址作为匹配规则。
- Host=**.donggua.com
可以看到,如果带了域名访问就可以,但是直接访问id地址,就报错了。
只有指定请求才可以路由,比如get请求…
- Method=GET
只有访问指定路径,才进行路由,比如访问,/abc才路由。
- Path=/payment/lb/**
必须带有请求参数才可以访问
-Query=username,\d+
要有参数名username并且值还要是整数才能路由。
比如 http://localhost:9527/?username=123 才可以进行路由。
说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
生命周期: 在请求进入路由之前和处理请求完成。
种类
GatewayFilter(单一)
与断言类似,比如闲置、请求头,只有特点的请求头才放行,反正就过滤。
GlobalFilter(全局)
实现两个接口 GlobalFilter和Ordered
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("*********come in MyLogGateWayFilter: "+new Date());
//获取请求参数username
String uname= exchange.getRequest().getQueryParams().getFirst("uname");
//如果username为空,就直接过滤掉,不走路由
if(StringUtils.isEmpty(uname)){
log.info("*****用户名为Null 非法用户,(┬_┬)");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
return exchange.getResponse().setComplete();
}
//反之,调用下一个过滤器,也就是放行。
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}