上一代Netflix的Zuul 1.X:https://github.com/Netflix/zuul/wiki/
Gateway:https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/
Spring Cloud全家桶中有一个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关
但在2.x版本中,Zuul的升级一直跳票,Spring Cloud最后自己研发了一个网关替代Zuul
那就是Spring Cloud Gateway 一句话:Gateway是原Zuul 1.x版的替代
概述
Gateway是在Spring生态系统上构建的API网关服务,基于Spring5,Spring Boot 2和Project Reactor等技术
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断限流重试等
Spring Cloud Gateway是Spring Cloud的一个全新项目,作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上的版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,Spring Cloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则是使用了高性能的Reactor模式通信框架Netty
一句话:Spring Cloud Gateway使用的是WebFlux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
Netflix不太靠谱,Zuul2.0一直跳票,迟迟不发布
一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能Zuul都没有用起来
Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul 2.x,但Spring Cloud貌似没有整合半成品的计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?
多方面综合考虑Gateway是很理想的网关选择
Spring Cloud Gateway具有如下特性:
Spring Cloud Gateway 与Zuul的区别
Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet lO处理模型。
Servlet的生命周期?
servlet由servlet container进行生命周期管理
container启动时构造servlet对象并调用servlet init()进行初始化
container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service()
container关闭时调用servlet destory()销毁servlet
上述模式的缺点
servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程,在并发不高的场景下
这种模型是适用的。但是一旦高并发(比如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势
所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型
,即spring实现了处理所有request请求的一个servlet(DispatcherServlet)并由该servlet阻塞式处理处理。所以Spring Cloud Zuul无法摆脱servlet模型的弊端
补:阻塞与非阻塞 —— https://www.zhihu.com/question/19732473 博主萧萧的讲解
传统的Web框架,比如说 struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的
但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8)
Spring WebFlux是Spring 5.0引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet APl,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范
路由是构建网关的基本模块
它由ID,目标URL,一系列的断言和过滤器组成
如果断言为true则匹配该路由
参考的是Java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),若请求与断言相匹配则进行路由
指的是Spring框架中GatewayFliter的实例
使用过滤器,可以在请求被路由前后对请求进行修改
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制
predicate就是我们匹配的条件,而filter,就可以理解为一个无所不能的拦截器
有了这两个因素,再加上目标uri,就可以实现一个具体的路由了
客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到GatewayWeb Handler
Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用
核心逻辑:路由转发 + 执行过滤器链
新建模块:cloud-gateway-gateway9527
注意:gateway不需要spring web模块的支持,因为gateway是基于netty和webflux的
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>comgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
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
业务类:无
//不写也可以 只要eureka.client.register-with-eureka: true', 网关也是一种微服务,也需要注册进服务中心
@EnableEurekaClient //他们是经过一个注册的方法,(注册到服务中心) 是根据配置文件来的
@SpringBootApplication
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class, args);
}
}
cloud-provider-payment8001看看rest接口的访问地址
@PostMapping("/payment")
public CommonResult<Integer> insert(@RequestBody Payment payment) {
... }
@GetMapping("/payment/discovery")
public Object discovery() {
... }
我们目前不想暴露8001端口,希望在8001外面套一层9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/** #断言,路径相匹配的进行路由
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/discovery/**
启动eureka注册中心7001(单机),启动8001:cloud-provider-payment8001
启动9527网关:cloud-gateway-gateway9527
添加网关前:http://localhost:8001/payment/1,http://localhost:8001/payment/discovery
添加网关前:http://localhost:9527/payment/1,http://localhost:9527/payment/discovery
发现我们成功访问且结果相同,说明网关起效,我们成功的在8001外套了一层!
Gateway网关路由有两种配置方式
第一种就是前面的在配置文件YML中配置,另一种就是代码中注入RouteLocator的Bean
**官网案例 **
自己编码验证实现
@Configuration //表明该类是一个配置类
public class GatewayConfig {
@Bean //将方法的返回值加入到IOC容器中
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("route1", //等同于YML配置中的id
r -> r.path("/guonei").uri("http://news.baidu.com")) //断言,路径相匹配的进行路由
.build();
return routes.build();
}
}
这样访问:http://localhost:9527/guonei 就会路由到 http://news.baidu.com/guonei
默认情况下Gateway会根据注册中心注册的服务列表
以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
一个eureka server7001,两服务提供者8001、8002
新增YML配置
spring.cloud.gateway.discovery.locator.enabled=true
开启从注册中心动态创建路由的功能,利用微服务名进行路由
然后将routes的uri改变为 lb:// + 微服务名称 例:lb://cloud-payment-service
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/** #断言,路径相匹配的进行路由
- id: payment_routh2
# uri: http://localhost:8001
uri: lb://cloud-payment-service
predicates:
- Path=/payment/discovery/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
然后访问 http://localhost:9527/payment/1 就可发现实现了通过微服务名动态路由
我们在启动gateway9527的时候发现控制台打印的日志信息
AfterRoutePredicate 与 BeforeRoutePredicate 与 BetweenRoutePredicate
AfterRoutePredicate
官网配置application.yml
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver] #在此时间之后请求访问才有效果
问题:上述这个After好懂,问题是这个时间串串???
public static void main(String[] args) {
ZonedDateTime time = ZonedDateTime.now(); //默认时区
System.out.println(time); //2020-11-02T21:25:12.805425400+08:00[Asia/Shanghai]
ZonedDateTime time1 = ZonedDateTime.now(ZoneId.of("America/New_York")); //用指定时区获取当前时间
System.out.println(time1); //2020-11-02T08:27:35.633291400-05:00[America/New_York]
}
BeforeRoutePredicate 与 BetweenRoutePredicate
与AfterRoutePredicate相同
BeforeRoutePredicate就是设置在此时间之前的请求访问才有效果
BetweenRoutePredicate就是设置在两个时间之前的请求访问才有效果
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
- Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
CookieRoutePredicate 与 HeaderRoutePredicate 与 HostRoutePredicate
CookieRoutePredicate
Cookie Route Predicate需要两个参数,一个是Cookie name
,一个是正则表达式
路由规则会通过获取相应的Cookie name值和正则表达式相匹配,匹配成功会进行路由,没匹配上则不执行
配置YAML
predicates:
- Path=/payment/1
- Cookie=username,tom
使用cmd的curl指令测试
不带Cookie访问:curl http://localhost:9527/payment/1 -> 报错404
带Cookie访问:curl http://localhost:9527/payment/1 --cookie “username=tom” -> 返回正常数据
HeaderRoutePredicate
如果请求具有名为X-Request-Id
其值与\d+
正则表达式匹配的标头(即,其值为一个或多个数字),则此路由匹配
使用cmd的curl指令测试上述YAML配置
不带Header访问:curl http://localhost:9527/payment/1 -> 报错404
带Header访问:curl http://localhost:9527/payment/1 -H “X-Request-Id:123” -> 返回正常数据
HostRoutePredicate
使用cmd的curl指令测试上述YAML配置
不带Host访问:curl http://localhost:9527/payment/1 -> 报错404
带Host访问:curl http://localhost:9527/payment/1 -H “Host:www.baidu.com” -> 错误404
带Host访问:curl http://localhost:9527/payment/1 -H “Host:www.somehost.org” -> 正确
MethodRoutePredicate 与 PathRoutePredicate 与 QueryRoutePredicate
MethodRoutePredicate
很简单,就是匹配特定的方法,上述就是请求为GET或者POST的情况下才会匹配
PathRoutePredicate
就是我们一直用的,路径相匹配进行路由
QueryRoutePredicate
支持传入两个参数,一个是属性名,一个为属性值,属性值可以是正则表达式
例如: Query=username, \d+ 意为要有参数名称并且是正整数才能路由
总结
说穿了,Predicate就是为了实现一组匹配规则,让请求过来找对应的Route进行处理
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用
Spring Cloud Gateway内置了多种路由过滤器,它们都由GatewayFilter的工厂类来产生
生命周期:pre 与 post
种类
官网:https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories
GatewayFilter:官网上有31种之多
GlobalFilter:10种
AddRequestParameter
spring:
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
filters:
- AddRequestParameter=X-request-Id,1024 #过滤器工厂会在匹配的请求头加上一对 名称为X-request-Id值为1024
两个主要接口 —— implements GlobalFilter, Ordered
能干嘛?
案例代码
@Component //别忘了加入容器中
public class MyGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//你要访问我的时候,我需要你含有一个用户名
String username = exchange.getRequest().getQueryParams().getFirst("username");
//不通过
if (username == null) {
System.out.println("用户名为null,非法用户");
//设置HTTP状态码 —— 不被接受
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
//通过,过滤链续传
return chain.filter(exchange);
}
@Override
//标明加载过滤器的顺序,一般数字越小优先级越高
public int getOrder() {
return 0;
}
}