Spring Cloud系列教程(十七):下一代网关服务Gateway-谓词篇(Finchley版本)

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

一. 下一代微服务网关Gateway

来自官方文档的一句话: Spring Cloud Gateway提供了一个在Spring生态系统之上构建的API网关,包括:Spring 5,Spring Boot 2和Project Reactor。Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注,例如:安全性,监视/指标和弹性。并且从引入的spring-cloud-starter-gateway依赖信息可见,Spring Cloud Gateway底层是基于SpringBoot2.x、Project Reactor和Spring5 WebFlux响应式驱动编程构建,而Spring5底层又是基于netty异步实现,可谓性能不一般啊。
在这里插入图片描述
由于响应式编程的特性,Spring WebFlux和Reactor底层需要支持异步的运行环境,比如Netty和Undertow;也可以运行在支持异步I/O的Servlet 3.1的容器之上,比如Tomcat(8.0.23及以上)和Jetty(9.0.4及以上)。而且Spring Cloud Gateway底层默认通过netty启动,所以在使用Gateway网关组件时,无需引入Tomcat容器组件;

二. 网关解决了什么问题

https://mrhelloworld.com/resources/articles/spring/spring-cloud/gateway/280044-20170731002541646-1718153933.png
网关具有身份认证与安全、审查与监控、动态路由、负载均衡、缓存、请求分片与管理、静态响应处理等功能。当然最主要的职责还是与“外界联系”。

总结一下,网关应当具备以下功能:

性能: API 高可用,负载均衡,容错机制。
安全: 权限身份认证、脱敏,流量清洗,后端签名(保证全链路可信调用),黑名单(非法调用的限制)。
日志: 日志记录,一旦涉及分布式,全链路跟踪必不可少。
缓存: 数据缓存。
监控: 记录请求响应数据,API 耗时分析,性能监控。
限流: 流量控制,错峰流控,可以定义多种限流规则。
灰度: 线上灰度部署,可以减小风险。
路由: 动态路由规则。

三. Gateway与Zuul的关系

Zuul是由Netflix公司所开源的一款网关组件,SpringBoot只是将Zuul集成在了Spring Cloud中使用,完善Spring Cloud生态圈;而Spring Cloud Gateway是Spring Cloud的一个子项目,在Zuul出来之后,由Spring自己又研发出来了下一代微服务网关组件,取名为:Spring Cloud Gateway;Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且还基于 Filter 链的方式提供了网关基本的功能。目前最新版 spring-cloud-starter-netflix-zuul依赖中引用的还是 Zuul 1.x 版本,而Zuul 1.x 版本是一个基于阻塞IO的API 网关,是阻塞 IO,不支持长连接。
在这里插入图片描述
在Zuul1.x版本中,默认是阻塞的,但是2019 年 5 月,Netflix 开源了支持异步调用模式的 Zuul 2.0 版本,基于Netty,是非阻塞的,支持长连接的API网关。

四. Gateway核心词汇表

  • Route: 网关的基本构建块路由。它由ID,目标URI,谓词集合和过滤器集合定义。如果聚合谓词为true,则匹配路由。

  • Predicate: 这是Java 8函数谓词,Spring Cloud Gateway 中的谓词函数输入类型是 Spring 5.0 框架中的 ServerWebExchange。Spring Cloud Gateway 中的谓词函数允许开发者去定义匹配来自于 Http Request 中的任何信息,比如请求头和参数等。

  • Filter: 一个标准的 Spring Web Filter。Spring Cloud Gateway 中的 Filter 分为两种类型,分别是 Gateway Filter 和 Global Filter。过滤器将会对请求和响应进行处理。

五. Gateway工作原理

下图从总体上概述了Spring Cloud Gateway的工作方式:
在这里插入图片描述
流程: 客户端向Spring Cloud Gateway发出请求,然后Gateway Handler Mapping寻找确定请求与路由匹配,匹配到路由后,将其发送到 Gateway Web Handler,Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回响应结果。

六. 准备工作

