B站尚硅谷P65~P73
代码Gitee地址
以上次博客的eureka集群作为注册中心来演示。
由于Zuul已经称为过去式且Zuul2前景未知,因此使用新一代服务网关GateWay已经成为主流。

1. GateWay

在这里插入图片描述

1.1 概述

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring5,SpringBoot2和Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,目标是代替Zuul。为了提升网关的性能,Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能Reactor模式通信框架Netty。
Gateway的目标提供统一路由方式且基于Filter链的方式提供了网关基本的功能,安全、监控/指标、限流等。
Gateway是基于异步非阻塞模型上开发的,性能比较高。

  • 功能:

    1. 反向代理;
    2. 鉴权;
    3. 流量控制;
    4. 熔断;
    5. 日志监控。
  • 处于项目位置
    在这里插入图片描述

  • 特性:

    1. 动态路由:能够匹配任何请求属性;
    2. 可以对路由指定Predicate(断言)和Filter(过滤器);
    3. 集成Hystrix的断路器功能;
    4. 集成Spring Cloud的服务发现功能;
    5. 易于编写PredicateFilter
    6. 请求限流功能;
    7. 支持路径重写。
  • zuul模型:Zuul是基于servlet之上的一个阻塞式处理模型,当请求进入Servlet Container时,会为当前请求绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发,线程数就会上升,而线程资源的代价是昂贵的,严重影响请求的处理时间,在一些简单的业务场景下,不希望为每个请求分配一个线程,只需要一个或者几个线程就能应对极大的并发请求,这种业务场景下servlet模型没有优势。
    在这里插入图片描述

  • gateway模型:在servlet3.1之后有了异步非阻塞的支持,而WebFlux是一个典型的非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如netty,undertow及支持servlet3.1的容器上。非阻塞式+函数式编程。

1.2 核心概念

  • Router(路由):是构建网关的基本模块,由ID和目标URI和一系列的断言和过滤器组成,如果断言为true则匹配该路由。
  • Predicate(断言):可以匹配Http请求中的所有内容,如果请求与断言相匹配则进行路由。
  • Filter(过滤):使用过滤器,可以在路由被请求前或者请求后对请求进行修改。

根据web请求和匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。即根据predicate的匹配条件,通过过滤器,再加上uri来实现具体的路由。
在这里插入图片描述

1.3 工作流程

  1. 客户端向Spring Cloud Gateway发出请求;
  2. 然后再Gateway Handler Mapping中找到与请求相匹配的路由;
  3. 将其发送到Gateway Web Handler;
  4. Handler再通过指定的过滤器链来将请求发送到实际业务执行逻辑中,返回。
    在这里插入图片描述
  • 请求前过滤器(pre):参数校验、权限校验、流量监控、日志输出、协议转换。
  • 请求后过滤器(post):响应内容和响应头修改,日志输出,流量监控等。

1.4 微服务项目搭建

  • 新建moudle:cloud-gateway-gateway9527

  • 依赖:

        <dependencies>
    
            <dependency>
                <groupId>org.example</groupId>
                <artifactId>cloud-api-commons</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</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>
            </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>
    
  • 配置文件:

    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/,http://eureka7002.com:7002/eureka/
    
  • 主启动类:

    @EnableEurekaClient
    @SpringBootApplication
    public class GatewayMain9527 {
        public static void main(String[] args) {
            SpringApplication.run(GatewayMain9527.class,args);
        }
    }
    

1.5 路由配置详情

1.5.1 yml配置文件
  • 配置路由,避免暴露8001端口,将8001端口路由到当前网关微服务:
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes: #  配置路由
        - id: payment_routh  #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001  #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/**   # 断言,路径相匹配的进行路由
        - id: payment_routh2   
          uri: http://localhost:8001        
          predicates:
            - Path=/payment/lb/**         
  • 访问9527测试:
    在这里插入图片描述
1.5.2 代码中注入RouteLocator的Bean
  • 模拟案例:使用网关9527访问外网:CSDN个人主页

  • 新建配置类:

    @Configuration
    public class GatewayConfig {
    
        @Bean
        public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
            // 路由集合:类似于配置文件的 routes:
            RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
    
            // 指定路由唯一id 和  映射地址path  和 实际地址uri
            routes.route("path_route_bli",
                    r -> r.path("/weixin_44289860")
                            .uri("http://blog.csdn.net/")).build();
            return routes.build();
        }
    
    }
    
  • 访问测试:输入http://localhost:9527/weixin_44289860,直接跳转到我的个人主页。

1.6 动态路由

按照先前针对路由的配置,无法实现负载均衡,仅仅只能访问一个端口。
在这里插入图片描述

这样就需要针对网关再来实现负载均衡的功能,比较繁琐;
可以通过微服务名来实现动态路由

  • 修改配置文件:

    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/**         # 断言,路径相匹配的进行路由
    eureka:
      instance:
        hostname: cloud-gateway-service
      client:
        service-url:
          register-with-eureka: true
          fetch-registry: true
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
    
    
  • 请求测试:可以看到实现了负载均衡。
    在这里插入图片描述

    在这里插入图片描述

1.7 Predicate(断言)

  • 断言分类:
    在这里插入图片描述
  1. After:在某时间之后才能访问;
  2. Before:在某时间之前才能访问;
  3. Between:在某时间之间才能访问;
  4. Cookie:验证Cookie中保存的信息
  5. Header:检查Header中是否包含了响应的属性
  6. Method:请求类型;
  7. Path:即指定当前网关的路径与实际路径的映射。
  8. Query:判断QueryPrameters列表是否存在指定值;
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/**         # 断言,路径相匹配的进行路由
            - After=2022-06-12T20:59:34.102+08:00[Asia/Shanghai]
            - Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
            - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] ,  2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
            - Cookie=username,zzyy   # 

1.8 Filter(过滤器)

允许进入Http请求和返回Http响应之间来进行一系列操作(修改)。

  • 生命周期

    1. pre:请求前过滤;
    2. post:请求后过滤。
  • 种类:

    1. GatewayFilter:针对某个请求的过滤器;
    2. GlobalFitler:全局过滤器。
  • 配置单一过滤器:

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/**         # 断言,路径相匹配的进行路由
            - After=2022-06-12T20:59:34.102+08:00[Asia/Shanghai]
            - Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
            - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] ,  2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
            - Cookie=username,zzyy   #
          filters: # 配置过滤器
            - AddRequestHeader=test,aa #在请求头上添加:key:test,value:aa
  • 自定义全局过滤器:继承implements GlobalFilter, Ordered接口。

    @Slf4j
    @Component
    public class MyLogGatewayFilter implements GlobalFilter, Ordered {
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            log.info("进入自定义全局过滤器:MyLogGatewayFilter");
            // 获取请求参数
            String uname = exchange.getRequest().getQueryParams().getFirst("uname");
            if (Objects.isNull(uname)){
                log.error("非法用户");
                // 返回响应信息
                exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                // 退出
                return exchange.getResponse().setComplete();
            }
            // 携带exchange放行去下一个过滤器
            return chain.filter(exchange);
        }
    
        /**
         * 指定加载过滤器的顺序,数字越小,优先级越高。
         * @return
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