Springcloud Gateway 详细介绍


一、简介

API网关是一个服务器,是系统的唯一入口。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、协议转换、限流熔断、静态响应处理。

API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。

微服务网关作为微服务后端服务的统一入口,它可以统筹管理后端服务,主要分为数据平面控制平面:

  • 数据平面主要功能是接入用户的HTTP请求和微服务被拆分后的聚合。使用微服务网关统一对外暴露后端服务的API和契约,路由和过滤功能正是网关的核心能力模块。另外,微服务网关可以实现拦截机制和专注跨横切面的功能,包括协议转换、安全认证、熔断限流、灰度发布、日志管理、流量监控等。
  • 控制平面主要功能是对后端服务做统一的管控和配置管理。例如,可以控制网关的弹性伸缩;可以统一下发配置;可以对网关服务添加标签;可以在微服务网关上通过配置Swagger功能统一将后端服务的API契约暴露给使用方,完成文档服务,提高工作效率和降低沟通成本。

网关主要功能

  • 路由功能:路由是微服务网关的核心能力。通过路由功能微服务网关可以将请求转发到目标微服务。在微服务架构中,网关可以结合注册中心的动态服务发现,实现对后端服务的发现,调用方只需要知道网关对外暴露的服务API就可以透明地访问后端微服务。
  • 负载均衡:API网关结合负载均衡技术,利用Eureka或者Consul等服务发现工具,通过轮询、指定权重、IP地址哈希等机制实现下游服务的负载均衡。
  • 统一鉴权:一般而言,无论对内网还是外网的接口都需要做用户身份认证,而用户认证在一些规模较大的系统中都会采用统一的单点登录(Single Sign On)系统,如果每个微服务都要对接单点登录系统,那么显然比较浪费资源且开发效率低。API网关是统一管理安全性的绝佳场所,可以将认证的部分抽取到网关层,微服务系统无须关注认证的逻辑,只关注自身业务即可。
  • 协议转换:API网关的一大作用在于构建异构系统,API网关作为单一入口,通过协议转换整合后台基于REST、AMQP、Dubbo等不同风格和实现技术的微服务,面向Web Mobile、开放平台等特定客户端提供统一服务。
  • 指标监控:网关可以统计后端服务的请求次数,并且可以实时地更新当前的流量健康状态,可以对URL粒度的服务进行延迟统计,也可以使用Hystrix Dashboard查看后端服务的流量状态及是否有熔断发生。
  • 限流熔断:在某些场景下需要控制客户端的访问次数和访问频率,一些高并发系统有时还会有限流的需求。在网关上可以配置一个阈值,当请求数超过阈值时就直接返回错误而不继续访问后台服务。当出现流量洪峰或者后端服务出现延迟或故障时,网关能够主动进行熔断,保护后端服务,并保持前端用户体验良好。
  • 黑白名单:微服务网关可以使用系统黑名单,过滤HTTP请求特征,拦截异常客户端的请求,例如DDoS攻击等侵蚀带宽或资源迫使服务中断等行为,可以在网关层面进行拦截过滤。比较常见的拦截策略是根据IP地址增加黑名单。在存在鉴权管理的路由服务中可以通过设置白名单跳过鉴权管理而直接访问后端服务资源。
  • 灰度发布:微服务网关可以根据HTTP请求中的特殊标记和后端服务列表元数据标识进行流量控制,实现在用户无感知的情况下完成灰度发布。
  • 流量染色:和灰度发布的原理相似,网关可以根据HTTP请求的Host、Head、Agent等标识对请求进行染色,有了网关的流量染色功能,我们可以对服务后续的调用链路进行跟踪,对服务延迟及服务运行状况进行进一步的链路分析。
  • 文档中心:网关结合Swagger,可以将后端的微服务暴露给网关,网关作为统一的入口给接口的使用方提供查看后端服务的API规范,不需要知道每一个后端微服务的Swagger地址,这样网关起到了对后端API聚合的效果。
  • 日志审计:微服务网关可以作为统一的日志记录和收集器,对服务URL粒度的日志请求信息和响应信息进行拦截。

