• SpringCloud - GateWay服务网关


    一. Gateway概述

    1.Gateway是什么

    gateway 官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

    微服务架构中,一个系统往往由多个微服务组成,而这些服务可能部署在不同机房、不同地区、不同域名下。这种情况下,客户端(例如浏览器、手机、软件工具等)想要直接请求这些服务,就需要知道它们具体的地址信息,例如 IP 地址、端口号等。

    这种客户端直接请求服务的方式存在以下问题:

    • 当服务数量众多时,客户端需要维护大量的服务地址,这对于客户端来说,是非常繁琐复杂的。
    • 在某些场景下可能会存在跨域请求的问题。
    • 身份认证的难度大,每个微服务需要独立认证。

    2. Gateway作用

    API 网关是一个搭建在客户端和微服务之间的服务,我们可以在 API 网关中处理一些非业务功能的逻辑,例如权限验证、监控、缓存、请求路由等。

    API 网关就像整个微服务系统的门面一样,是系统对外的唯一入口。有了它,客户端会先将请求发送到 API 网关,然后由 API 网关根据请求的标识信息将请求转发到微服务实例。

    对于服务数量众多、复杂度较高、规模比较大的系统来说,使用 API 网关具有以下好处:

    • 客户端通过 API 网关与微服务交互时,客户端只需要知道 API 网关地址即可,而不需要维护大量的服务地址,简化了客户端的开发。
    • 客户端直接与 API 网关通信,能够减少客户端与各个服务的交互次数。
    • 客户端与后端的服务耦合度降低。
    • 节省流量,提高性能,提升用户体验。
    • API 网关还提供了安全、流控、过滤、缓存、计费以及监控等 API 管理功能。

    常见的 API 网关实现方案主要有以下 5 种:

    • Spring Cloud Gateway
    • Spring Cloud Netflix Zuul
    • Kong
    • Nginx+Lua
    • Traefik

    3. 微服务架构中网关的位置

    在这里插入图片描述

    4. SpringCloud Gateway概念

    SpringCloud Gateway 是在建立在 Spring 生态系统之上的 API 网关服务,基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式,以及提供一些强大的过滤器功能,例如:安全,监控/指标、熔断、限流、重试等。

    Spring Cloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。

    5. SpringCloud Gateway IO 模型

    Zuul 1.x 的 IO 模型:

    SpringCloud 中所集成的 Zuul 1.x 版本,采用的是 Tomcat 容器,使用的是传统的 Servlet IO 处理模型。servlet 由 servlet container 进行生命周期管理:

    1. container 启动时构造 servlet 对象并调用 servlet.init() 进行初始化;
    2. container 关闭时调用 servlet.destory() 销毁 servlet;
    3. container 运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用 service()。

    在这里插入图片描述

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

    所以 Zuul 1.x 是基于 servlet 阻塞 IO 模型的 API 网关,即 Spring 实现了处理所有 request 请求的一个 servlet(DispatcherServlet),并由该 servlet 阻塞式处理处理。每次 I/О 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成。所以 Zuul 1.x 无法摆脱 servlet 模型的弊端。

    虽然 Zuul 2.0 开始,使用了 Netty 非阻塞和支持长连接,并且已经有了大规模 Zuul 2.0 集群部署的成熟案例,但是,SpringCloud 官方已经没有集成该版本的计划了。

    GateWay 非阻塞异步模型

    传统的Web框架,比如说:Struts2,SpringMVC 等都是基于 Servlet APl 与 Servlet 容器基础之上运行的。

    但是在 Servlet3.1 之后有了异步非阻塞的支持。而 WebFlux 是一个典型非阻塞异步的框架,它的核心是基于 Reactor 的相关 API 实现的。Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖 Servlet APl,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。

    SpringCloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。

    二.Gateway的三大核心概念

    1. Route 路由

    路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。

    2. Predicate 断言

    路由转发的判断条件,我们可以通过 Predicate 对 HTTP 请求进行匹配,例如请求方式、请求路径、请求头、参数等,如果请求与断言匹配成功,则将请求转发到相应的服务

    3. Filter 过滤

    过滤器,我们可以使用它对请求进行拦截和修改,还可以使用它对上文的响应进行再处理。

    4. 总结

    • web 请求,通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化控制
    • predicate 就是我们的匹配条件
    • filter:就可以理解为一个无所不能的拦截器,有了这两个元素,再加上目标的uri,就可以实现一个具体的路由了。

    在这里插入图片描述

    三.Spring Cloud Gateway工作流程

    在这里插入图片描述

    Spring Cloud Gateway 工作流程说明如下:

    1. 客户端将请求发送到 Spring Cloud Gateway 上。
    2. Spring Cloud Gateway 通过 Gateway Handler Mapping 找到与请求相匹配的路由,将其发送给 Gateway Web Handler
    3. Gateway Web Handler 通过指定的过滤器链(Filter Chain),将请求转发到实际的服务节点中,执行业务逻辑,返回响应结果返回给客户端。
    4. 过滤器之间用虚线分开是因为过滤器可能会在转发请求之前(pre)或之后(post)执行业务逻辑。
    5. 过滤器(Filter)可以在请求被转发到服务端前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等。
    6. 过滤器可以在响应返回客户端之前,对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。

    总而言之,客户端发送到 Spring Cloud Gateway 的请求需要通过一定的匹配条件,才能定位到真正的服务节点。在将请求转发到服务进行处理的过程前后(pre 和 post),我们还可以对请求和响应进行一些精细化控制。

    Predicate 就是路由的匹配条件,而 Filter 就是对请求和响应进行精细化控制的工具。有了这两个元素,再加上目标 URI,就可以实现一个具体的路由了。

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

    四.服务搭建

    1. 创建cloud-gateway-gateway-9527 模块

    2. 写pom

    做网关不需要添加 web starter

    
    <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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud2022artifactId>
            <groupId>com.jm.springcloudgroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>cloud-gateway-gateway9527artifactId>
    
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-gatewayartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            
            <dependency>
                <groupId>com.jm.springcloudgroupId>
                <artifactId>cloud-api-commonsartifactId>
                <version>${project.version}version>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    
    
    project>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    3. 改yml

    server:
      port: 9527
    
    spring:
      application:
        name: cloud-gateway
    
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4. 主启动

    package com.jm.springcloud;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    @SpringBootApplication
    @EnableEurekaClient
    public class GateWayMain9527 {
         public static void main(String[] args) {
               SpringApplication.run(GateWayMain9527.class, args);
         }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5. 网关路由映射

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

    YML新增网关配置

    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:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    6. 测试

    启动网关前访问:http://localhost:8001/get/payment/31
    启动网关后访问:http://localhost:9527/get/payment/31

    五.路由配置的两种方式

    1. YML 配置

    spring:
      application:
        name: cloud-gateway
      cloud:
        gateway:  #网关路由配置
          routes:
            - id: payment_routh    #路由的ID,没有固定规则但要求唯一,建议与服务名对应
              uri: http://localhost:8001  #匹配后提供服务的路由地址
              predicates:
                - Path=/payment/getPaymentById/**    #断言,路径相匹配的进行路由
                - Method=GET 	#只能时GET请求时,才能访问
            - id: payment_routh2   #路由的ID,没有固定规则但要求唯一,建议与服务名对应
              uri: http://localhost:8001  #匹配后提供服务的路由地址
              predicates:
                - Path=/payment/lb/**   #断言,路径相匹配的进行路由
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    测试地址:http://localhost:9527/payment/get/31

    2. 代码配置

    当请求路径为 /guonei时,转发到 https://news.baidu.com/guonei

    @Configuration
    public class GateWayConfig {
        @Bean
        public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
            RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
            routes.route("path_route",
                    r -> r.path("/guonei")
                            .uri("https://news.baidu.com/guonei"));
            return routes.build();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    测试:http://localhost:9527/guonei

    六.Gateway 动态路由

    相当于给网关配置一个负载均衡,因为看上面的配置把8001写死了

    默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

    开启动态路由:spring.cloud.gateway.discovery.locator.enabled:true;

    在添加uri的时候,开头是 lb://微服务名,lb: 负载均衡协议

    spring:
      application:
        name: cloud-gateway
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
          routes:
            - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service     #lb:负载均衡协议
              predicates:
                - Path=/payment/get/**            # 断言,路径相匹配的进行路由
    
            - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service     #lb:负载均衡协议
              predicates:
                - Path=/payment/lb/**             # 断言,路径相匹配的进行路由
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    测试:

    因为开启了8001和8002两个端口,所以网关负载均衡的效果是 8001/8002切换

    七.Predicate 断言的使用

    Spring Cloud Gateway 通过 Predicate 断言来实现 Route 路由的匹配规则。简单点说,Predicate 是路由转发的判断条件,请求只有满足了 Predicate 的条件,才会被转发到指定的服务上进行处理。

    使用 Predicate 断言需要注意以下 3 点:

    • Route 路由与 Predicate 断言的对应关系为“一对多”,一个路由可以包含多个不同断言。
    • 一个请求想要转发到指定的路由上,就必须同时匹配路由上的所有断言。
    • 当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。

    参考gateway官网的断言的例子:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-after-route-predicate-factory

    1. 常用的断言

    常用的Route Predicate

    After Route Predicate

    • - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
    • 匹配该断言时间之后的 uri请求

    Before Route Predicate

    • - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
      - Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]

    Between Route Predicate

    • - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]

    Cookie Route Predicate

    不带cookies访问

    带上cookies访问 - Cookie=username,atguigu #并且Cookie是username=atguigu 才能访问

    Cookie Route Predicate 需要两个参数,一个时Cookie name,一个是正则表达式。

    路由规则会通过获取对应的Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上就不执行

    Header Route Predicate
    两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行;

    Host Route Predicate

    • - Host=**.atguigu.com

    Method Route Predicate

    • - Method=GET

    Path Route Predicate

    == Query Route Predicate==

    • - Query=username, \d+ #要有参数名称并且是正整数才能路由

    2.小总结(pom)

    server:
      port: 9527
    
    spring:
      application:
        name: cloud-gateway
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
          routes:
            - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service     #lb:负载均衡协议
              predicates:
                - Path=/payment/get/**            # 断言,路径相匹配的进行路由
    
            - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service     #lb:负载均衡协议
              #filters:
              #  - AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
              predicates:
                - Path=/payment/lb/**             # 断言,路径相匹配的进行路由
                #- After=2022-11-26T19:14:10.785+08:00[Asia/Shanghai]
                #- Before=2022-11-26T19:22:29.119+08:00[Asia/Shanghai]
                #- Between=2022-11-26T19:14:10.785+08:00[Asia/Shanghai], 2022-11-26T19:22:29.119+08:00[Asia/Shanghai]
                #- Cookie=username,zzyy
                #- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式
                #- Host=**.atguigu.com
                #- Method=GET
                #- Query=username, \d+  # 要有参数名username并且值还要是整数才能路由
    
    eureka:
      instance:
        hostname: cloud-gateway-service
      client: #服务提供者provider注册进eureka服务列表内
        service-url:
          register-with-eureka: true
          fetch-registry: true
          defaultZone: http://eureka7001.com:7001/eureka
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    八.Filter 过滤器

    1. 什么是Filter

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

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

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

    声明周期 :

    • pre 在业务逻辑之前
    • post 在业务逻辑之后

    种类:

    • 单一的:GatewayFilter
    • 全局的:GlobalFilter

    2. 自定义过滤器

    2.1 实现两个接口

    implements GlobalFilterOrdered

    2.2 作用

    全局日志记录
    统一网关鉴权

    2.3 演示代码

    package com.jm.springcloud.filter;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import java.util.Date;
    
    @Component
    @Slf4j
    public class MyLogGateWayFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            log.info("************come in MyLogGateWayFilter: "+new Date());
            String uname = exchange.getRequest().getQueryParams().getFirst("uname");
            if(uname==null){
                log.info("********用户名为null,非法用户,/(ㄒoㄒ)/~~");
                exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
  • 相关阅读:
    ELK Stack日志采集架构详解
    Davinci Developer Classic SWC新建port并连接非complete port方式
    105、python-第四阶段-2-装饰器
    .NET Core Configuration 配置项知识点一网打尽!
    Mysql生成数据字典
    智慧厕所解决方案给生活带来了什么好处?
    C#堆排序算法
    docker服务CPU飙高排查
    redis21道经典面试题,极限拉扯面试官
    程序员第一次接私活?记住这三点让你事半功倍
  • 原文地址:https://blog.csdn.net/m0_66689823/article/details/128057839