JavaEE 企业级分布式高级架构师(二十)微服务框架 SpringCloudAlibaba (2.2 版)(3)

Spring Cloud Gateway微服务网关

概述

网关简介

  • 网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行鉴权、限流、路由、监控等功能。

Gateway简介

在这里插入图片描述

  • 这个项目提供了一个建立在 Spring 生态系统之上的 API 网关,包括:Spring 5、Spring Boot 2 和 project Reactor。Spring Cloud Gateway 旨在提供一种简单而有效的方法来路由到 api,并为它们提供跨领域的关注点,例如:安全性、监控/度量和弹性。

Gateway与Zuul的对比

Zuul
  • Zuul 是由 Netflix 开源的 API 网关,基于 servlet 的,使用阻塞 API,它不支持任何长连接。
Gateway
  • Spring Cloud Gateway 是 Spring Cloud 自己开发的开源 API 网关,建立在 Spring5,Reactor 和 Spring Boot 2 之上,使用非阻塞 API,支持异步开发。
Zuul 闭源
  • Spring Cloud Gateway 是 Zuul 网关的替代者。只所以弃用 Zuul 并不是因为 Zuul 在功能有什么大的问题,而是因为最开始的 Zuul 是开源的,所以 Spring Cloud 就集成了 Zuul 做网关。但后来 Zuul 又宣布闭源,所以 Spring Cloud 自己开发了 Spring Cloud Gateway。再后来 Zuul 2.0 又开源了,但 Spring Cloud 不再集成它了。

工作原理

重要概念

  • 在 Spring Cloud Gateway 中有三个非常重要的概念:
route 路由
  • 路由是网关的最基本组成,由一个路由 id、一个目标地址 url、一组断言工厂及一组 filter 组成。若断言为 true,则请求将经由 filter 被路由到目标 url。
predicate 断言
  • 断言即一个条件判断,根据当前的 http 请求进行指定规则的匹配,比如说 http 请求头,请求时间等。只有当匹配上规则时,断言才为 true,此时请求才会被直接路由到目标地址(目标服务器),或先路由到某过滤器链,经过过程器链的层层处理后,再路由到相应的目标地址(目标服务器)。
filter 过滤器
  • 对请求进行处理的逻辑部分。当请求的断言为 true 时,会被路由到设置好的过滤器,以对请求进行处理。例如,可以为请求添加一个请求头,或添加一个请求参数,或对请求 URI 进行修改等。总之,就是对请求进行处理。

工作原理解析

  • 下图从总体上概述了Spring Cloud Gateway的工作方式:

在这里插入图片描述

  • 客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。该处理程序通过特定于请求的过滤器链运行请求。筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。所有“前置”过滤器逻辑均被执行。然后发出代理请求。发出代理请求后,将运行“后”过滤器逻辑。

示例演示

示例一

配置式路由 05-gateway-config-9000
  • 创建工程:创建一个 Spring Initializr 工程,并命名为 05-gateway-config-9000。
  • 定义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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <groupId>com.yw.sca.example</groupId>
  <artifactId>05-gateway-config-9000</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <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>Hoxton.SR8</spring-cloud.version>
    <spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <!-- spring-cloud -->
      <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>
    <!-- 注意:依赖中一定不要包含spring-cloud-starter-web依赖 -->
    <!-- gateway依赖 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- actuator监控检测 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>
  • 代码中,只需要一个启动类即可:
@SpringBootApplication
public class Gateway059000 {
    public static void main(String[] args) {
        SpringApplication.run(Gateway059000.class, args);
    }
}
  • 定义 application.yml 配置文件:
server:
  port: 9000
spring:
  cloud:
    gateway:
      routes:
      - id: weight_baidu_route
        uri: http://www.baidu.com
        predicates:
        - Weight=search, 8

      - id: weight_360_route
        uri: http://www.so.com
        predicates:
        - Weight=search, 2
  • 启动工程:

在这里插入图片描述

  • 测试:不断刷新访问 http://localhost:9000/,会跳转到百度或360,比例 4: 1。
