1. Spring Gateway简介

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

2. 三大核心概念

springcloud hystrix使用 springcloudgetway_spring cloud

route(路由):
路由是网关的基本构件。它由ID、目标URI、谓词集合和过滤器集合定义。如果聚合谓词为真,则匹配路由。

predicate(断言):
参照Java8的新特性Predicate。这允许开发人员匹配HTTP请求中的任何内容,比如头或参数。

filter(过滤器):
可以在发送下游请求之前或之后修改请求和响应。

3. Gateway的工作流程

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

springcloud hystrix使用 springcloudgetway_gateway_02

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

Gateway的核心流程就是:路由转发和过滤器链的执行。

4. Gateway的配置

4.1. 基础配置
  1. 创建网关项目module
  2. 引入网关依赖
<dependencies>
        <!--gateway该依赖排斥starter-web依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.atguigu.springboot</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!--一般基础配置类-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </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>
  1. 在主启动类启用网关
@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
    public static void main(String[] args) {
        SpringApplication.run(GatewayMain9527.class,args);
    }
}
  1. 配置yaml文件
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:
  instance:
    hostname: cloud-gateway-service
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka-server-7001:7001/eureka/
4.2. 创建一个配置类配置网关路由
  1. 创建一个配置类
/**
 * @author 强浩
 * @version 1.0.0
 * Copyright(c) YTANG All Rights Reserved
 * @className
 * @project 管理系统
 * @date 2021年12月17日
 */
@Configuration
public class RouteConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
        RouteLocatorBuilder.Builder routes = builder.routes();
        routes.route("path_route_atguigu",r -> r.path("/guonei").uri("https://www.bilibili.com")).build();
        return routes.build();
    }
}

springcloud hystrix使用 springcloudgetway_gateway_03

5. 使用gateway实现动态路由

使用服务名实现动态路由

