SpringCloud笔记——Gateway服务网关

概述简介


官网

上一代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通讯框架

能干嘛?

  • 反向代理
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控
  • 。。。。

在这里插入图片描述

有了Zuul怎么又出来了Gateway

为什么选择Gateway?

Netflix不太靠谱,Zuul2.0一直跳票,迟迟不发布

一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能Zuul都没有用起来

Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul 2.x,但Spring Cloud貌似没有整合半成品的计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?

多方面综合考虑Gateway是很理想的网关选择

Spring Cloud Gateway具有如下特性:

  • 基于Spring Framework 5,Project Reactor和Spring Boot 2.0进行构建
  • 动态路由:能够匹配任何请求属性
  • 可以对路由指定Predicate(断言)和Filter (过滤器)
  • 集成Hystrix的断路器功能
  • 集成Spring Cloud 服务发现功能
  • 易于编写的 Predicate (断言)和Filter (过滤器);请求限流功能
  • 支持路径重写

Spring Cloud Gateway 与Zuul的区别

  1. 在SpringCloud Finchley 正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul:
  2. Zuul 1.x,是一个基于阻塞I/O的APl Gateway
  3. Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如 WebSocket)Zuul的设计模式和Nginx较像,每次IО操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用的是Java实现,而JVM本身会有第1次加载较慢的情况,使得Zuul的性能相对较差
  4. Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zul x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍
  5. Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot 2之上,使用非阻塞APl
  6. Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验
Zuul 1.x模型

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 博主萧萧的讲解

Gateway模型

传统的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来实现响应式流规范

Gateway三大核心概念


Route(路由)

路由是构建网关的基本模块

它由ID,目标URL,一系列的断言和过滤器组成

如果断言为true则匹配该路由

Predicate(断言)

参考的是Java8的java.util.function.Predicate

开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),若请求与断言相匹配则进行路由

Fliter(过滤)

指的是Spring框架中GatewayFliter的实例

使用过滤器,可以在请求被路由前后对请求进行修改

在这里插入图片描述

web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制

predicate就是我们匹配的条件,而filter,就可以理解为一个无所不能的拦截器

有了这两个因素,再加上目标uri,就可以实现一个具体的路由了

Gateway工作流程


在这里插入图片描述

客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到GatewayWeb Handler

Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回

过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑

Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等

在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用

核心逻辑:路由转发 + 执行过滤器链

入门配置


新建模块:cloud-gateway-gateway9527

POM

注意:gateway不需要spring web模块的支持,因为gateway是基于netty和webflux的

    <dependencies>
        <!--新增gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--自己封装的entity,返回结果用-->
        <dependency>
            <groupId>com</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>
    </dependencies>

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

业务类:无

主启动类

//不写也可以  只要eureka.client.register-with-eureka: true',     网关也是一种微服务,也需要注册进服务中心
@EnableEurekaClient              //他们是经过一个注册的方法,(注册到服务中心) 是根据配置文件来的
@SpringBootApplication
public class GatewayMain9527 {
    public static void main(String[] args) {
        SpringApplication.run(GatewayMain9527.class, args);
    }
}

9527网关如何做路由映射?

cloud-provider-payment8001看看rest接口的访问地址

    @PostMapping("/payment")
    public CommonResult<Integer> insert(@RequestBody Payment payment) { ... }
        
    @GetMapping("/payment/discovery")
    public Object discovery() { ... }

我们目前不想暴露8001端口,希望在8001外面套一层9527

YML新增网关配置

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外套了一层!

YML配置说明

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

新增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 就可发现实现了通过微服务名动态路由

Gateway常用的Predicate


是什么?

我们在启动gateway9527的时候发现控制台打印的日志信息

在这里插入图片描述

What is RoutePredicateFactory?

在这里插入图片描述

常用的Route Predicate

在这里插入图片描述

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进行处理

Fliter的使用


Fliter是什么?

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用

Spring Cloud Gateway内置了多种路由过滤器,它们都由GatewayFilter的工厂类来产生

Spring Cloud Gateway的Filter

生命周期:pre 与 post

种类

官网:https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories

GatewayFilter:官网上有31种之多

GlobalFilter:10种

常用的GatewayFilter

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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大恐龙的小弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值