API 路由 05-gateway-api-9001
  • 创建工程:复制工程 05-gateway-config-9000,并重命名为 05-gateway-api-9001。
  • 修改 application.yml 配置文件:去掉原来配置的路由策略,仅剩如下内容。
server:
  port: 9001
  • 修改启动类:在启动类中添加一个@Bean 方法,用于设置路由策略。
@SpringBootApplication
public class Gateway059001 {
    public static void main(String[] args) {
        SpringApplication.run(Gateway059001.class, args);
    }
    
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(ps -> ps.path("/**").uri("https://www.baidu.com").id("baidu_route"))
                .build();
    }
}
  • 启动工程,访问地址 http://localhost:9001/ 即可跳转至百度。

示例二

  • 根据微服务名称进行 Ribbon 负载均衡:有一个微服务,有三个提供者,这里要通过 spring cloud gateway 实现对该微服务的负载均衡访问。
配置式路由 05-gateway-ribbon-config-9000
  • 创建工程:复制工程 05-gateway-config-9000,并重命名为 05-gateway-ribbon-config-9000。
  • 添加依赖:
<dependencyManagement>
  <dependencies>
    <!-- spring-cloud-alibaba -->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-alibaba-dependencies</artifactId>
      <version>${spring-cloud-alibaba.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <!-- nacos discovery依赖 -->
  <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  </dependency>
</dependencies>
  • 修改 application.yml 配置文件:
server:
  port: 9000
spring:
  application:
    name: msc-gateway-depart
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.254.128:8848
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
      - id: ribbon_route
        predicates:
        - Path=/provider/depart/**
        uri: lb://msc-provider-depart
  • 利用前面的 04-provider-nacos-8081 工程,启动三个 provider 工程,端口分别是 8081、8082、8083。
  • 再启动 05-gateway-ribbon-config-9000 工程,测试访问 http://localhost:9000/provider/depart/get/1,测试下负载均衡的效果。
API 路由 05-gateway-ribbon-api-9001
  • 创建工程:复制工程 05-gateway-ribbon-config-9000,并重命名为 05-gateway-ribbon-api-9001。
  • 修改 application.yml 配置文件:
server:
  port: 9001
spring:
  application:
    name: msc-gateway-depart
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.254.128:8848
  • 修改启动类:
@SpringBootApplication
public class Gateway059001 {
    public static void main(String[] args) {
        SpringApplication.run(Gateway059001.class, args);
    }

    @Bean
    public RouteLocator someRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(ps -> ps.path("/provider/depart/**")
                        .uri("lb://msc-provider-depart")
                        .id("ribbon_route"))
                .build();
    }
}
  • 同样启动 05-gateway-ribbon-api-9001 工程,测试访问 http://localhost:9001/provider/depart/get/1,测试下负载均衡的效果。

路由断言工厂

简介

  • Spring Cloud Gateway 将路由匹配作为最基本的功能。而这个功能是通过路由断言工厂完成的。Spring Cloud Gateway 中包含了许多内置的路由断言工厂。所有这些断言都可以匹配 HTTP 请求的不同属性,并且可以根据逻辑与状态,将多个路由断言工厂复合使用。
  • 路由断言工厂的使用方式有两种:配置方式与 API 方式

After 路由断言工厂

  • 规则:该断言工厂的参数是一个 UTC 格式的时间。其会将请求访问到 Gateway 的时间与该参数时间相比,若请求时间在参数时间之后,则匹配成功,断言为 true。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://www.baidu.com
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[Asia/Shanghai]
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    ZonedDateTime dateTime = LocalDateTime.now().minusDays(5)
            .atZone(ZoneId.systemDefault());

    return builder.routes()
            .route(ps -> ps.after(dateTime)
                    .uri("https://www.baidu.com")
                    .id("after_route"))
            .build();
}

Before 路由断言工厂

  • 规则:该断言工厂的参数是一个 UTC 格式的时间。其会将请求访问到 Gateway 的时间与该参数时间相比,若请求时间在参数时间之前,则匹配成功,断言为 true。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: before_route
        uri: https://www.baidu.com
        predicates:
        - Before=2017-01-20T17:42:47.789-07:00[Asia/Shanghai]
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    ZonedDateTime dateTime = LocalDateTime.now().minusDays(5)
            .atZone(ZoneId.systemDefault());

    return builder.routes()
            .route(ps -> ps.before(dateTime)
                    .uri("https://www.baidu.com")
                    .id("before_route"))
            .build();
}

Between 路由断言工厂

  • 规则:该断言工厂的参数是两个 UTC 格式的时间。其会将请求访问到 Gateway 的时间与这两个参数时间相比,若请求时间在这两个参数时间之间,则匹配成功,断言为 true。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: https://www.baidu.com
        predicates:
        - Between=2017-01-20T17:42:47.789-07:00[Asia/Shanghai],2021-02-20T17:42:47.789-07:00[Asia/Shanghai]
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    ZonedDateTime minusTime = LocalDateTime.now().minusDays(5)
            .atZone(ZoneId.systemDefault());
    ZonedDateTime plusTime = LocalDateTime.now().plusDays(3)
            .atZone(ZoneId.systemDefault());

    return builder.routes()
            .route("between_route", ps -> ps.between(minusTime, plusTime)
                    .uri("https://www.baidu.com"))
            .build();
}

Cookie 路由断言工厂

  • 规则:该断言工厂中包含两个参数,分别是 cookie 的 key 与 value。当请求中携带了指定 key 与 value 的 cookie 时,匹配成功,断言为 true。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://www.baidu.com
        predicates:
        - Cookie=chocolate, dove
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("cookie_route", ps -> ps.cookie("chocolate", "regex")
                    .uri("https://www.baidu.com"))
            .build();
}

Header 路由断言工厂

  • 规则:该断言工厂中包含两个参数,分别是请求头 header 的 key 与 value。当请求中携带了指定 key 与 value 的 header 时,匹配成功,断言为 true。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: https://www.baidu.com
        predicates:
        - Header=X-Request-Id, \d+
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("header_route", ps -> ps.header("X-Request-Id", "\\d+")
                    .uri("https://www.baidu.com"))
            .build();
}

Host 路由断言工厂

  • 规则:该断言工厂中包含的参数是请求头中的 Host 属性。当请求中携带了指定的 Host 属性值时,匹配成功,断言为 true。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://www.baidu.com
        predicates:
        - Host=mylocalhost:9000, myhost:9000
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("host_route", ps -> ps.host("myhost:9000")
                    .uri("https://www.baidu.com"))
            .build();
}

Method 路由断言工厂

  • 规则:该断言工厂用于判断请求是否使用了指定的请求方法,是 POST,还是 GET 等。当请求中使用了指定的请求方法时,匹配成功,断言为 true。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: https://www.baidu.com
        predicates:
        - Method=GET, POST
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("method_route", ps -> ps.method("POST")
                    .uri("https://www.baidu.com"))
            .build();
}

Path 路由断言工厂

  • 规则:该断言工厂用于判断请求路径中是否包含指定的 uri。若包含,则匹配成功,断言为 true,此时会将该匹配上的 uri 拼接到要转向的目标 uri 的后面,形成一个统一的 uri。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: provider_route
        uri: http://localhost:8081
        predicates:
        - Path=/provider/**
      - id: consumer_route
        uri: http://localhost:8080
        predicates:
        - Path=/consumer/**
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("provider_route", ps -> ps.path("/provider/**").uri("http://localhost:8081"))
            .route("consumer_route", ps -> ps.path("/consumer/**").uri("http://localhost:8080"))
            .build();
 }

Query 路由断言工厂

  • 规则:该断言工厂用于从请求中查找指定的请求参数。其可以只查看参数名称,也可以同时查看参数名与参数值。当请求中包含了指定的参数名,或名值对时,匹配成功,断言为 true。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://www.baidu.com 
        predicates:
		- Query=color, gr.+
		- Query=size, 5
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("query_route", ps -> ps.query("color", "gr.+")
                    .and()
                    .query("size", "5")
                    .uri("http://localhost:8081"))
            .build();
}

RemoteAddr 路由断言工厂

  • 规则:该断言工厂用于判断请求提交的所要访问的 IP 地址是否在断言中指定的 IP 范围。当请求中的 IP 在指定范围时,匹配成功,断言为 true。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: remoteAddr_route
        uri: https://www.baidu.com 
        predicates:
		- RemoteAddr=192.168.0.100/24
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("remoteAddr_route", ps -> ps.remoteAddr("192.168.3.1/24")
                    .uri("https://www.baidu.com "))
            .build();
}

Weight 路由断言工厂

  • 规则:该断言工厂中包含两个参数,分别是用于表示组 group,与权重 weight。对于同一组中的多个 uri 地址,路由器会根据设置的权重,按比例将请求转发给相应的 uri。实现负载均衡。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: weight_baidu_route
        uri: https://www.baidu.com
        predicates:
        - Weight=search, 8

      - id: weight_360_route
        uri: https://www.so.com
        predicates:
        - Weight=search, 2
  • API方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("weight_baidu_route", ps -> ps.weight("search", 8)
                    .uri("https://www.baidu.com"))
            .route("weight_360_route", ps -> ps.weight("search", 2)
                    .uri("https://www.so.com"))
            .build();
}

GatewayFilter工厂

  • 过滤器允许以某种方式修改传入的 HTTP 请求或返回的 HTTP 响应。过滤器的作用域是某些特定路由。Spring Cloud Gateway 包括许多内置的 GatewayFilter 工厂。
  • GatewayFilter 工厂的使用方式有两种:配置方式与 API 方式
  • 创建两个工程 05-gateway-filter-config-9000、05-gateway-filter-api-9001 进行测试。

定义三个工程

web工程 05-showinfo-8080
  • 创建一个普通的 Spring Boot 工程,仅导入一个 Spring Web 依赖,即spring-boot-web-starter 依赖。
  • 定义处理器:
@RestController
@RequestMapping("/info")
public class ShowInfoController {
    @RequestMapping("header")
    public String headerHandler(HttpServletRequest request) {
        return "X-Request-Red: " + request.getHeader("X-Request-Red");
    }

    @RequestMapping("/uri")
    public String uriHandler(HttpServletRequest request) {
        return "uri: " + request.getRequestURI();
    }

    @RequestMapping("/param")
    public String paramHandler(String color) {
        return "color: " + color;
    }

    @RequestMapping("/time")
    public String timeHandler() {
        return "time: " + System.currentTimeMillis();
    }
}
创建过滤器工程
  • 配置方式:创建 config 工程 05-gateway-filter-config-9000,直接复制 05-gateway-config-9000 工程,并将配置文件中原来设置的路由策略全部删除,后续对于 filter 的配置式设置,都在该工程中进行。
  • API方式:创建 API 工程 05-gateway-filter-api-9001,直接复制 05-gateway-api-9001 工程,并将启动类中原来设置的路由策略全部删除,后续对于 filter 的 API 式设置,都在该工程中进行。

AddRequestHeader 过滤工厂

  • 规则:该过滤器工厂会对匹配上的请求添加指定的 header。
  • 配置方式:测试地址 http://localhost:9000/info/header
spring:
  cloud:
    gateway:
      routes:
      - id: addRequestHeader_filter
        uri: http://localhost:8080
        predicates:
        - Path=/**
        filters:
        - AddRequestHeader=X-Request-Color, blue
  • API 方式:测试地址 http://localhost:9001/info/header
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("addRequestHeader_filter", ps -> ps.path("/**")
                    .filters(fs -> fs.addRequestHeader("X-Request-Red", "Red"))
                    .uri("http://localhost:8080")
            ).build();
}

AddRequestParameter 过滤工厂

  • 规则:该过滤器工厂会对匹配上的请求添加指定的请求参数。
  • 配置方式:测试地址 http://localhost:9000/info/param
spring:
  cloud:
    gateway:
      routes:
      - id: addRequestParameter_filter
        uri: http://localhost:8080
        predicates:
        - Path=/**
        filters:
        - AddRequestParameter=color, blue
  • API 方式:测试地址 http://localhost:9001/info/param
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("addRequestParameter_filter", ps -> ps.path("/**")
                    .filters(fs -> fs.addRequestParameter("color", "Red"))
                    .uri("http://localhost:8080")
            ).build();
}

AddResponseHeader 过滤工厂

  • 规则:该过滤器工厂会给从网关返回的响应添加上指定的 header。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: addResponseHeader_filter
        uri: http://localhost:8080
        predicates:
        - Path=/**
        filters:
        - AddResponseHeader=X-Response-Color, blue
  • API 方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("addResponseHeader_filter", ps -> ps.path("/**")
                    .filters(fs -> fs.addResponseHeader("X-Response-Color", "Red"))
                    .uri("http://localhost:8080")
            ).build();
}

PrefixPath 过滤工厂

  • 规则:该过滤器工厂会为指定的 URI 自动添加上一个指定的 URI 前辍。
  • 配置方式:
spring:
  cloud:
    gateway:
      routes:
      - id: prefixPath_filter
        uri: http://localhost:8080
        predicates:
        - Path=/**
        filters:
        - PrefixPath=/info
  • API 方式:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("prefixPath_filter", ps -> ps.path("/**")
                    .filters(fs -> fs.prefixPath("/info"))
                    .uri("http://localhost:8080")
            ).build();
}

StripPrefix 过滤工厂

  • 规则:该过滤器工厂会为指定的 URI 去掉指定长度的前辍。
  • 配置方式:测试地址 http://localhost:9000/aa/bb/info/uri
spring:
  cloud:
    gateway:
      routes:
      - id: stripPrefix_filter
        uri: http://localhost:8080
        predicates:
        - Path=/**
        filters:
        - StripPrefix=2
  • API 方式:测试地址 http://localhost:9001/aa/bb/info/uri
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("stripPrefix_filter", ps -> ps.path("/**")
                    .filters(fs -> fs.stripPrefix(2))
                    .uri("http://localhost:8080")
            ).build();
}

RewritePath 过滤工厂

  • 规则:该过滤器工厂会将请求 URI 替换为另一个指定的 URI 进行访问,RewritePath 有两个参数,第一个是正则表达式,第二个是要替换为的目标表达式。
  • 配置方式:测试地址 http://localhost:9000/red/info/uri
spring:
  cloud:
    gateway:
      routes:
      - id: rewritePath_filter
        uri: http://localhost:8080
        predicates:
        - Path=/**
        filters:
        - RewritePath=/red(?<segment>/?.*), $\{segment}
  • API 方式:测试地址 http://localhost:9001/red/info/uri
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("rewritePath_filter", ps -> ps.path("/**")
                    .filters(fs -> fs.rewritePath("/red(?<segment>/?.*)", "${segment}"))
                    .uri("http://localhost:8080")
            ).build();
}

CircuitBreaker 过滤工厂

  • 规则:该过滤器工厂完成网关层的服务熔断与降级。
  • 添加依赖:
<!-- resilience4j依赖 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
  • 需求:我们下面的例子实现的需求是,浏览器访问 05-showinfo-8080 的处理器不成功,从而服务降级到 Gateway 工程中定义的降级处理器。
  • 定义降级处理器:05-gateway-filter-config-9000 与 05-gateway-filter-api-9001 两个工程中均需要添加上该降级处理器。
@RestController
public class GatewayFallbackController {
    @GetMapping("/fallback")
    public String fallbackHandler() {
        return "This is the Gateway Fallback";
    }
}
  • 配置方式:停掉 05-showinfo-8080 工程,测试地址 http://localhost:9000/info/uri
spring:
  cloud:
    gateway:
      routes:
      - id: circuitBreaker_filter
        uri: http://localhost:8080
        predicates:
        - Path=/**
        filters:
        - name: CircuitBreaker
          args: 
            name: myCircuitBreaker
            fallbackUri: forward:/fallback
  • API 方式:停掉 05-showinfo-8080 工程,测试地址 http://localhost:9001/info/uri
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("circuitBreaker_filter", ps -> ps.path("/**")
                    .filters(fs -> fs.circuitBreaker(config -> {
                                config.setName("myCircuitBreaker");
                                config.setFallbackUri("forward:/fallback");
                            }))
                    .uri("http://localhost:8080")
            ).build();
}

RequestRateLimiterGateway 过滤工厂

  • 规则:该过滤器工厂会对进来的请求进行限流。这里采用的是令牌桶算法。另外,从这里也可以看出其是基于 Redis 实现的,所以需要导入 Redis 的依赖。
  • 添加依赖:
<!-- redis-reactive依赖 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
  • 配置方式:修改配置
spring:
  cloud:
    gateway:
      routes:
      - id: requestRateLimiter_filter
        uri: http://localhost:8080
        predicates:
        - Path=/**
        filters:
        - name: RequestRateLimiter
          args:
            key-resolver: "#{@keyResolver}" 
            redis-rate-limiter.replenishRate: 2
            redis-rate-limiter.burstCapacity: 5 
            redis-rate-limiter.requestedTokens: 1
  • 配置方式:启动类注入bean
@Bean
public KeyResolver keyResolver() {
    return exchange -> Mono.just(exchange.getRequest()
            .getRemoteAddress().getHostName());
}

默认 Filters 工厂

  • 前面的 GatewayFilter 工厂是在某一特定路由策略中设置的,仅对这一种路由生效。若要使某些过滤效果应用到所有路由策略中,就可以将该 GatewayFilter 工厂定义在默认 Filters 中。修改 gateway 工程配置文件。
spring:
  cloud:
    gateway:
      default-filters: 
      - name: CircuitBreaker
        args:
          name: myCircuitBreaker
          fallbackUri: forward:/fallback
          
      routes:
      - id: test_filter
        uri: http://localhost:8080
        predicates:
        - Path=/**

优先级

  • 局部 filter 的优先级要高于默认 Filter 的。
  • 相同路由策略,配置式的要高于 API 式的。

自定义Filter

  • 前面的 GatewayFilter 工厂可以创建出某种特定的 Filter 过滤效果,但这些过滤功能可能并不能满足真正的业务需求,此时可以根据具体需求自定义自己的 Filter。

Filter 基础

Filter 分类
  • 过滤器允许以某种方式修改传入的 HTTP 请求或返回的 HTTP 响应。Filter 根据其作用范围的不同,分为两种:Gateway Filter 与 Global Filter。
  • Gateway Filter 应用于单个路由策略上,其功能仅对路由断言为 true 的请求起作用。
  • Global Filter 应用于所有路由策略上,其功能就像前面的配置文件中设置的默认 Filter。
Filter 获取方式
  • 这些 Filter 有三种获取方式:通过内置的 Filter 工厂获取、系统内置的 Filter,及自定义Filter。对于这两种 Filter 的获取与使用,有以下说明。
  • GatewayFilter:对于该类型 Filter 的使用,直接通过系统中内置的 GatewayFilter 工厂的配置即可使用。
  • GlobalFilter:系统中已经定义了一些 Global Filter,而这些 Filter 的使用一般不是显式的使用,在潜移默化中就使用了。例如,在自定义了一些 GlobalFilter 后,为了保证这些Filter 的执行顺序,在每个返回 GlobalFilter 的@Bean 方法上添加@Order,或直接使自定义的 GlobalFilter 类实现 Order 接口,就是对 Combined Global Filter 的使用。再例如,在完成负载均衡时,router 的 uri 属性值以 lb://开头,就是对 LoadBalancerClient Filter 的使用。

在这里插入图片描述

自定义 GatewayFilter-修改请求

  • 这里要实现的需求是,在自定义的 Filter 中为请求添加指定的请求头。
  • 创建工程:复制 05-gateway-api-9001 工程,并重命名为 05-custom-gatewayfilter-9000。在此工程基础上进行修改,首先要修改端口号为 9000。
  • 定义GatewayFilter:
public class AddHeaderGatewayFilter implements GatewayFilter {
    /**
     * Mono:包含0个或1个元素的异步序列
     * Flux:包含0个或多个元素的异步序列
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 向请求中添加指定的header
        ServerHttpRequest httpRequest = exchange.getRequest()
                .mutate()   // 变更
                .header("X-Request-Red", "blue")
                .build();
        // 将变更过的请求写入到exchange
        ServerWebExchange webExchange = exchange.mutate()
                .request(httpRequest)
                .build();
        // 将变更过的exchange向下传递
        return chain.filter(webExchange);
    }
}
  • 注入:
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("custom_filter_route", ps -> ps.path("/**")
                    // 指定使用自定义的Filter
                    .filters(f -> f.filter(new AddHeaderGatewayFilter()))
                    .uri("http://localhost:8080")
            )
            .build();
}
  • 测试地址:http://localhost:9000/info/header

自定义 GatewayFilter-多 filter

  • 下面我们要定义出多个 Filter,每个 Filter 都具有 pre 与 post 两部分。将所有 Filter 注册到路由中,以查看它们执行的顺序。
  • 创建工程:复制 05-custom-gatewayfilter-9000 工程,并重命名为 05-custom-multifilters-9000。在此工程基础上进行修改。
  • 定义三个 GatewayFilter:
@Slf4j
public class OneGatewayFilter implements GatewayFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // pre-filter
        long startTime = System.currentTimeMillis();
        log.info("pre-filter-111 " + startTime);
        exchange.getAttributes().put("startTime", startTime);

        return chain.filter(exchange).then(
                // post-filter,使用一个Runnable任务构建一个Mono
                Mono.fromRunnable(() -> {
                    log.info("post-filter-111");
                    long startTimeAttr = (long) exchange.getAttributes().get("startTime");
                    long elapsedTime = System.currentTimeMillis() - startTimeAttr;
                    log.info("该过滤器执行用时 = " + elapsedTime);
                })
        );
    }
}
@Slf4j
public class TwoGatewayFilter implements GatewayFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // pre-filter
        log.info("pre-filter-222");
        return chain.filter(exchange).then(
                // post-filter
                Mono.fromRunnable(() -> log.info("post-filter-222 " + System.currentTimeMillis()))
        );
    }
}
@Slf4j
public class ThreeGatewayFilter implements GatewayFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // pre-filter
        log.info("pre-filter-333");
        return chain.filter(exchange).then(
                // post-filter
                Mono.fromRunnable(() -> log.info("post-filter-333 " + System.currentTimeMillis()))
        );
    }
}
  • 注入:

在这里插入图片描述

  • 启动工程,进行测试访问 http://localhost:9000/info/time,验证效果

在这里插入图片描述

自定义 GlobalFilter

  • Global Filter 不需要在任何具体的路由规则中进行注册,只需在类上添加@Compoment注解,将其生命周期交给 Spring 容器来管理即可。
  • 需求:这里要实现的需求是,访问当前系统的任意模块的 URL 都需要是合法的 URL。这里所谓合法的 URL 指的是请求中携带了 token 请求参数。由于是对所有请求的 URL 都要进行验证,所以这里就需要定义一个 Global Filter,可以应用到所有路由中。
  • 创建工程:复制 05-custom-gatewayfilter-9000 工程,并重命名为 05-custom-globalfilter-9000。在此工程基础上进行修改。
  • 定义 GlobalFilter:
@Component
public class UrlValidateFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求参数 token
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (StringUtils.isEmpty(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
    @Override
    public int getOrder() {
        // 设置最高优先级
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
  • 修改启动类:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值