【SpringCloud】Gateway新一代网关

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


Gateway是什么

  1. Gateway是继Zuul后的新一代网关,官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

  2. SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供—种简单有效的统一的API路由管理方式。

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

  4. Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

  5. Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloud Gateway一句话: gateway是原zuul1.x版的替代

  6. 小结
    (1)微服务架构中网关在哪里:网关是在微服务之前,挡着外部请求。在这里插入图片描述
    (2)Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
    (3)WebFlux是:
    在这里插入图片描述

(4)Spring Cloud Gateway 与Zuul的区别:
在SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul:

  • Zuul 1.x,是一个基于阻塞I/O的APl Gateway
  • Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如 WebSocket)Zuul的设计模式和Nginx较像,每次I/О操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现Zuul用Java实现,而JVM本身会有第—次加载较慢的情况,使得Zuul的性能相对较差。
  • Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul 2.x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。
  • Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot 2之上,使用非阻塞API。
  • Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验
    (5)Spring Cloud Gateway具有如下特性:
  • 基于Spring Framework 5, Project Reactor和Spring Boot 2.0进行构建
  • 动态路由:能够匹配任何请求属性
  • 可以对路由指定 Predicate(断言)和Filter (过滤器);集成Hystrix的断路器功能
  • 集成Spring Cloud服务发现功能
  • 易于编写的 Predicate (断言)和Filter (过滤器)
  • 请求限流功能
  • 支持路径重写

三大核心

Route(路由)

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

Predicate(断言)
参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。

Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

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

Gateway工作流程

官网提供的流图:
The following diagram provides a high-level overview of how Spring Cloud Gateway works:
在这里插入图片描述

Clients make requests to Spring Cloud Gateway. If the Gateway Handler Mapping determines that a request matches a route, it is sent to the Gateway Web Handler. This handler runs the request through a filter chain that is specific to the request. The reason the filters are divided by the dotted line is that filters can run logic both before and after the proxy request is sent. All “pre” filter logic is executed. Then the proxy request is made. After the proxy request is made, the “post” filter logic is run.

大致可以这样理解:
客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到GatewayWeb Handler。

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

Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

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

Gateway的使用

  1. 下面演示使用的是Eureka注册中心,用哪个注册中心无影响。
  2. 在原来的SpringCloud项目中新建一个Module为普通的Maven项目,名cloud-gateway-gateway9527
  3. pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringcloudTest</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-gateway-gateway9527</artifactId>

    <dependencies>
        <!--新增gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <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-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</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>



</project>
  1. 配置文件application.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,http://eureka7002.com:7002/eureka

  1. 主启动类:
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
    public static void main(String[] args) {
        SpringApplication.run(GateWayMain9527.class,args);
    }
}
  1. 测试我们这个9527网关是挡在cloud-provider-payment8001微服务前面的,8001的搭建参考博客:【SpringCloud】服务注册中心Eureka中的搭建,我需要/payment/{id}/payment/lb配置网关,完整的application配置如下:
server:
  port: 9527
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001   #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/**   #断言,路径相匹配的进行路由

        - id: payment_routh2
          uri: http://localhost:8001
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由


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. 测试:启动7001、7002Eureka集群,9527网关,8001服务
    (1)访问:http://localhost:8001/payment/1在这里插入图片描述
    (2)访问:http://localhost:9527/payment/1在这里插入图片描述
    (3)有什么作用,我们可以暴露微服务的端口,通过网关配置路由,使用统一的9527端口

Gateway网关路由配置方式

有两种,一种是前面通过配置文件配置application.yml配置,还有一种是下面的通过代码中注入RouteLocator的Bean

官网使用案例:在这里插入图片描述

例子:
在9527配置GateWayConfig类:

@Configuration
public class GateWayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("path_rote_baidu",  //自己取的id,不要和其他的重名
                r -> r.path("/test")  // 路由
                        .uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }
}

解释一下:http://localhost:9527/test来代替http://news.baidu.com/guonei

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

之前我们是通过Ribbon(【SpringCloud】Ribbon负载均衡服务调用)来实现负载均衡的,轮流调用8001和8002,现在路由配置是写死的在这里插入图片描述
我们的服务提供者不可能只有8001,那么怎样不写死了,就需要用到GateWay的动态路由。

只需要修改配置文件application.yml就可以实现,修改后完整的application.yml:

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-provider-payment
          predicates:
            - Path=/payment/**   #断言,路径相匹配的进行路由

        - id: payment_routh2
          #uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-provider-payment
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由


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
 
 

添加了一个配置:开启动态路由。
修改了uri为lb://cloud-payment-service:uri的协议为lb,表示启用Gateway的负载均衡功能,后面是微服务提供者的名称。

测试:访问http://localhost:9527/payment/lb,我们可以看到8001和8002两个端口轮流调用。

Predicate(断言)的使用

开启9527Gateway服务时,控制台:在这里插入图片描述
这些都是我们Predicate可以配置的,我们配置文件仅仅配置了Path在这里插入图片描述

我们可以在官网中,找到是如何使用的:在这里插入图片描述

举例使用:

  1. After

大致意思:匹配在指定日期时间之后才能请求
使用:

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - Path=/payment/lb/**
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]

怎样获得当前的时间

public class T2 {
    public static void main(String[] args) {
        ZonedDateTime zonedDateTime = ZonedDateTime.now();
        System.out.println(zonedDateTime);
    }
}

输出:2021-01-30T00:26:35.219+08:00[Asia/Shanghai]

我们可以根据这个来修改上面的After

  1. Cookie

Cookie Route Predicate需要两个参数,一个是Cookie name ,一个是正则表达式。
路由规则会通过获取对应的Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行

配置如下:

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://example.org
        predicates:
        - Cookie=chocolate, ch.p

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

Filter的使用

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。官网:
在这里插入图片描述

可以直接在配置文件中配置即可:

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:
        - AddRequestHeader=X-Request-red, blue

除了官方给的,主要是自定义过滤器:
下面以自定义全局GlobalFilter为例:

新建一个类MyLogGateWayFilter,加注解@Component和实现impiemerts GlobalFilter ,Ordered

@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 username = exchange.getRequest().getQueryParams().getFirst("username");
        if(StringUtils.isEmpty(username)){
            log.info("*****用户名为Null 非法用户,(┬_┬)");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);//交给下一个过滤器链
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

重启服务即可,上面达到一个什么效果:访问每一个网页都需要带上username=XXXX,不然访问不了。
例如:http://localhost:9527/payment/lb?username=z3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值