搭建注册中心springcloud-eureka-server:8888、网关服务springcloud-api-gateway:8015、商品服务springcloud-product-service:8016三个工程。客户端发起请求,调用API接口,首先通过gateway服务,路由到具体的API服务接口执行业务逻辑,返回响应结果。

1. Eureka注册中心

eureka注册中心工程在前面的每篇博客环境中都有用到,工程已经有现有的,不需要再重复搭建,所以这里搭建注册中心环境的过程省略掉,小伙伴们可以去GitHub上下载eureka已经搭好的工程:
https://github.com/Thinkingcao/SpringCloudLearning/tree/master/springcloud-eureka

2. Gateway网关服务

  • pom.xml
  <groupId>org.example</groupId>
    <artifactId>spring-api-gateway</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>springcloud-api-gateway</name>
    <description>SpringCloud整合Gateway网关服务</description>

    <!--SpringBoot依赖版本-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/>
    </parent>

    <!--项目编码、jdk版本、SpringCloud版本定义-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <!--声明管理SpringCloud版本依赖信息-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

     <dependencies>
        <!-- SpringBoot整合Gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!--SpringCloud整合-eureka-client组件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
  • application.yml
server:
  port: 8015
#定义服务名称
spring:
  application:
    name: api-gateway

#在此指定服务注册中心地址,将当前服务注册到eureka注册中心上
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8888/eureka
    #启动注册操作,该值默认为true。若设置为fasle将不会启动注册操作。是否需要去检索寻找服务,默认是true
    register-with-eureka: true
    #是否需要从eureka上获取注册信息
    fetch-registry: true

#Gateway日志级别
logging:
  level:
    org.springframework.cloud.gateway: debug
  • 路由日志追踪RouteLogFilter
package com.thinkingcao.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Collections;
import java.util.Set;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*;

/**
 * @desc:  gateway log路由
 * @author: cao_wencao
 * @date: 2020-06-28 13:42
 */
@Slf4j(topic = "RouteLog")
@Component
public class RouteLogFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Set<URI> uris = exchange.getAttributeOrDefault(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, Collections.emptySet());
        String originalUri = (uris.isEmpty()) ? "Unknown" : uris.iterator().next().toString();
        Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
        URI routeUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
        log.info("【in request 】: " + originalUri + "   ," + "【is routed to id】: " + route.getId()
                + ", uri: " + routeUri);
        return chain.filter(exchange);
    }
}
  • 启动类AppGateway
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @desc:  Api Gateway网关
 * @author: cao_wencao
 * @date: 2020-06-23 13:07
 */
@SpringBootApplication
@EnableEurekaClient
public class AppGateway {
    public static void main(String[] args) {
        SpringApplication.run(AppGateway.class, args);
    }
}

3. Product商品服务

  • pom.xml
 <groupId>org.example</groupId>
    <artifactId>springcloud-product-service</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>springcloud-product-service</name>
    <description>商品服务</description>

    <!--SpringBoot依赖版本-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/>
    </parent>

    <!--项目编码、jdk版本、SpringCloud版本定义-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <!--声明管理SpringCloud版本依赖信息-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!--依赖信息-->
    <dependencies>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--SpringCloud整合-eureka-client组件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!-- 监控 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--Lombok简化代码插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>

    <!--maven插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  • application.yml
server:
  port: 8016
#定义服务名称
spring:
  application:
    name: product-service

#在此指定服务注册中心地址,将当前服务注册到eureka注册中心上
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8888/eureka
    #启动注册操作,该值默认为true。若设置为fasle将不会启动注册操作。是否需要去检索寻找服务,默认是true
    register-with-eureka: true
    #是否需要从eureka上获取注册信息
    fetch-registry: true
  • Product服务接口
package com.thinkingcao.product.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * @desc:   商品服务
 * @author: cao_wencao
 * @date: 2020-06-28 10:34
 */
@RestController
@RequestMapping("/product")
public class ProductController {