Springcloud gateway本身也是一个微服务,

官网:https://spring.io/projects/spring-cloud-gateway
文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

https://spring.io/projects/spring-cloud-gateway

Spring Cloud Gateway 是Spring Cloud的一个全新的API网关项目,目的是为了替换掉Zuul1,它基于Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)等技术开发,性能⾼于Zuul,官⽅测试,Spring Cloud GateWay是Zuul的1.6倍,旨在为微服务架构提供⼀种简单有效的统⼀的API路由管理⽅式。可以与Spring Cloud Discovery Client(如Eureka)、Ribbon、Hystrix等组件配合使用,实现路由转发、负载均衡、熔断、鉴权、路径重写、⽇志监控等,并且Gateway还内置了限流过滤器,实现了限流的功能。

1、功能特点:

  • 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
  • 动态路由
  • Predicates 和 Filters 作用于特定路由
  • 集成 Hystrix 断路器
  • 集成 Spring Cloud DiscoveryClient
  • 简单好用的 Predicates 和 Filters
  • 限流
  • 路径重写

springcloud gateway

  • 不管是来自客户端的请求,还是服务内部调用。一切对服务的请求都可经过网关。
  • 网关实现鉴权、动态路由等等操作。
  • Gateway是我们服务的统一入口。
  • 鉴权,安全控制,⽇志统⼀处理,易于监控的相关功能。

2、术语解释

Route(路由):这是网关的基本模块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。

Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。

Filter(过滤器):这是org.springframework.cloud.gateway.fifilter.GatewayFilter的实例,我们可以使用它修改请求和响应。

3、核心流程

springcloud gateway

流程:

  1. Gateway ClientSpring Cloud Gateway 发送请求
  2. 请求首先会被 HttpWebHandlerAdapter 进行提取组装成网关上下文
  3. 然后网关的上下文会传递到 DispatcherHandler ,它负责将请求分发给 RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping 负责路由查找,并根据路由断言判断路由是否可用
  5. 如果过断言成功,由 FilteringWebHandler 创建过滤器链并调用
  6. 通过特定于请求的 Fliter 链运行请求,Filter 被虚线分隔的原因是 Filter 可以在发送代理请求之前(pre)和之后(post)运行逻辑
  7. 执行所有pre过滤器逻辑。然后进行代理请求。发出代理请求后,将运行post过滤器逻辑。
  8. 处理完毕之后将 Response 返回到 Gateway 客户端

Filter过滤器:

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

4、网关技术对比

  • nginx Nginx (engine x) :是⼀个⾼性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。
  • Zuul1.0 :Netflix开源的网关,使用Java开发,基于Servlet架构构建,便于二次开发。因为基于Servlet内部延迟严重,并发场景不友好,一个线程只能处理一次连接请求。
  • Zuul2.0 :采用Netty实现异步非阻塞编程模型,一个CPU一个线程,能够处理所有的请求和响应,请求响应的生命周期通过事件和回调进行处理,减少线程数量,开销较小。
  • spring-cloud-gateway: SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。业务网关,在所有业务服务之前。
  • Kong:Kong是一款基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的,由Mashape公司开源的API Gateway项目。Kong是基于NGINX和Apache Cassandra或PostgreSQL构建的,能提供易于使用的RESTful API来操作和配置API管理系统,所以它可以水平扩展多个Kong服务器,通过前置的负载均衡配置把请求均匀地分发到各个Server,来应对大批量的网络请求。一般用作流量网关,在业务网关之前(springcloud gateway)。

二、快速开始

1、创建项目