目的是可以自动从服务注册中心获取服务名,在Gateway中使用。默认情况下gateway会根据注册中心注册的服务列表,亿注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。

  1. 引入pom依赖
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  1. 配置yaml文件,启用从注册中心动态创建路由的功能,利用微服务名进行调用。
    2.1. 需要注意的是uri的协议为lb,表示启用gateway的负载均衡功能。
    2.2. lb://serviceName 是 spring cloud gateway 在微服务中自动为我们创建的负载均衡uri
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/get/**    #断言,路径匹配的进行路由转发

        - id: payment_routh2
          #uri: http://localhost:8001
          uri: lb://cloud-payment-service #uri的协议为lb,表示启用gateway的负载均衡功能
          predicates:
            - Path=/payment/lb/**
eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka-server-7001:7001/eureka/

6. predicate断言

Predicate(谓语、断言):路由转发的判断条件,目前SpringCloud Gateway支持多种方式,常见如:Path、Query、Method、Header等。

每一个Predicate的使用,你可以理解为:当满足这种条件后才会被转发,如果是多个,那就是都满足的情况下被转发。

springcloud hystrix使用 springcloudgetway_微服务_04

1. 通过请求参数匹配

Query Route Predicate 支持传入两个参数,一个是属性名一个为属性值,属性值可以是正则表达式。

spring:
  cloud:
    gateway:
      routes:
        - id: service3
          uri: https://www.baidu.com
          order: 0  # 排序,数字越小优先级越高
          predicates:
            - Query=keep, pu.

这样只要当请求中包含 keep 属性并且参数值是以 pu 开头的长度为三位的字符串才会进行匹配和路由。用localhost:8080?keep=pub,测试可以返回页面代码,将 keep 的属性值改为 pubx 再次访问就会报 404,证明路由需要匹配正则表达式才会进行路由。

2. 通过Header匹配

Header Route Predicate 和 Query Route Predicate 一样,也是接收 2 个参数,一个 header 中属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。

spring:
  cloud:
    gateway:
      routes:        
        - id: service4
          uri: https://www.baidu.com
          order: 0  # 排序,数字越小优先级越高
          predicates:
            - Header=X-Request-Id, \d+

使用 curl 测试,命令行输入:curl http://localhost:8080 -H “X-Request-Id:88”,则返回页面代码证明匹配成功。将参数-H "X-Request-Id:88"改为-H “X-Request-Id:spring”,再次执行时返回404证明没有匹配。

3. 通过Cookie匹配

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

spring:
  cloud:
    gateway:
      routes:        
        - id: service5
          uri: https://www.baidu.com
          predicates:
            - Cookie=sessionId, test

使用 curl 测试,命令行输入,curl http://localhost:8080 --cookie “sessionId=test”,则会返回页面代码,如果去掉–cookie “sessionId=test”,后台汇报 404 错误。

4. 通过Host匹配

Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。它通过参数中的主机地址作为匹配规则。

spring:
  cloud:
    gateway:
      routes:    
        - id: service6
          uri: https://www.baidu.com
          predicates:
            - Host=**.baidu.com

使用 curl 测试,命令行输入,curl http://localhost:8080 -H "Host: www.baidu.com"或者curl http://localhost:8080 -H “Host: md.baidu.com”,经测试以上两种 host 均可匹配到 host_route 路由,去掉 host 参数则会报 404 错误。

5. 通过请求方式匹配

可以通过是 POST、GET、PUT、DELETE 等不同的请求方式来进行路由。

spring:
  cloud:
    gateway:
      routes:    
        - id: service7
          uri: https://www.baidu.com
          predicates:
            - Method=PUT

curl 测试,命令行输入,curl -X PUT http://localhost:8080,测试返回页面代码,证明匹配到路由,以其他方式,返回 404 没有找到,证明没有匹配上路由

6. 通过请求路径匹配

Path RoutePredicate 接收一个匹配路径的参数来判断是否路由。

spring:
  cloud:
    gateway:
      routes:    
        - id: service8
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/payment/{segment}

如果请求路径符合要求,则此路由将匹配, curl 测试,命令行输入,curl http://localhost:8080/payment/1,
可以正常获取到页面返回值,curl http://localhost:8080/payment2/1,报404,证明路由是通过指定路由来匹配。

7. 组合匹配
spring:
  cloud:
    gateway:
      routes:    
        - id: service9
          uri: https://www.baidu.com
          order: 0
          predicates:
            - Host=**.foo.org
            - Path=/headers
            - Method=GET
            - Header=X-Request-Id, \d+
            - Query=foo, ba.
            - Query=baz
            - Cookie=chocolate, ch.p

各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。一个请求满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发

7. filter

filter的作用和生命周期

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

生命周期
Spring Cloud Gateway同zuul类似,有“pre”和“post”两种方式的filter。客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务,比如上图中的user-service,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端。

filter的配置及使用

在Spring Cloud Gateway中,filter从作用范围可分为另外两种,一种是针对于单个路由的gateway filter,它在配置文件中的写法同predict类似;另外一种是针对于所有路由的global gateway filer。现在从作用范围划分的维度来讲解这两种filter。

gateway filter(单个路由过滤)

GatewayFilter工厂同上一篇介绍的Predicate工厂类似,都是在配置文件application.yml中配置,遵循了约定大于配置的思想,只需要在配置文件配置GatewayFilter Factory的名称,而不需要写全部的类名,比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全部类名。在配置文件中配置的GatewayFilter Factory最终都会相应的过滤器工厂类处理。

springcloud hystrix使用 springcloudgetway_Cloud_05

自定义过滤器(全局路由过滤)

spring cloud gateway之filter篇

自定义过滤器:
在spring Cloud Gateway中,过滤器需要实现GatewayFilter和Ordered2个接口

public class RequestTimeFilter implements GatewayFilter, Ordered {

    private static final Log log = LogFactory.getLog(GatewayFilter.class);
    private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
                    if (startTime != null) {
                        log.info(exchange.getRequest().getURI().getRawPath() + ": " + (System.currentTimeMillis() - startTime) + "ms");
                    }
                })
        );

    }

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