Gateway服务网关

发布于 2022-04-19  3.37k 次阅读


一,Gateway的概述和底层模型

一, Gateway的概述

  Gateway是Cloud中一个非常重要的网关组件,在SpringCloud1.X版本中都是采用Zuul网关,但在2.X版本中,zuul的升级一直延迟,SpringCloud最后自己研发了一个网关代替Zuul,GateWay代替了zuul1.x版本

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot2和Project Reactor等技术

Gateway亦在提供一种简单有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例:熔断,限流,重试等

SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud2.0以上版本,没有对Zuul2.0以上最新版本进行集成,仍然还是使用的Zuul1.x非Reactor模式的老版本,而为了提升网关性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty

Gateway的主要功能:

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

二,Gateway的非堵塞异步模型

Gateway是基于异步非堵塞模式进行开发的,性能较为优越,虽然Netflix发布了Zuul2.x,但Spring Cloud没有进行整个,而Netflix相关组件宣布加入维护状态

Gateway的特征:基于Spring Framework5.0,Project Rector和Spring Boot2.0进行构建

  1. 动态路由:能够匹配任何请求属性
  2. 可以对路由指定Predictate(断言)和Filter(过滤器)
  3. 集成Hystrix的断路器功能
  4. 集成Spring Cloud服务发现功能
  5. 易于编写
  6. 请求限流
  7. 支持路径重写

Gateway和Zuul的区别:

Zuul:

  1. Zuul1.x基于Servlet2.5使用堵塞架构它不支持任何长连接(如:WebSocket)Zuul的设计模式和Nginx教相似,每一次I/O操作都是从工作线程中选择一个执行,请求线程被堵塞到工作线程完成,但是Nginx由C++实现,Zuul使用java实现,JVM本身第一次加载较慢,使得zuul性能较差
  2. Zuul2.x设计较1.x更丝滑,基于Netty非堵塞和支持长连接,但Spring Cloud目前没有整合,Zuul2.x性能较1.x有很大提升

Gateway:

  1. 使用非堵塞式I/O,还支持websocket长连接
  2. 性能较好,根据官方测试它的性能是Zuul的1.6倍

二,Gateway的工作流程

Gateway的三大核心概念:

  1. Route(路由):路由是构建网关的级别模块,它由ID,目标URL,一系列的断言和过滤器组成,如果断言为true则匹配该路由
  2. Predicate(断言):参考的是java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头和请求参数),如果请求和断言相匹配则进行路由
  3. Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求在路由前或者之后对请求进行修改

   Web请求,通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化控制,predicate就是匹配条件,而filter就可以理解为一个拦截器,有断言匹配和过滤精细控制就可以实现route路由

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

  Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回过滤器之间用虚线分开是因为过滤器可能会再发送代理请求之前(pre)或者之后(post)执行业务逻辑

Filter再pre类型的过滤器可以做参数效验,权限效验,流量监控,日志输出,协议转换等,再post类型的过滤器中可以做响应内容,响应头的修改,日志输出,流量监控等

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

三,Gateway的配置使用

准备:

  • Eureka服务端
  • Eureka客户端(服务提供者)
  • Gateway网关(路由服务提供者)
Eureka Servre
服务提供者
Gateway
9091
8082
9097
Eureka服务端和Eureka客户端省略

①依赖

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

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

<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>

② application配置(路由的服务是eureka博客中演示的服务)