    @RequestMapping("/getProductInfo")
    public Map<String,String> getProductInfo(){
        Map<String,String> productsMap = new HashMap<>();
        productsMap.put("productNo","111");
        productsMap.put("productName","荔枝");
        productsMap.put("productDesc","一骑红尘妃子笑,无人知是荔枝来");
        productsMap.put("productPrice","11.0");
        productsMap.put("productWeight","10.0");
        return productsMap;
    }
}
  • 启动类
package com.thinkingcao.product;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @desc:   商品服务
 * @author: cao_wencao
 * @date: 2020-06-23 13:17
 */
@SpringBootApplication
@EnableEurekaClient
public class AppProduct{
    public static void main(String[] args) {
        SpringApplication.run(AppProduct.class,args);
    }
}

七. Gateway十一种路由谓词工厂

通过查阅Gateway官方文档,可发现Gateway提供了11种路由谓词工厂来匹配请求,通过谓词的匹配转发到具体的微服务实例。
在这里插入图片描述

7.1.后路线谓词工厂

路由谓词工厂采用一个参数,即日期时间。该谓词匹配在指定日期时间之后发生的请求。以下示例配置了路由后谓词:

例子1. application.yml

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

这条路线符合2017年1月20日17:42山区时间(丹佛)之后的任何请求。

7.2.之前路线谓词工厂

路由前谓词工厂采用一个参数datetime,即日期时间。该谓词匹配在指定日期时间之前发生的请求。以下示例配置了路由前谓词:

例子2. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: before_route
        uri: https://example.org
        predicates:
        - Before=2017-01-20T17:42:47.789-07:00[America/Denver]

这条路线符合2017年1月20日山区时间(丹佛)之前的任何请求。

7.3. 路由谓词间工厂

路线谓词工厂之间有两个参数,datetime1datetime2。此谓词匹配之后datetime1和之前发生的请求datetime2。该datetime2参数必须是后datetime1。以下示例配置了路由之间的谓词:

例子3. application.yml
spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: https://example.org
        predicates:
        - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

该路线与2017年1月20日山区时间(丹佛)之后和2017年1月21日17:42山区时间(丹佛)之前提出的任何请求相匹配。这对于维护时段可能很有用。

7.4.Cookie路线谓词工厂

cookie路由谓词工厂采用两个参数,即cookie名称和正则表达式。该谓词匹配具有给定名称且其值与正则表达式匹配的cookie。以下示例配置cookie路由谓词工厂:

例子4. application.yml

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

此路由匹配具有名称为chocolatech.p正则表达式匹配的cookie的请求。

7.5.标头路由谓词工厂

标头路由谓词工厂使用两个参数,标头名称和正则表达式。该谓词与具有给定名称的标头匹配,该标头的值与正则表达式匹配。以下示例配置标头路由谓词:

例子5. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: https://example.org
        predicates:
        - Header=X-Request-Id, \d+

如果请求具有名为X-Request-Id的标头,且其值与\ d +正则表达式匹配(即,其值为一个或多个数字),则此路由匹配。

7.6.主机路由谓词工厂

主机路由谓词工厂使用一个参数:主机名模式列表。该模式是带有.分隔符的Ant样式的模式。谓词与Host匹配模式的标头匹配。以下示例配置主机路由谓词:

例子6. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://example.org
        predicates:
        - Host=**.somehost.org,**.anotherhost.org

{sub}.myhost.org还支持URI模板变量(例如)。

如果请求的Host标头值为www.somehost.orgbeta.somehost.org或,则此路由匹配www.anotherhost.org

该谓词提取URI模板变量(例如sub,在前面的示例中定义的)作为名称和值的映射,并ServerWebExchange.getAttributes()使用中定义的键将其放在中ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE。这些值可供工厂使用GatewayFilter

7.7.方法路线谓词工厂

方法路由谓词工厂采用一个或多个参数:要匹配的HTTP方法。以下示例配置方法route谓词:

例子7. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: https://example.org
        predicates:
        - Method=GET,POST