a.在线创建:入口:https://start.spring.io
b.idea 创建

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.6.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.weiyimu.opengateway</groupId>
    <artifactId>opengateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>opengateway</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.0</spring-cloud.version>
        <nacos.version>2021.1</nacos.version>
        <sentinel.version>2021.1</sentinel.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--常用工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <!--启动nacos配置管理-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>${nacos.version}</version>
        </dependency>
        <!--nacos 客户端 配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>${nacos.version}</version>
        </dependency>

        <!-- sentinel
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>${sentinel.version}</version>
        </dependency>
         -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2、启动引导类开启注册中心客户端发现

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient // 开启客户端发现服务
public class DemoGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoGatewayApplication.class, args);
    }
}

3、配置文件appliation.yml

以eureka为例,如果使用nacos则修改成nacos配置

spring:
  codec:
    max-in-memory-size: 10MB  ## 出现DataBufferLimitException: Exceeded limit on max bytes to buffer :262144
  application:
    name: apigateway
  profiles:
    active: dev
  #日期格式化
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  cloud:
#    sentinel:
#      transport:
#        dashboard: 127.0.0.1:8080
    gateway:
      metrics:
        enabled: true
      # 配置全局 http 超时
      httpclient:
        connect-timeout: 30000
        response-timeout: 5s
      discovery:
        locator:
          lower-case-service-id: true
    nacos:
      server-addr: localhost:8848
      username: nacos
      password: nacos
      discovery:
        namespace: dev
        watch-delay: 15000 # 拉取配置中心服务的时间间隔
      config:
        namespace: dev
        name: apigateway
        #group: DEFAULT_GROUP 自定义需要修改
        file-extension: YAML

4、编写路由规则

