2

1.Gateway简介

2.为什么有了zuul,我们还要用Gateway

3.Gateway的三大核心概念

4.Gateway工作流程

5.Gateway的简单配置

6.Gateway通过微服务名实现动态路由

7.Gateway的Predicate

8.Gateway的Filter

1.Gateway简介
在了解gateway之前,我们可以先登录官网看一下gateway是一个什么样的工具:https://docs.spring.io/spring...

image.png

SpringCloud Gateway 是 Spring Cloud 的一个全新项目,它旨在为微服务架构提供一种简单有效统一api路由管理方式

SpringCloud Gateway 作为springCloud生态系统中的网关,目标是代替zuul,在Spring Cloud2.0以上的版本中,没有对新版本Zuul 2.0以上最高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

说了这么多,我们可能还是不明白,我们再把上面的话精简一下:

Spring Cloud Gateway 是一款网关的工具。在以前的spring Cloud版本中,采用的都是zuul网关,但是2.x版本中,zuul的升级一直没有落实到位,spring Cloud最后自己研发了一个Spring cloud gateway代替zuul。

Spring Cloud Gateway的作用:
像反向代理,权限鉴定,流量控制,负载均衡,日志监控等等,它都能做到。

Spring Cloud Gateway位于我们系统的哪个位置?

这是官网给的图例,
image.png
我们从这张图中可以看出,gateway的位置是一个比较靠前的位置,起了整个系统的流量入口的作用,但是实际生产中,我们不止一个网关,也是集群模式的,所以就有了下面这张图:

image.png

2.为什么有了zuul,我们还要用Gateway

原因一zuul发布太慢,Netflix的相关组件都进入了维护期,综合考虑,还是自己开发一个Gateway是一个很理想的选择。

原因二:Spring Cloud Gateway具有以下特性
2.1.1)动态路由:能匹配任何请求属性
2.1.2)集成Hystrix的断路器功能
2.1.3)集成 Spring Cloud 服务发现功能
2.1.4)有 Predicate(断言)和 Filter(过滤器)功能,极易编写
2.1.5)请求限流功能
2.1.6)支持路径重写

原因三:SpringCloud Gateway 与 Zuul的区别。

spring Cloud以前的网关是zuul

2.2.1)zuul 1.x是一个基于阻塞I/O的api gateway

2.2.2)Zuul 1.x基于servlet2.5使用阻塞架构,所以它不支持任何长链接,每次I/O操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,而且底层是java实现,jvm有那种第一次加载较慢的情况,所以zuul性能不佳

2.2.3)zuul 2.x理念更先进,想基于Netty非阻塞和支持长链接,但Spring Cloud目前自己做了一个Gateway,所以还没有整合

2.2.4)Spring Cloud Gateway 还支持 WebSocket, 并且与Spring紧密集成拥有更好的开发体验

我们看一下zuul 1.x模型的特点
在早期的zuul版本中,采用的是tomcat容器,也就是传统的servlet IO模型
我们来复习一下servlet的生命周期
servlet由servlet容器进行生命周期的管理。
容器启动的时候,会构造servlet对象并调用servlet的init()进行初始化。
容器运行的时候,接受请求,为每个请求分配一个线程(从线程池中获取空闲线程),然后调用service()。
容器关闭的时候,调用servlet destory()销毁servlet。

image.png

上述模式的缺点:
servlet是一个简单的网络IO模型,当请求进入servlet容器的时候,servlet容器就会为其绑定一个线程,在并发不高的情况下,这种模型是适用的,但是一旦并发量提高,线程数量就会上涨,而线程资源的代码比较昂贵(上下文切换,内存消耗),严重影响请求的处理时间。在一些简单的业务场景下,不希望为每个request分配线程,只需要1个或者几个线程就能对应极大并发的请求,这种场景下servlet的模型就没有优势。

所以zuul 1.x是基于servlet之上的一个阻塞式处理器,spring实现了处理所有request请求的一个servlet(DispatcherServlet)并由该阻塞式处理器处理,所以springcloud zuul无法摆脱servlet模型的弊端。

接下来我们来看看GateWay模型

新的gateway模型采用了spring WebFlux的模型https://docs.spring.io/spring...

传统的框架,如spring mvc,struts2,都是基于servlet api和servlet容器之上运行的。

但是servlet3.1之后有了异步非阻塞的支持,而webFlux是一个典型的非阻塞异步的框架,它使用了一个非阻塞的web堆栈来处理少量线程的并发性并以更少的硬件资源进行扩展

3.Gateway的三大核心概念
3.1)Route(路由)
路由是构建网关的基本模块,由id,目标uri,一系列的断言和过滤器组成,就相当于把一个访问路径转发到另外一个访问路径,其中包含了一系列规则,如果断言为true则匹配该路由。

3.2)Predicate(断言)
匹配HTTP请求中的所有内容,如果请求与断言相匹配,则进行路由。

3.3)Filter(过滤)
使用过滤器,可以在请求在路由之前或者之后进行修改。

image.png