如果请求方法是GETPOST,则此路由匹配。

7.8.路径路线谓词工厂

路径路由谓词工厂具有两个参数:Spring PathMatcher模式列表和一个称为matchOptionalTrailingSeparator的可选标志。。以下示例配置路径路由谓词:

例子8. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://example.org
        predicates:
        - Path=/red/{segment},/blue/{segment}

如果请求路径为例如/red/1/red/blue/blue/green,则他的路线匹配。该谓词提取URI模板变量(例如segment,在前面的示例中定义的)作为名称和值的映射,作为名称和值的映射,并使用ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE中定义的键将其放置在ServerWebExchange.getAttributes()中。这些值随后可供GatewayFilter使用。

可以使用实用程序方法(称为get)来简化对这些变量的访问。下面的示例演示如何使用该get方法:

Map<String, String> uriVariables = ServerWebExchangeUtils.getPathPredicateVariables(exchange);

String segment = uriVariables.get("segment");

7.9.查询路由谓词工厂

查询路由谓词工厂采用两个参数:required paramoptional regexp。以下示例配置查询路由谓词:

例子9. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=green

如果请求包含green查询参数,则前面的路由匹配。

application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=red, gree.

如果请求包含一个前述路线匹配red,其值相匹配的查询参数gree.regexp,所以greengreet将匹配。

7.10.RemoteAddr路由谓词工厂

RemoteAddr路由谓词工厂采用CIDR表示法(IPv4IPv6)字符串的列表(最小大小1),例如192.168.0.1/16(其中192.168.0.1IP地址和16子网掩码)。下面的示例配置一个RemoteAddr路由谓词:

例子10. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: https://example.org
        predicates:
        - RemoteAddr=192.168.1.1/24

如果请求的远程地址是例如192.168.1.10,则此路由匹配。

7.11.重量路线谓词工厂

权重路由谓词工厂采用两个参数:groupweight。权重是按组计算的。以下示例配置权重路由谓词:

例子11. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2

这条路线会将约80%的流量转发至weighthigh.org,并将约20%的流量转发至weightlow.org

八. Gateway配置支持跨域和负载均衡

【参考】: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#cors-configuration

在微服务架构中,一般都是后端使用SpringCloud技术栈、前端使用Vue等技术,整个项目在架构上以前后端分离的形式开发和部署,如果部署过程中前端和后端不在同一台服务器上或者端口不同、域名不同、或者协议不同,由于浏览器的同源策略,此时前端访问后端接口时,就会产生跨域问题。跨域问题属于浏览器策略问题,经常前端和后端工程师为此问题争论不休,其实应当由后端来解决服务请求跨域问题,在GateWay网关中支持跨域解决,在微服务中将跨域放在微服务统一入口解决比较好,因此建议放在网关层解决。

1. Gateway中解决跨域在yml中配置如下

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowCredentials: true   #允许访问携带cookie的
            allowedOrigins: "*"
            allowedMethods: "*"
            allowedHeaders: "*"
            maxAge: 3628800

2. Gateway支持负载均衡

lb://开头的请求, 会被全局过滤器RetryLoadBalancerClientFilter拦截并进行负载均衡处理, 所有的动态路由都会自动负载均衡。

九. 谓词工厂实践

9.1. Path谓词匹配

#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Path=/product/**       # 匹配对应 URL 的请求,将匹配到的请求追加在目标 URI 之后

请求URL网关: http://127.0.0.1:8015/product/getProductInfo ,将会路由至Product商品服务接口: http://127.0.0.1:8016/product/getProductInfo

在这里插入图片描述

IDEA控制台打印日志如下:

2020-06-30 10:44:04.994 DEBUG 21632 --- [ctor-http-nio-5] o.s.c.g.h.RoutePredicateHandlerMapping   : Mapping [Exchange: GET http://127.0.0.1:8015/product/getProductInfo] to Route{id='product-service', uri=lb://product-service, order=0, predicate=org.springframework.cloud.gateway.support.ServerWebExchangeUtils$$Lambda$484/760135579@50a80b3a, gatewayFilters=[]}
2020-06-30 10:44:04.995 DEBUG 21632 --- [ctor-http-nio-5] o.s.c.g.handler.FilteringWebHandler      : Sorted gatewayFilterFactories: [OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@633a2e99}, order=-2147482648}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@37ed010a}, order=-1}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@5a82ebf8}, order=0}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@367d2816}, order=10000}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerClientFilter@7d5508e0}, order=10100}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@68fe48d7}, order=2147483646}, GatewayFilterAdapter{delegate=com.thinkingcao.gateway.filter.RouteLogFilter@7c551ad4}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@554cd74a}, order=2147483647}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@5b84f14}, order=2147483647}]
2020-06-30 10:44:05.008  INFO 21632 --- [ctor-http-nio-5] RouteLog                                 : 【in request 】: lb://product-service:8015/product/getProductInfo   ,【is routed to id】: product-service, uri: http://DESKTOP-JBFTEEM:8016/product/getProductInfo
2020-06-30 10:44:06.231  WARN 21632 --- [ctor-http-nio-5] .a.w.r.e.DefaultErrorWebExceptionHandler : Failed to handle request [GET http://127.0.0.1:8015/favicon.ico]: Response status 404

9.2. After谓词匹配

#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - After=2020-07-01T00:00:00+08:00[Asia/Shanghai]       # 请求时间在 2020年7月1日0点0分0秒之后的所有请求都转发到商品服务实例product-service 上

解释: 请求时间在 2020年7月1日0点0分0秒之后的所有请求都转发到商品服务实例product-service 上,+08:00是指时间和UTC时间相差八个小时,时间地区为Asia/Shanghai。。

9.3. Before谓词匹配

#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Before=2020-07-01T00:00:00+08:00[Asia/Shanghai]       # 请求时间在 2020年7月1日0点0分0秒之前的所有请求都转发到商品服务实例product-service 上

解释: 请求时间在 2020年7月1日0点0分0秒之前的所有请求都转发到商品服务实例product-service 上,+08:00是指时间和UTC时间相差八个小时,时间地区为Asia/Shanghai。。

9.4. Between谓词匹配

#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Between=2020-07-01T00:00:00+08:00[Asia/Shanghai] , 2020-08-01T00:00:00+08:00[Asia/Shanghai]      # 请求时间在 2020年7月1日0点0分0秒到 2020年8月1日0点0分0秒之间的所有请求都转发到商品服务实例product-service 上。

解释: 请求时间在 2020年7月1日0点0分0秒到 2020年8月1日0点0分0秒之间的所有请求都转发到商品服务实例product-service 上,+08:00是指时间和UTC时间相差八个小时,时间地区为Asia/Shanghai。。

9.5. Cookie谓词匹配

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

#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Cookie=sessionId, 111111      # Cookie name = sessionId  ,Cookie value= 111111

解释: 上面Cookie谓词匹配中指定了CookienamesessionIdCookievalue111111,所以只有当请求的请求头header中携带 sessionId =111111 时才进行路由。
在这里插入图片描述

9.6. Header谓词匹配

Header Route Predicate和Cookie一样,也是接收两个参数,一个 header 中属性名称,另外一个header值,该值可以是一个正则表达式,当此断言匹配了请求的header名和值时,断言通过,进入到router的规则中去。

#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Header=X-Request-Id, \d+      # X-Request-Id=[数字]

解释: 上面路由的含义是header中要存在X-Request-Id=[数字]的信息,所以只有当请求的请求头header中携带X-Request-Id=[数字]时才进行路由。
在这里插入图片描述

注意: 如果将参数"X-Request-Id:111111"改为"X-Request-Id:aaaa"等任意一个非数字字符串时,再次执行时,返回404证明没有匹配。

9.7. Host谓词匹配

Host Route Predicate 路由断言工厂接受一组参数,一组匹配的域名列表hostname,如果与多个host匹配则用逗号分隔即可。它可以使用. *等去匹配host。这个参数会匹配请求头中的host的值,一致会被路由。

#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Host=**.baidu.com      # 使用 . *  去匹配多个host

解释: 如果请求头host为Host: www.baidu.comHost: md.baidu.com,都可以匹配到 host_route路由,根据 . *去匹配host后缀。
在这里插入图片描述

9.8. Method谓词匹配

Method谓词表示可以接收一个参数,就是请求方式 POSTGETPUTDELETE 等不同的请求方式来进行匹配路由。

#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Method=GET             # 所有以GET方式的请求都会被路由到product-service服务。

解释: 上面- Method=GET表示所有只要以GET方式的请求,就都会被路由到product-service服务。

9.9. Query谓词匹配

Query Route Predicate谓词支持接收两个参数,一个是属性名[必填],一个是属性值[选填],属性值可以是正则表达式。

  • 只填参数名称
#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Query=name

解释: 上面- Query=name中,只填了必填项属性名称,不填选填项参数值后,只要请求中包含name属性的参数即可匹配路由,不带 name 参数则不会匹配。。

  • 填参数名称和参数值
#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Query=name, Thinkingcao #匹配请求中包含参数的属性名称是`name`,并且参数值是以 `Thinkingcao`开头的字符串

解释: 上面- Query=name, Thinkingcao中,只要当请求中包含参数的属性名称是name,并且参数值是以 Thinkingcao开头字符串才会进行匹配和路由。

9.10. RemoteAddr谓词匹配

RemoteAddr Route Predicate路由断言工厂支持通过设置某个ip区间号段的请求才会路由, 接受 cidr 符号(IPv4 或 IPv6 )字符串的列表(最小大小为1),例如 192.168.10.11/18 (其中 192.168.10.11 是 IP 地址,18 是子网掩码)

#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - RemoteAddr=192.168.10.1/24 #远程请求IP地址满足192.168.10网段即可匹配

解释: - RemoteAddr=192.168.10.1/24 表示只要远程请求地址的IP在网段192.168.10间,则匹配路由,例如,我的服务器主机请求的IP地址是: 192.168.10.3

9.11. Weight谓词匹配

Weight Route Predicate权重路由谓词工厂支持接收两个参数: group和weight, 权重是按组计算的

#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        #商品服务1权重为8
        - id: product-service1
          uri: lb://product-service1
          predicates:
            - Path=/product/** 
            - Weight=product-service1, 8
        #商品服务2权重为2
        - id: product-service2
          uri: lb://product-service2
          predicates:
            - Path=/product/** 
            - Weight=order-service2, 2

解释: 当请求商品服务http://127.0.0.1:8015/product/getProductInfo 时,gateway网关路由匹配,根据权重分配比例,会将80%的流量转发至product-service1,并将约20%的流量转发至product-service2

9.12. 所有谓词组合使用

  1. 当多个谓词组合一起使用的时候,必须所有谓词条件都满足,才可匹配路由;
  2. 当一个请求满足多个路由的谓词匹配条件时,该请求只会被放在第一一个所的路由进行转发。
#定义服务名称
spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Host=**.thinkingcao.top
            - Path=/headers
            - Method=GET
            - Header=X-Request-Id, \d+
            - Query=name, Thinkingcao.
            - Query=name
            - Cookie=sessionId, 111111
            - After=2020-07-01T00:00:00+08:00[Asia/Shanghai]

解释: 当上述多个谓词组合一起使用的时候,必须所有谓词条件都满足,才可匹配路由,默认当满足多个谓词时,默认被第一个所匹配的进行转发。

十. 源码

源码:https://github.com/Thinkingcao/SpringCloudLearning/tree/master/springcloud-gateway

Thinkingcao CSDN认证博客专家 Java Spring Boot 架构
CSDN2019年度博客之星、博客专家,专注架构、Java、SpringBoot、SpringCloud、Spring、微服务、分布式等领域
©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值