spring:
  cloud:
    gateway:
    # 路由si(集合)
      routes:
      	# id(唯一标识)
        - id: order_service
        # 路由服务地址(转发后地址)
          uri: http://127.0.0.1:9091
          order: 9000
        # 断言(判断哪些请求要被转发)
          predicates:
            - Path=/order/**
  • 将符合 path 规则的请求,路由到 uri 参数指定地址。
  • 举例http://localhost:8080/order/getOrder?id=1 路由转发到http://localhost:9091/order/getOrder?id=1
  • 访问路径中,必须包含路由规则的映射路径/order才会被路由

三、路由配置(转发)(predicates)

谓词(predicates):当满足条件在进行路由转发

在 Spring Cloud Gateway 中谓词实现 GatewayPredicate 接口。其中类名符合: XXXRoutepredicateFactory,其中 XXX 就是在配置文件中谓词名称。在上面示例中 Path=/demo/** 实际上使用的就是PathRoutePredicateFactory

GatewayPredicate 实现类

所有的谓词都设置在 predicates 属性中,当设置多个谓词时取逻辑与条件,且一个谓 词只能设置一组条件,如果需要有个多条件,添加多个相同谓词。

Spring Cloud 内置的几种 Predicate 的实现:
Predicate

可以自定义Predicate 实现`AbstractRoutePredicateFactory

1、Query属性

表示路径满足/system/**同时包含参数 abc。

predicates:
  - Path=/system/**
  - Query=abc

支持正则表达式jqk.

支持指定某个字段值abc,jqk.

abc=jqka 或 abc=jqkw 能满足谓词条件。

predicates:
  - Path=/system/**
  - Query=abc,jqk.

测试:

http://localhost:9000/system/one?abc=jqk

完整版:

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
        filters:
          - StripPrefix= 1

2、Header

表示请求头中必须包含的内容。

  • 参数名和参数值之间依然使用逗号。
  • 参数值要使用正则表达式。
  • 支持选择多个Header头信息。

如果 Header 只有一个值表示请求头中必须包含的参数。如果有两个值,第一个表示请求头必须包含的参数名,第二个表示请求头参数对应值。

predicates:
  - Header=Connection,keep-alive
  - Header=Cache-Control,max-age=0

完整版:

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0

3、Method

Method 表示请求方式。支持多个值,使用逗号分隔,多个值之间为 or 条件。

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST

4、RemoteAddr

允许访问的客户端地址

  • 要使用127.0.0.1,不要使用localhost。
spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1

5、Host

匹配请求中的Host信息。满足Ant模式。

?:匹配一个字符

*:匹配0个或多个字符

**:匹配0个或多个目录

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1
          - Host=127.0.0.1:9000

6、Cookie

要求请求中包含指定的Cookie名和满足特定正则要求的值。

  • 必须要有2个值,第一个包含的是参数名,第2个表示参数对应的值(正则表达式)。
spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1
          - Host=127.0.0.1:9000
          - Cookie=age,.*

7、Before

在指定时间点之前

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1
          - Host=127.0.0.1:9000
          - Cookie=age,.*
          - Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]

8、After

在指定时间点之后

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1
          - Host=127.0.0.1:9000
          - Cookie=age,.*
          - Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
          - After=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]

9、Between

必须在设定的范围时间内,才能进行路由转发

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1
          - Host=127.0.0.1:9000
          - Cookie=age,.*
          - Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
          - After=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
          - Between=2020-01-31T18:00:00.000+08:00[Asia/Shanghai], 2020-02-01T00:00:00.000+08:00[Asia/Shanghai]

10、Weight

设置服务转发的权重,用于限制某个的请求占比

语法:Weight=组名,负载均衡权重

suiyi:权重:20%

suiyi2:权重:80%

spring:
  cloud:
    routes:
      - id: suiyi
        uri: lb://demo-one
        predicates:
          - Path=/demo/**
          - Weight=group,2
        filters:
          - StripPrefix=1
          
      - id: suiyi2
        uri: lb://demo-two
        predicates:
          - Path=/demo/**
          - Weight=group,8
        filters:
          - StripPrefix=1

11、ReadBody

自带ReadBodyPredicateFactory断言,可以将body中的内容读取到exchange对象中,后续过滤器等使用exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY)获取请求内容。

spring:
  cloud:
    routes:
      - id: system #测试
        uri: lb://system
        predicates:
          - Path=/api/**
          - name: ReadBody #读取body断言
            args:
              inClass: "#{T(String)}" #body数据类型
              predicate: "#{@testRequestBody}" #自定义断言处理器

12、Path

必须设置
根据路径匹配路由

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/v1/**,/v2/**

四、过滤器(拦截)(Filter)

1、简介

匹配到某个路由后将请求交给GatewayWebHandler处理,Handler调用一个Filter链来处理这个请求。

执行顺序:

  • 首先,会执行「pre」过滤器的逻辑
  • 然后执行请求后端服务处理
  • 最后再执行「post」过滤器的逻辑

处理流程

Spring Cloud Gateway 的 Filter 的执行顺序有两个:“pre” 和 “post”。“pre”和 “post” 分别会在请求被执行前调用和被执行后调用。

过滤器作为网关的其中一个重要功能,就是实现请求的鉴权。

Gateway自带过滤器有几十个,常见自带过滤器有:

官网地址:

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

Filters

过滤器名称 说明
AddRequestHeader 对匹配上的请求加上Header
AddRequestParameters 对匹配上的请求路由
AddResponseHeader 对从网关返回的响应添加Header
StripPrefix 对匹配上的请求路径去除前缀
PrefixPath 对匹配上的请求路径添加前缀

使用场景:

  • 请求鉴权:如果没有访问权限,直接进行拦截
  • 异常处理:记录异常日志
  • 服务调用时长统计

2、系统内置过滤器使用

**过滤器类型:**Gateway有两种过滤器

  • 局部过滤器:只作用在当前配置的路由上。
  • 全局过滤器:作用在所有路由上。

2.1 添加响应头(AddResponseHeader)

对输出的响应设置其头部属性名称为i-love,值为itheima。

配置文件更新:

# 往响应过滤器中加入信息
   - AddResponseHeader: i-love,itheima

全部配置:

spring:
  cloud:
    gateway:
      # 全局过滤器配置
      default-filters:
     # 往响应过滤器中加入信息
        - AddResponseHeader: i-love,itheima
        
        - AddRequestParameters:
        - AddRequestHeader:
        - StripPrefix:
        - PrefixPath:

结果:

效果

2.2 增加路由配置(PrefixPath)

在gateway中可以通过配置路由的过滤器PrefifixPath实现映射路径中的前缀添加。可以起到隐藏接口地址的作用,避免接口地址暴露。

# 请求地址添加路径前缀过滤器 
filters:
  - PrefixPath=/user

完整配置:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route # 路由id,可以随意写 
          # 代理服务地址;lb表示从Eureka中获取具体服务 
          uri: lb://user-service 
          # 路由断言,配置映射路径 
          predicates:
            - Path=/** 
          # 请求地址添加路径前缀过滤器 
          filters:
            - PrefixPath=/user

结果展示:

配置 访问地址 路由地址
PrefifixPath=/user localhost:10010/findById?id=1 localhost:9091/user/findById?id=1
PrefifixPath=/user/abc localhost:10010/findById?id=1 localhost:9091/user/abc/findById?id=1

2.3 删除路由配置(StripPrefix)

在gateway中通过配置路由过滤器StripPrefifix,实现映射路径中地址的去除。通过StripPrefifix=1来指定路由要去掉的前缀个数。

如:路径/api/user/1将会被路由到/user/1

filters:
  # 去除路径前缀过滤器
  - StripPrefix=1

完整配置文件

spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route # 路由id,可以随意写
          # 代理服务地址;lb表示从Eureka中获取具体服务
          uri: lb://user-service
          # 路由断言,配置映射路径
          predicates:
            - Path=/**
          # 请求地址添加路径前缀过滤器
          filters:
            # 去除路径前缀过滤器
            - StripPrefix=1

访问效果:

配置 访问地址 路由地址
StripPrefix=1 localhost:10010/api/user/findById?id=1 localhost:9091/user/findById? id=1
StripPrefix=2 localhost:10010/aa/api/user/findById? id=1 localhost:9091/user/findById? id=1

2.4 AddRequestHeader

添加请求头参数,参数和值之间使用逗号分隔

filters:
  - StripPrefix= 1
  - AddRequestHeader=MyHeader,jqk

2.5 AddRequestParameter

添加请求表单参数,多个参数需要有多个过滤器。

filters:
  - AddRequestParameter=name,bjsxt
  - AddRequestParameter=age,123

2.6 DedupeResponseHeader

对指定响应头去重复。

语法:DedupeResponseHeader=响应头参数 响应头参数,strategy

可选参数 strategy 可取值:

  • RETAIN_FIRST:默认值,保留第一个

  • RETAIN_LAST:保留最后一个。

    • RETAIN_UNIQUE:保留唯一的,出现重复的属性值,会保留一个。例如有两个 My:bbb 的属性,最后会只留一个。
filters:
  - StripPrefix= 1
  - DedupeResponseHeader=My Content-Type,RETAIN_UNIQUE

2.7 CircuitBreaker

实现熔断时使用,支持 CircuitBreaker 和 Hystrix 两种

2.8 FallbackHeaders

可以添加降级时的异常信息

2.9 RequestRateLimiter

限流过滤器

2.10 RedirectTo

重定向。

有两个参数,status 和 url。其中 status 应该 300 系列重定向状态码

2.11 RemoveRequestHeader

删除请求头参数

2.12 RemoveResponseHeader

删除响应头参数

2.13 RemoveRequestParameter

删除请求参数

2.14 RewritePath

重写请求路径

2.15 RewriteResponseHeader

重写响应头参数

2.16 SaveSession

如果项目中使用Spring Security和Spring Session整合时,会使用到此属性。

2.17 SecureHeaders

具有权限验证时,建议的头信息内容。

2.18 Retry

设置重试次数

功能和StripPrefix类似。

2.19 RequestSize

请求的最大大小。包含maxSize参数,可以有单位'KB''MB'默认为B。

2.20 ModifyRequestBody

修改请求体内容

2.21 ModifyResponseBody

修改响应体内容

2.22 SetPath

当请求路径为/red/blue时,会将/blue发送给下游。

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/red/{segment}
        filters:
          - SetPath=/{segment}

2.23 SetRequestHeader

替换请求参数头。

2.24 SetResponseHeader

替换相应头参数

2.25 SetStatus

设置相应状态码

3、自定义全局过滤器

放行:

//12. 放⾏
return chain.filter(exchange);

拦截:

//7. 响应中放⼊返回的状态吗, 没有权限访问
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//8. 返回
return response.setComplete()

3.1 假如token为null,则拦截

import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Configuration
public class MyGlobalFileter implements GlobalFilter, Ordered {


    /**
     * 自定义过滤器规则
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("-----------------全局过滤器MyGlobalFilter---------------------");
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (StringUtils.isBlank(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    /**
     * 定义过滤器执行顺序
     * 返回值越小,越靠前执行
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

3.2 IP过滤器

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;

@Component
public class IPFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("经过IP过滤器");
        ServerHttpRequest request = exchange.getRequest();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        System.out.println("请求的IP地址为:" + remoteAddress.getHostName());

        return chain.filter(exchange);
    }

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

3.3 请求url过滤器

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class UrlFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("经过url过滤器");
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        System.out.println("请求的url为:" + path);
        return chain.filter(exchange);
    }

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

4、自定义可配置过滤器(添加到配置文件)

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

@Component
public class MyRouteGatewayFilterFactory extends AbstractGatewayFilterFactory<MyRouteGatewayFilterFactory.Config> {
    public MyRouteGatewayFilterFactory() {
        super(MyRouteGatewayFilterFactory.Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println("在这个位置写点东西传递进来的 name: " + config.getName() + ",age:" + config.getAge());
                return chain.filter(exchange);
            }

            @Override
            public String toString() {
                return GatewayToStringStyler.filterToStringCreator(MyRouteGatewayFilterFactory.this).append("name", config.getName()).toString();
            }
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("name");
    }

    public static class Config {
        private String name;
        private int age;

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public Config() {
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

添加到配置文件中(application.yaml)

spring:
  application:
    name: sysgateway
  cloud:
    routes:
    #   自定义配置
      - id: myFilter
        uri: lb:/jqk
        predicates:
          - Path=/project/**
        filters:
          - StripPrefix=1
          - name: MyRoute
            args:
           # 传递的参数(name、age)
              name: hello
              age: 12

五、其它配置

1、网关自动映射处理

案例:

请求地址:http://localhost:9999/ribbon-app-service/getArgs?name=admin&age=20
自动转发到:http://ribbon-app-service/getArgs?name=admin&age=20

配置文件

spring:
  application:
    name: sysgateway
  cloud:
    gateway:
      discovery: # 配置网关发现机制
        locator: #  配置处理机制
          enabled: true # 开启网关自动映射处理机制
          lower-case-service-id: true # 开启微服务名称小写转换。Eureka对服务名管理默认全大写。

2、动态路由

#	路由到执行IP
uri: http://127.0.0.1:9091
#	根据服务名称进行路由(从配置中心获取指定IP)
uri: lb://user-service
  • 路由配置中uri所用的协议为lb时,gateway将把user-service解析为实际的主机和端口,并通过Ribbon进行负载均衡。
  • 应该是根据服务名称,去Eureka注册中心查找服务对应的所有实例列表,然后进行动态路由!

测试日志:

效果

这次gateway进行路由时,会利用Ribbon进行负载均衡访问。日志中可以看到使用了负载均衡器。

效果

3、跨域配置

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # ⽀持的⽅法
              - GET
              - POST
              - PUT
              - DELETE

4、记录执行耗时

//	将开始时间放入请求中
exchange.getAttributes().put("startTime", System.currentTimeMillis());
//	计算执行耗时
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
    Long startTime = exchange.getAttribute("startTime");
    Long executeTime = System.currentTimeMillis() - startTime;
}));

完整版:

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Slf4j
@Component
public class UrlFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("经过url过滤器");
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        System.out.println("请求的url为:" + path);
      	exchange.getAttributes().put("startTime", System.currentTimeMillis());

        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            Long startTime = exchange.getAttribute("startTime");
            if (startTime != null) {
                long executeTime = System.currentTimeMillis() - startTime;
                log.info("请求时间:{}", exchange.getRequest().getURI().getRawPath() + "d" + executeTime + "ms");
            }
        }));
    }

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

5、exchange代码信息

//  获取请求头信息
HttpHeaders headers = exchange.getRequest().getHeaders();
String first = exchange.getRequest().getHeaders().getFirst("Content-type");
ServerHttpRequest build = exchange.getRequest().mutate().header("Content-type","json").build();
//  获取请求方式
String name = exchange.getRequest().getMethod().name();
//  获取请求URL地址信息
String host = exchange.getRequest().getURI().getHost();
String path1 = exchange.getRequest().getURI().getPath();
String rawPath = exchange.getRequest().getURI().getRawPath();
//  获取请求属性信息
exchange.getAttributes().put("startTime",123123);
Long startTime1 = exchange.getAttribute("startTime");
//  获取返回体
ServerHttpResponse response = exchange.getResponse();

六、网关限流

1、简介

限流,当我们的系统 被频繁的请求的时候,就有可能 将系统压垮,所以 为了解决这个问题,需要在每⼀个微服务中做限流操作,但是如果有了⽹关,那么就可以在⽹关系统做限流,因为所有的请求都需要先通过⽹关系统才能路由到微服务中。

限流

2、令牌桶算法简介

令牌桶算法是⽐较常⻅的限流算法之⼀,⼤概描述如下:

  1. 所有的请求在处理之前都需要拿到⼀个可⽤的令牌才会被处理;
  2. 根据限流⼤⼩,设置按照⼀定的速率往桶⾥添加令牌;
  3. 桶设置最⼤的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
  4. 请求达到后⾸先要获取令牌桶中的令牌,拿着令牌才可以进⾏其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
  5. 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证⾜够的限流

令牌桶

3、代码实现

3.1 pom.xml依赖

spring cloud gateway 默认使⽤redisRateLimter限流算法来实现。所以我们要使⽤⾸先需要引⼊redis的依赖。

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

3.2 定义KeyResolver

GatewayApplicatioin引导类中添加如下代码,KeyResolver⽤于计算某⼀个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端发现服务
public class DemoGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoGatewayApplication.class, args);
    }

    //    定义一个KeyResolver
    @Bean
    public KeyResolver ipKeyResolver() {
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
            }
        };
    }
}

3.3 修改配置

修改application.yml中配置项,指定限制流量的配置以及REDIS的配置。

filters:
  - PrefixPath=/test
  - name: RequestRateLimiter
    args:
      key-resolver: "#{@ipKeyResolver}"
      redis-rate-limiter.replenishRate: 1
      redis-rate-limiter.burstCapacity: 1
  • burstCapacity:令牌桶总容量。

  • replenishRate:令牌桶每秒填充平均速率。

  • key-resolver:⽤于限流的键的解析器的 Bean 对象的名字。它使⽤ SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

通过在 replenishRate 和中设置相同的值来实现稳定的速率 burstCapacity 。设置 burstCapacity ⾼于时,可以允许临时突发 replenishRate 。在这种情况下,需要在突发之间允许速率限制器⼀段时间(根据 replenishRate ),因为2次连续突发将导致请求被丢弃( HTTP 429 \- Too Many Requestskey-resolver: "#{@userKeyResolver}" ⽤于通过SPEL表达式来指定使⽤哪⼀个KeyResolver

如上配置:

  • 表示 ⼀秒内,允许 ⼀个请求通过,令牌桶的填充速率也是⼀秒钟添加⼀个令牌。

  • 最⼤突发状况 也只允许 ⼀秒内有⼀次请求,可以根据业务来调整 。

3.4 最终配置:

bootstrap.yml

server:
  port: 6080
# Spring配置
spring:
  application:
    name: gateway
  profiles:
    active: db
  # 模板引擎
  thymeleaf:
    mode: HTML
    encoding: utf-8
    # 禁用缓存
    cache: false
  # 资源信息
  messages:
    # 国际化资源文件路径
    basename: i18n/messages
  #日期格式化
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  codec:
    max-in-memory-size: 20MB
  cloud:
    gateway:
      metrics:
        enabled: true
      # 配置全局 http 超时
      httpclient:
        connect-timeout: 30000
        response-timeout: 5s
    nacos:
      server-addr: localhost:8848
      username: nacos
      password: nacos
      discovery:
        namespace: dev
        watch-delay: 15000 # 拉取配置中心服务的时间间隔
      config:
        namespace: dev
        name: apigateway
        #group: DEFAULT_GROUP 自定义需要修改
        file-extension: YAML
    loadbalancer:
      health-check:
        interval: 15s #检查服务健康状态时间间隔
        #path:
        #  cbb: /api/check
      service-discovery:
        timeout: 9000




management:
  metrics:
    tags:
      application: ${spring.application.name}
  endpoints:
    web:
      exposure:
        include: "*"
        # 开启指定端点
        #include: "health,info,gateway,metrics"
logging:
  level:
    root: info
    org.springframework.boot: info
    org.springframework.cloud.gateway: info
    org.springframework.cloud.loadbalancer: debug
    com.alibaba.nacos.client.naming: info
    com.netflix: info
  file:
    name: ./logs/apigateway.log
  logback:
    rollingpolicy:
      max-history: 7
      max-file-size: 100MB

放入配置中心
application-dev.yaml

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # ⽀持的⽅法
              - GET
              - POST
              - PUT
              - DELETE
    routes:
      - id: goods
        uri: lb://goods
        predicates:
          - Path=/goods/**
        filters:
          - StripPrefix= 1
          - name: RequestRateLimiter #请求数限流 名字不能随便写
            args:
              key-resolver: "#{@ipKeyResolver}"
              redis-rate-limiter.replenishRate: 1
              redis-rate-limiter.burstCapacity: 1
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
        filters:
          - StripPrefix= 1
    # 配置Redis 127.0.0.1可以省略配置
    redis:
      host: 192.168.200.128
      port: 6379

无配置中心



### 配置无注册中心的服务,加入监控检查
spring:
  cloud:
    gateway:
      routes:
        - id: goods #路由名 
          uri: lb://goods # 服务名
          predicates:
            - Path=/goods/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter #请求数限流 名字不能随便写
              args:
                key-resolver: "#{@ipKeyResolver}"
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1
          metadata: # 单个服务请求超时时间
            - connect-timeout=180000
            - response-timeout=180000  
                             
    discovery:
      client:
        simple:
          instances:  ## 服务发现实力列表,无注册中心服务实例的地址,新增或删除服务需要手动维护这个列表
            goods[0]:
              uri: http://192.168.1.10:8080
            goods[1]:
              uri: http://192.168.1.11:8080

    loadbalancer:
      service-discovery:
        timeout: 9000
      configurations: health-check # 启用health-check
      ## 全局
      health-check:
        initial-delay: 3s
      clients:  ## 服务发现无注册中心服务的实例(实例列表)健康检查
        online: # 单个服务指定服务名
          health-check:
            interval: 30s
            initial-delay: 1s
            refetch-instances-interval: 5s
            path:
              goods: /actuator/health

文章作者: weilongshi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 weilongshi !
  目录