通过这张流程图可以看出,当一个web请求进来后,通过predicate匹配条件,就可以定位到真正的服务节点同时在转发过程的前后,通过filter进行一些精细化控制。

4.Gateway工作流程

我们看看官网是如何介绍它的工作流程的:

image.png

image.png

客户端向Spring Cloud Gateway发出请求,如果网关处理器匹配到了路由就将请求发送到网关处理程序。处理器再通过该请求指定的过滤器链,将请求发送给实际服务,让服务执行业务逻辑,然后返回。

过滤器之间用需先分开是因为我们可以在发送请求之前和之后运行逻辑。

在pre可以用来做参数校验,权限校验,流量监控,日志输出等等
在post可以用来做相应内容,响应头的修改,日志的输出,流量监控等等。

5.Gateway的简单配置
pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>cloud-gateway-9000</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cloud-gateway-9000</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- gateway要排除这个依赖,因为 gateway已经自带了spring-boot-starter-web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>*</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

yml:

server:
  port: 9000

eureka:
  instance:
    hostname: cloud-gateway #eureka服务端的实例名称
    prefer-ip-address: true #访问路径可以显示IP地址
    instance-id: ${spring.cloud.client.ip-address}:${server.port}-cloud-gateway #访问路径的名称格式
  client:
    register-with-eureka: true     #false表示不向注册中心注册自己。
    fetch-registry: true     #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      #集群指向其它eureka
      defaultZone: http://127.0.0.1:8001/eureka/,http://127.0.0.1:8002/eureka/
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: routh1 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8501          #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

然后,我们通过网关访问接口,就可以转发过去了:http://localhost:9000/payment...

image.png

6.Gateway通过微服务名实现动态路由

我们上面只是把一个接口固定转发给另外一个服务而已,但是服务的端口可能是变化的而且还是集群部署,需要负载均衡的,我们不可能把服务名加端口号写死,更不可能只转发到一个服务提供者,所以我们需要通过微服务名称调用+负载均衡:

pom:

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: routh1 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8501          #匹配后提供服务的路由地址
          uri: lb://service-provider #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

image.png

image.png

7.Gateway的Predicate

上述predicates只是用来做一个uri地址的匹配和转移,其实predicates还有许多的作用,Spring Cloud Gateway包含许多内置的Route Predicate工厂。这些Predicate都与HTTP请求的不同属性匹配,比如有以下这些匹配方式:
时间匹配(某个时间点前/后/区间才开放这个转发)

- id: routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001          #匹配后提供服务的路由地址
uri: lb://service-provider #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/**         # 断言,路径相匹配的进行路由
- After=2022-02-05T15:10:03.685+08:00[Asia/Shanghai]         # 这个时间之后再开放转发
- Before=2022-02-05T15:10:03.685+08:00[Asia/Shanghai]         # 这个时间之前开放转发
- Between=2022-02-02T17:45:06.206+08:00[Asia/Shanghai],2022-03-25T18:59:06.206+08:00[Asia/Shanghai] # 这个时间区间开放转发

cookie(cookie一定要带某个参数)

- id: routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001          #匹配后提供服务的路由地址
uri: lb://service-provider #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/**         # 断言,路径相匹配的进行路由
- Cookie=username,slf

Header匹配(请求头参数匹配)

- id: routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001          #匹配后提供服务的路由地址
uri: lb://service-provider #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/**         # 断言,路径相匹配的进行路由
- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式

请求方式匹配(Get,POST等等)

- id: routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001          #匹配后提供服务的路由地址
uri: lb://service-provider #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/**         # 断言,路径相匹配的进行路由
- Method=GET

详情更多可以看官网:https://cloud.spring.io/sprin...

说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。

8.Gateway的Filter

我们看完上述的配置,但是还是发现一个缺点,难不成我们每个接口都要去配置一遍转发的接口吗?

这个时候,我们就可以使用Filter————过滤器!

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

server:
  port: 9000

eureka:
  instance:
    hostname: cloud-gateway #eureka服务端的实例名称
    prefer-ip-address: true #访问路径可以显示IP地址
    instance-id: ${spring.cloud.client.ip-address}:${server.port}-cloud-gateway #访问路径的名称格式
  client:
    register-with-eureka: true     #false表示不向注册中心注册自己。
    fetch-registry: true     #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      #集群指向其它eureka
      defaultZone: http://127.0.0.1:8001/eureka/,http://127.0.0.1:8002/eureka/
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: routh1 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8501          #匹配后提供服务的路由地址
          uri: lb://service-provider/** #匹配后提供服务的路由地址
          predicates:
            - Path=/gateway/service-provider/**         # 断言,路径相匹配的进行路由,我们可以通过服务名称来指定接口,这个服务的接口全部转到这个服务的接口下面,并且去掉两个参数
          filters:
            - StripPrefix=2  # 去掉path的/gateway/service-provider,将尾巴部分接到url上,StripPrefix=几 就是去掉几个参数

更多过滤器请看:https://cloud.spring.io/sprin...


苏凌峰
73 声望38 粉丝

你的迷惑在于想得太多而书读的太少。