server:
  port: 9097
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes: #路由设置,可以设置多个路由
        - id: payment_routh #路由的ID,没有固定规则但要求唯一(建议配合服务名)
          uri: http://localhost:8082 #匹配后提供服务的路由地址
          predicates: #断言路径相匹配进行断言
            - Path=/payment/select/**
        - id: payment_routh2
          uri: http://localhost:8082
          predicates:
            - Path=/payment/time
eureka:
  instance:
    hostname: cloud-gateway-server
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka1.com:9090/eureka/,http://eureka2.com:9091/eureka/

③ main

@SpringBootApplication
@EnableEurekaClient
public class GatewayMain {
    public static void main(String[] args) {

        SpringApplication.run(GatewayMain.class,args);

    }
}

测试:

二,Gateway的网关配置方式

Gateway有两种网关配置方式:

  • 再application配置文件中进行配置
  • 代码中注入RouteLocator的Bean

① 配置文件配置:

spring:
  cloud:
    gateway:
      routes: #路由设置,可以设置多个路由
        - id: payment_routh #路由的ID,没有固定规则但要求唯一(建议配合服务名)
          uri: http://localhost:8082 #匹配后提供服务的路由地址
          predicates: #断言路径相匹配进行断言
            - Path=/payment/select/**

② 配置类配置(RouteLocator):

@Configuration
public class GatwayConfig {

    @Bean//通过RouteLocatorBuilder进行构建
    public RouteLocator gatewayroute(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        //route的参数id为路由的唯一ID,r.path就是路由的地址(通过断言进行匹配),url为服务地址
        RouteLocator path_route = routes.route("path_route", r -> r.path("/2022/03/23/spring-session/").uri("https://wql.luoqin.ltd/")).build();
        return path_route;
    }

path就是路径,当客户端输入时会通过Gateway进行断言匹配,当匹配成功时,会将地址转发给URI服务地址,最终完成服务

注:在开发中两种配置方式可以同时存在

测试RouteLocator代码配置:

四,Gateway动态路由

  在没有使用Gateway网关时,服务调用使用Ribbon它对注册中心的服务进行负载均衡,现在加入了网关,消费者直接服务调用网关地址,网关在去请求转发,这样负载均衡就用网关的来完成,不需要服务消费者进行调用

以微服务名创建动态路由进行转发

动态路由:在服务集群中,只需要绑定注册中心的服务集群地址,gateway可以动态的在服务集群中访问其中一台机器并可以实现负载均衡

准备:

  • 一个eureka注册中心
  • 两个访问提供者( CLOUD-PAYMENT-SERVER)
  • gateway网关

配置动态路由:

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes: #路由设置,可以设置多个路由
        - id: payment_routh #路由的ID,没有固定规则但要求唯一(建议配合服务名)
          uri: lb://CLOUD-PAYMENT-SERVER #匹配后提供服务的路由地址 lb表示负载均衡
          predicates: #断言路径相匹配进行断言
            - Path=/payment/select/**
        - id: payment_routh2
          uri: lb://CLOUD-PAYMENT-SERVER
          predicates:
            - Path=/payment/time

访问:

五,Gateway高级配置

一,prodicate断言匹配机制

prodicate是一种断言的匹配机制,Gateway默认提供了很多匹配方式如时间,Cookie,Handler,Methder等

Gateway使用AbstractRouteProdieateFactory来实现各种断言方式:

一,时间匹配Prodicate

1,After Route Predicate:在某个时间点之后才能进行访问

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver] #在这个时间之后才能进行访问匹配

注:2017-01-20T17:42:47.789-07:00[America/Denver]是JDK1.8新推出的一种时间格式

2,Before Route Predicate:在某个时间点之前才能访问

- Before=2017-01-20T17:42:47.789-07:00[America/Denver]

3,Between Route Predicate:在两个时间之间才能进行访问

- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

二,Cookie匹配

格式:- Cookie=键,值

注:值可以使用正则表达式进行匹配

例:

- Header=X-Request-Id, \d+ #只能请求头携带X-Request-Id并且值和\d+匹配的请求才能访问

四,Host匹配

路由prodicate工厂采用host一个参数:主机名列表patterns,该模式是一种 Ant 风格的模式,.以分隔符为分隔符,此prodicate匹配Host与模式匹配的标头

例:

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://example.org
        predicates: #请求必须有Host参数,且值必须为.somehost.org或者.anotherhost.org
        - Host=**.somehost.org,**.anotherhost.org

五,Method匹配

根据请求类型进行匹配,如GET,POST,DELETE等

例:

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: https://example.org
        predicates:
        - Method=GET,POST #只要get和post请求才能匹配成功

六,Path路径匹配

根据请求路径进行匹配,很常用的一种匹配方式

例:

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: https://example.org
        predicates:
        - Path=/red/** #只要请求路径以/red开头的请求都进行匹配 **为任意

 

七,Query查询匹配

就是提供请求参数匹配,只不过可以是任意参数

- Query=green #请求必须携带green参数
- Query=green,.*d #请求必须携带green参数,值必须以d结尾

二,Filter过滤器

Gateway的Filtre和Servlet的Filter一样有两种行为:1,pre:在请求之前  2,post:在请求之后

在范围上Filter有两种:

  • GatewayFilter:单一的过滤器
  • GlobalFilter:全局过滤器

Gateway已经根据不同的需求实现了31种Filter,在官网有操作说明(https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-addresponseheader-gatewayfilter-factory)不细说,主要写怎么自定义Filter

一,自定义GlobalFilter

实现两个接口:

  1. GlobalFilter接口
  2. Ordered接口

例:

@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("***********GlobalFilter***********");
        //获取request
        ServerHttpRequest request = exchange.getRequest();
        //获取请求参数wql,假如没有就拦截
        String name = request.getQueryParams().getFirst("wql");
        if(name==null){
            log.info("***********没有wql请求参数拦截***********");
            //拒绝请求
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        //放行
        return chain.filter(exchange);
    }


    @Override
    public int getOrder() { //当前Filter的顺序,加入有多个Filter通过设置数字的大小来决定优先级,默认返回的数字越大优先级越高
        return 0;
    }
}

测试:

 


路漫漫其修远兮,吾将上下而求索