第五章 服务网关

作者为冰河忠实粉丝,可以加入【冰河技术】知识星球获取源码,也可以关注【冰河技术】公众号,加入社群学习,学习氛围好,干货满满。
冰河技术.jpg

1 网关概述

当采用分布式、微服务的架构模式开发系统中,服务网关是整个系统中必不可少的一部分。

1.1 没有网关的弊端

当一个系统使用分布式、微服务架构后,系统会被拆分为一个个小的微服务,每个微服务专注一个小的业务。那么,客户端如何调用这么多微服务的接口呢?如果不做任何处理,没有服务网关,就只能在客户端记录下每个微服务的每个接口地址,然后根据实际需要去调用相应的接口。
image.png
这种直接使用客户端记录并管理每个微服务的每个接口的方式,存在着太多的问题,列举几个常见的问题。

  • 由客户端记录并管理所有的接口缺乏安全性。
  • 由客户端直接请求不同的微服务,会增加客户端程序编写的复杂性。
  • 涉及到服务认证与鉴权规则时,需要在每个微服务中实现这些逻辑,增加了代码的冗余性。
  • 客户端调用多个微服务,由于每个微服务可能部署的服务器和域名不同,存在跨域的风险。
  • 当客户端比较多时,每个客户端上都管理和配置所有的接口,维护起来相对比较复杂。

1.2 引入API网关

API网关,其实就是整个系统的统一入口。网关会封装微服务的内部结构,为客户端提供统一的入口服务,同时,一些与具体业务逻辑无关的通用逻辑可以在网关中实现,比如认证、授权、路由转发、限流、监控等。引入API网关后,如下所示。
image.png
引入API网关后,客户端只需要连接API网关,由API网关根据实际情况进行路由转发,将请求转发到具体的微服务,同时,API网关会提供认证、授权、限流和监控等功能。

2 主流的API网关

当系统采用分布式、微服务的架构模式后,API网关就成了整个系统不可分割的一部分。业界通过不断的探索与创新,实现了多种API网关的解决方案。目前,比较主流的API网关有:Nginx+Lua、Kong官网、Zuul网关、Apache Shenyu网关、SpringCloud Gateway网关。

2.1 Nginx+Lua

Nginx的一些插件本身就实现了限流、缓存、黑白名单和灰度发布,再加上Nginx的反向代理和负载均衡,能够实现对服务接口的负载均衡和高可用。而Lua语言可以实现一些简单的业务逻辑,Nginx又支持Lua语言。所以,可以基于Nginx+Lua脚本实现网关。

2.2 Kong网关

Kong网关基于Nginx与Lua脚本开发,性能高,比较稳定,提供多个限流、鉴权等插件,这些插件支持热插拔,开箱即用。Kong网关提供了管理页面,但是,目前基于Kong网关二次开发比较困难。

2.3 Zuul网关

Zuul网关是Netflix开源的网关,功能比较丰富,主要基于Java语言开发,便于在Zuul网关的基础上进行二次开发。但是Zuul网关无法实现动态配置规则,依赖的组件相对来说也比较多,在性能上不如Nginx。

2.4 Apache Shenyu网关

Dromara社区开发的网关框架,其是一个异步的,高性能的,跨语言的,响应式的API网关,并在此基础上提供了非常丰富的扩展功能:

  • 支持各种语言(http协议),支持Dubbo, Spring-Cloud, Grpc, Motan, Sofa, Tars等协议。
  • 插件化设计思想,插件热插拔,易扩展。
  • 灵活的流量筛选,能满足各种流量控制。
  • 内置丰富的插件支持,鉴权,限流,熔断,防火墙等等。
  • 流量配置动态化,性能极高。
  • 支持集群部署,支持 A/B Test,蓝绿发布。

2.6 SpringCloud Gateway网关

Spring为了替换Zuul而开发的网关,SpringCloud Alibaba技术栈中,并没有单独实现网关的组件。在后续的案例实现中,我们会使用SpringCloud Gateway实现网关功能。

3 SpringCloud Gateway网关

Spring Cloud Gateway是Spring公司基于Spring 5.0, Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流、重试等。

3.1 SpringCloud Gateway概述

Spring Cloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0 + Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。Spring Cloud Geteway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关性能,Spring Cloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
总结一句话:Spring Cloud Gateway使用的Webflux中的reactor-netty响应式编程组件,底层使用Netty通讯框架。

3.2 SpringCloud Gateway核心架构

客户端请求到 Gateway 网关,会先经过 Gateway Handler Mapping 进行请求和路由匹配。匹配成功后再发送到 Gateway Web Handler 处理,然后会经过特定的过滤器链,经过所有前置过滤后,会发送代理请求。请求结果返回后,最后会执行所有的后置过滤器。
image.png
SpringCloud Gateway的主要流程为:客户端请求会先打到Gateway,具体的讲应该是DispacherHandler(因为Gateway引入了WebFlux,作用可以类比MVC的DispacherServlet),Gateway根据用户的请求找到相应的HandlerMapping,请求和具体的handler之间有一个映射关系,网关会对请求进行路由,handler会匹配到RoutePredicateHandlerMapping,匹配请求对应的Route,然后到达Web处理器,WebHandler代理了一系列网关过滤器和全局过滤器的实例,这些过滤器可以对请求和响应进行修改,最后由代理服务完成用户请求,并将结果返回。

注:SpringCloud Gateway部分参考链接:
https://www.csdn.net/tags/NtTagg0sMTk1OTItYmxvZwO0O0OO0O0O.html
https://baijiahao.baidu.com/s?id=1685753662803832483

3.3 项目整合SpringCloud Gateway网关

在项目中整合SpringCloud Gateway来为项目增加API网关,同时,会将SpringCloud Gateway与Sentinel进行整合实现网关的限流能力。

3.3.1 项目整合网关

在项目中增加一个服务网关模块shop-gateway,在服务网关模块中实现网关的能力。此时,我们的项目中就会有用户微服务、商品微服务、订单微服务、服务网关,除此之外,作者将nacos集成到项目中,还有一个Nacos服务(通过jar启动的不影响)。
image.png

3.3.2 新建网关模块

在项目中,新建shop-gateway模块,新增网关模块后项目结构图如下所示。
image.png

3.3.3 初步整合SpringCloud Gateway

  1. 在服务网关shop-gateway模块的pom.xml文件中添加如下依赖。
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
</dependencies>
  1. 在服务网关shop-gateway模块的resources目录下新建application.yml文件,并在文件中添加如下配置信息。
server:
 port: 10001
spring:
 application:
  name: server-gateway
 cloud:
  gateway:
   # 解决SpringCloud Gateway跨域的问题
   globalcors:
    cors-configurations:
     '[/**]':
      allowedOrigins: "*"
      allowedMethods: "*"
      allowCredentials: true
      allowedHeaders: "*"
   # 表示一个路由数组,可以在此节点下配置多个路由信息。
   routes:
    - id: user-gateway # 当前路由的唯一标识。
      uri: http://localhost:8060
      order: 1 # 路由的优先级,数字越小表示优先级越高。
      predicates: # 网关断言,也就是路由转发的条件,也是一个数组,可以配置多个路由转发条件。
       - Path=/server-user/** # 当客户端请求的路径满足Path的规则时,进行路由转发操作。
      filters: # 网关过滤器,在过滤器中可以修改请求的参数和header信息,以及响应的结果和header信息,网关过滤器也是一个数组,可以配置多个过滤规则。
       - StripPrefix=1 # 网关在进行路由转发之前,会去掉1层访问路径。

    - id: product-gateway
      uri: http://localhost:8070
      order: 1
      predicates:
       - Path=/server-product/**
      filters:
       - StripPrefix=1

    - id: order-gateway
      uri: http://localhost:8080
      order: 1
      predicates:
       - Path=/server-order/**
      filters:
       - StripPrefix=1

此处注意,如果springboot版本为2.4.0以上,需要将cors-configurations下allowedOrigins替换为allowedOriginPatterns,否则会报错IllegalArgumentException,提示跨域配置问题。

  1. 在网关服务shop-gateway模块下的 cn.mawenda.shop 包下新建ShopGatewayApplication类,表示服务网关的启动类,源码如下所示。
/**
 * 服务网关启动类
 * @Author: Ma.wenda
 * @Date: 2024-01-31 17:19
 * @Version: 1.0
 **/
@SpringBootApplication
public class ShopGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShopGatewayApplication.class, args);
    }
}
  1. 由于之前项目中整合了Nacos和Sentinel,所以,在启动项目前,要分别启动Nacos和Sentinel。
  • Nacos jar方式启动,进入Nacos的bin目录下,输入如下命令启动Nacos。
startup.cmd -m standalone
  • 进入Sentinel jar包所在目录,输入如下命令启动 Sentinel。
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.4.jar

通过自己的方式将Nacos 和 Sentinel启动成功即可。

  1. 分别启动用户微服务、商品微服务、订单微服务和服务网关。
  2. 通过服务网关访问用户微服务,在浏览器中输入http://localhost:10001/server-user/user/get/1001,如下所示。

image.png
可以看到,通过服务网关能够正确访问到用户微服务。

  1. 通过服务网关访问产品微服务,在浏览器中输入http://localhost:10001/server-product/product/get/1001,如下所示。

image.png

  1. 通过服务网关访问订单微服务,在浏览器中输入http://localhost:10001/server-order/order/test_sentinel,如下所示。

image.png
访问各微服务正常。

3.3.4 网关整合Nacos

初步整合SpringCloud Gateway,我们在服务网关模块的application.yml文件中硬编码配置了服务转发的地址,如下所示。

# 用户微服务地址
uri: http://localhost:8060
......
# 产品微服务地址
uri: http://localhost:8070
......
# 订单微服务地址
uri: http://localhost:8080
......
 

这里,我们将网关整合Nacos实现从nacos注册中心获取转发的服务地址。

  1. 在服务网关shop-gateway模块的pom.xml文件中继续添加如下依赖。
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  1. 在服务网关shop-gateway模块的启动类cn.mawenda.shop.ShopGatewayApplication上添加@EnableDiscoveryClient注解,如下所示。
/**
 * 服务网关启动类
 * @Author: Ma.wenda
 * @Date: 2024-01-31 17:19
 * @Version: 1.0
 **/
@SpringBootApplication
@EnableDiscoveryClient
public class ShopGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShopGatewayApplication.class, args);
    }
}
  1. 将application.yml备份一份,命名为application-simple.yml,并修改application.yml配置文件,修改后的文件如下所示。
server:
  port: 10001
spring:
  application:
    name: server-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

    gateway:
      # 解决SpringCloud Gateway跨域的问题
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOriginPatterns: "*"
            allowedMethods: "*"
            allowCredentials: true
            allowedHeaders: "*"
        discovery:
          locator:
            enabled: true

      # 表示一个路由数组,可以在此节点下配置多个路由信息。
      routes:
        - id: user-gateway # 当前路由的唯一标识。
          uri: lb://server-user
          order: 1 # 路由的优先级,数字越小表示优先级越高。
          predicates: # 网关断言,也就是路由转发的条件,也是一个数组,可以配置多个路由转发条件。
            - Path=/server-user/** # 当客户端请求的路径满足Path的规则时,进行路由转发操作。
          filters: # 网关过滤器,在过滤器中可以修改请求的参数和header信息,以及响应的结果和header信息,网关过滤器也是一个数组,可以配置多个过滤规则。
            - StripPrefix=1 # 网关在进行路由转发之前,会去掉1层访问路径。

        - id: product-gateway
          uri: lb://server-product
          order: 1
          predicates:
            - Path=/server-product/**
          filters:
            - StripPrefix=1

        - id: order-gateway
          uri: lb://server-order
          order: 1
          predicates:
            - Path=/server-order/**
          filters:
            - StripPrefix=1

在上述配置中增加了Nacos相关的配置,如下所示。

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

新增了让SpringCloud Gateway可以发现Nacos中的服务配置,如下所示。

Spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true

另外,将硬编码的服务转发地址修改成从Nacos中按照名称获取微服务地址,并按照负载均衡策略分发。

# 从Nacos中获取微服务地址
uri: lb://server-user
......
uri: lb://server-product
......
uri: lb://server-order
......

lb指的是从Nacos中按照微服务的名称获取微服务地址,并按照负载均衡的策略分发。使用lb从Nacos中获取微服务时,遵循如下的格式。

lb://微服务名称

微服务的名称就是各个微服务在application.yml文件中配置的服务名称。

spring:
  application:
    name: 服务名称
  1. 分别启动用户微服务、商品微服务、订单微服务和服务网关。
  2. 分别通过服务网关,访问用户微服务、产品微服务、订单微服务的接口,如下所示。

image.png

image.png

image.png
配置 lb://微服务名称后,通过服务网关请求各服务均正常。

3.4 网关整合Sentinel限流

Sentinel从1.6.0版本开始,提供了SpringCloud Gateway的适配模块,并且可以提供两种资源维度的限流,一种是route维度;另一种是自定义API分组维度。

  • route维度:对application.yml文件中配置的spring.cloud.gateway.routes.id限流,并且资源名为spring.cloud.gateway.routes.id对应的值。
  • 自定义API分组维度:利用Sentinel提供的API接口来自定义API分组,并且对这些API分组进行限流。

3.4.1 实现route维度限流

  1. 在服务网关shop-gateway模块的pom.xml文件中添加如下依赖。
<dependencies>
  <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  </dependency>

  <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
  </dependency>

  <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  </dependency>

  <dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
  </dependency>
</dependencies>
  1. 在服务网关shop-gateway模块中新建cn.mawenda.shop.gateway.config包,并在包下新建GatewayConfig类。基于Sentinel 的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的SentinelGatewayFilter实例以及 SentinelGatewayBlockExceptionHandler 实例即可。

GatewayConfig类的源代码如下所示。

/**
 * 网关配置类
 * @Author: Ma.wenda
 * @Date: 2024-02-01 14:20
 * @Version: 1.0
 **/
@Configuration
public class GatewayConfig {
    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    @Value("${spring.cloud.gateway.discovery.locator.route-id-prefix}")
    private String routeIdPrefix;

    public GatewayConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                         ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 初始化一个限流的过滤器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    @PostConstruct
    public void init() {
        this.initGatewayRules();
        this.initBlockHandlers();
    }

    /**
     * 配置初始化的限流参数
     */
    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();

        /**
         * Sentinel整合SpringCloud Gateway使用的API类型为Route ID类型,也就是基于route维度时,
         * 由于Sentinel为SpringCloud Gateway网关生成的API名称规则如下:
         * 生成的规则为:${spring.cloud.gateway.discovery.locator.route-id-prefix}后面直接加上目标微服务的名称,如下所示。
         * ${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称
         * 其中,${spring.cloud.gateway.discovery.locator.route-id-prefix}是在yml文件中配置的访问前缀
         *
         * 为了让通过服务网关访问目标微服务链接后,请求链路中生成的API名称与流控规则中生成的API名称一致,以达到启动项目即可实现访问链接的限流效果,
         * 而无需登录Setinel管理界面手动配置限流规则,可以将
         * resource参数设置为${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称
         *
         * 当然,如果不按照上述配置,也可以在项目启动后,通过服务网关访问目标微服务链接后,在Sentinel管理界面的请求链路中找到对应的API名称所代表的请求链路,
         * 然后手动配置限流规则。
         **/
        //        //用户微服务网关
        //        rules.add(this.getGatewayFlowRule("user-gateway"));
        //        //商品微服务网关
        //        rules.add(this.getGatewayFlowRule("product-gateway"));
        //        //订单微服务网关
        //        rules.add(this.getGatewayFlowRule("order-gateway"));
        //用户微服务网关
        rules.add(this.getGatewayFlowRule(getResource("server-user")));
        //商品微服务网关
        rules.add(this.getGatewayFlowRule(getResource("server-product")));
        //订单微服务网关
        rules.add(this.getGatewayFlowRule(getResource("server-order")));
        //加载规则
        GatewayRuleManager.loadRules(rules);
    }

    private String getResource(String targetServiceName){
        if (routeIdPrefix == null){
            routeIdPrefix = "";
        }
        return routeIdPrefix.concat(targetServiceName);
    }

    private GatewayFlowRule getGatewayFlowRule(String resource){
        //传入资源名称生成GatewayFlowRule
        GatewayFlowRule gatewayFlowRule = new GatewayFlowRule(resource);
        //限流阈值
        gatewayFlowRule.setCount(1);
        //统计的时间窗口,单位为
        gatewayFlowRule.setIntervalSec(1);
        return gatewayFlowRule;
    }

    /**
     * 配置限流的异常处理器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    /**
     * 自定义限流异常页面
     */
    private void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map map = new HashMap<>();
                map.put("code", 1001);
                map.put("codeMsg", "接口被限流了");
                return ServerResponse.status(HttpStatus.OK).
                        contentType(MediaType.APPLICATION_JSON_UTF8).
                        body(BodyInserters.fromObject(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
}

注意:
Sentinel1.8.4整合SpringCloud Gateway使用的API类型为Route ID类型时,也就是基于route维度时,由于Sentinel为SpringCloud Gateway网关生成的API名称规则如下:
生成的规则为:${spring.cloud.gateway.discovery.locator.route-id-prefix}后面直接加上目标微服务的名称,如下所示。 s p r i n g . c l o u d . g a t e w a y . d i s c o v e r y . l o c a t o r . r o u t e − i d − p r e f i x 目标微服务的名称。其中, {spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称。其中, spring.cloud.gateway.discovery.locator.routeidprefix目标微服务的名称。其中,{spring.cloud.gateway.discovery.locator.route-id-prefix}是在yml文件中配置的访问前缀。
为了让通过服务网关访问目标微服务链接后,请求链路中生成的API名称与流控规则中生成的API名称一致,以达到启动项目即可实现访问链接的限流效果,而无需登录Setinel管理界面手动配置限流规则,可以将生成GatewayFlowRule对象的resource参数设置为${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称
当然,如果不按照上述配置,也可以在项目启动后,通过服务网关访问目标微服务链接后,在Sentinel管理界面的请求链路中找到对应的API名称所代表的请求链路,然后手动配置限流规则。

  1. 将服务网关shop-gateway模块的application.yml文件备份一份名称为application-nacos-simple.yml的文件,并将application.yml文件的内容修改成如下所示。
server:
  port: 10001
spring:
  application:
    name: server-gateway
  main:
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      discovery:
        server-addr: ${NACOS_HOST:shop-nacos}:${NACOS_PORT:8848}
    sentinel:
      transport:
        port: 7777
        dashboard: 127.0.0.1:8888
        web-context-unify: false
        eager: true

    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            # allowedOrigins springboot版本为2.4.0以上替换为allowedOriginPatterns
            allowedOrigins: "*"
            allowedMethods: "*"
            allowCredentials: true
            allowedHeaders: "*"
      discovery:
        locator:
          enabled: true
          route-id-prefix: gateway-

  • spring.cloud.sentinel.eager表示程序启动时,流控规则是否立即注册到Sentinel,配置为true表示立即注册到Sentinel。
  • spring.cloud.gateway.discovery.locator.route-id-prefix:生成流控规则API名称的前缀。
  1. 在IDEA中配置启动服务网关shop-gateway模块的参数-Dcsp.sentinel.app.type=1,如下所示。

image.png
或在启动类io.binghe.shop.ShopGatewayApplication的main()方法中添加一行System.setProperty(“csp.sentinel.app.type”, “1”);代码,如下所示。
image.png

  1. 分别启动用户微服务、商品微服务、订单微服务和服务网关,启动后会在Sentinel管理界面左侧菜单栏中看到server-gateway菜单,并且在流控规则中可以看到各服务的流控规则已经注册到Sentinel中,如下所示。

image.png

  1. 通过服务网关访问各微服务,不断刷新各微服务的请求,如下所示。

image.png

image.png

image.png
可以看到,通过不断刷新请求,各服务均触发了服务限流,并返回了自定义的限流结果数据。

3.4.2 实现自定义API分组维度限流

  1. 在服务网关shop-gateway模块的cn.mawenda.shop.gateway.config.GatewayConfig配置类中新增initCustomizedApis()方法,初始化API管理的信息,源码如下所示。
private void initCustomizedApis() {
    Set<ApiDefinition> definitions = new HashSet<>();
    ApiDefinition api1 = new ApiDefinition("user_api1")
        .setPredicateItems(new HashSet<ApiPredicateItem>() {{
            // 以/server-user/user/api1 开头的请求
            add(new ApiPathPredicateItem().setPattern("/server-user/user/api1/**").
                setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
        }});
    ApiDefinition api2 = new ApiDefinition("user_api2")
        .setPredicateItems(new HashSet<ApiPredicateItem>() {{
            // 以/server-user/user/api2/demo1 完成的url路径匹配
            add(new ApiPathPredicateItem().setPattern("/server-user/user/api2/demo1"));
        }});
    definitions.add(api1);
    definitions.add(api2);
    GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}

上述代码中,配置了两个API分组,每个API分组的规则如下。

  • user_api1分组:匹配以/product-serv/product/api1开头的所有请求。
  • user_api2分组:精确匹配/server-user/user/api2/demo1。
  1. 在服务网关shop-gateway模块的cn.mawenda.shop.gateway.config.GatewayConfig配置类中init()方法中调用initCustomizedApis()方法,为了避免route维度的限流对自定义API分组维度的限流产生影响,这里,同时在init()方法中注释掉调用initGatewayRules()方法,修改后的init()方法的代码如下所示。
@PostConstruct
public void init() {
    //this.initGatewayRules();
    this.initBlockHandlers();
    this.initCustomizedApis();
}
  1. 在用户微服务shop-user的cn.mawenda.shop.user.controller.UserController类中新增四个测试接口,源码如下所示。
@GetMapping(value = "/api1/demo1")
public String api1Demo1(){
    log.info("访问了api1Demo1接口");
    return "api1Demo1";
}
@GetMapping(value = "/api1/demo2")
public String api1Demo2(){
    log.info("访问了api1Demo2接口");
    return "api1Demo2";
}

@GetMapping(value = "/api2/demo1")
public String api2Demo1(){
    log.info("访问了api2Demo1接口");
    return "api2Demo1";
}
@GetMapping(value = "/api2/demo2")
public String api2Demo2(){
    log.info("访问了api2Demo2接口");
    return "api2Demo2";
}
  1. 分别启动用户微服务、商品微服务、订单微服务和服务网关,启动后会在Sentinel管理界面左侧菜单栏中看到server-gateway菜单,在Api管理中会发现我们自定义的API分组已经注册到Sentinel中了,如下所示。

image.png

  1. 在Sentinel管理界面的流控规则中,由于我们注释了以route维度限流的方法,所以,在流控规则里的限流规则为空,那么我们新增网关流控规则,如下所示。

image.png

  1. 点击新增网关流控规则后,会弹出新增网关流控规则配置框,按照如下方式为user_api1分组配置限流规则。

image.png

  1. 点击新增按钮后,按照同样方式为user_api2分组配置限流规则。

image.png

  1. 配置完毕后,在流控规则中的限流规则如下所示。

image.png

  1. 配置的预期测试结果如下。
  • 当频繁访问http://localhost:10001/server-user/user/api1/demo1时会被限流。
  • 当频繁访问http://localhost:10001/server-user/user/api1/demo2时会被限流。
  • 当频繁访问http://localhost:10001/server-user/user/api2/demo1时会被限流。
  • 当频繁访问http://localhost:10001/server-user/user/api2/demo2时不会被限流。

注意,只有最后一个不会被限流。

  1. 使用浏览器频繁访问如下几个接口,分别进行测试,如下所示。

image.png

image.png

image.png

image.png
经过以上的测试,可以发现api1/demo1、api1/demo2、api2/demo1都触发了限流,并且返回了限流结果数据,而api2/demo2无论访问的多频繁,都不会触发Sentinel限流,至此,我们成功的在项目中整合了SpringCloud Gateway网关,并实现了网关限流操作。

4 SpringCloud Gateway核心技术

SpringCloud Gateway能够实现多种网关功能,比如路由转发、断言、过滤器、熔断、限流、降级、自定义谓词配置、自定义过滤器等等多种功能。

4.1 网关断言

网关断言是用于在 API 网关中进行路由规则匹配和过滤的一种机制。它的作用是根据请求的特征(比如请求路径、请求参数、请求头等)来进行条件判断,从而实现请求的路由、过滤或转发。以下是网关断言的主要作用:

  1. 请求路由:网关断言可以根据请求的特征将请求路由到不同的目标服务。比如,根据请求路径来将请求路由到不同的微服务实例,或者根据请求头信息将请求路由到不同的处理逻辑。
  2. 请求过滤:网关断言可以根据特定的条件对请求进行过滤。比如,根据请求参数或请求头信息来拦截请求、修改请求、添加额外的信息等。
  3. 请求转发:网关断言可以根据条件将请求转发到其他的网关或服务实例,实现请求的转发功能。
  4. 请求重定向:根据条件将请求重定向到指定的 URL 地址。
  5. 请求限流:通过断言来对请求进行限流,控制请求的流量,防止过载。

4.1.1 SpringCloud Gateway内置断言

SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。

4.1.1.1 基于日期时间类型的断言

基于日期时间类型的断言根据时间做判断,主要有三个:

  • AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期
  • BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期
  • BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内

使用示例

- After=2022-05-10T23:59:59.256+08:00[Asia/Shanghai]
4.1.1.2 基于远程地址的断言

RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机缔造者是否在地址段中。
使用示例

- RemoteAddr=192.168.0.1/24
4.1.1.3 基于Cookie的断言

CookieRoutePredicateFactory:接收两个参数, cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。
使用示例

- Cookie=name, binghe.
4.1.1.4 基于Header的断言

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式,判断请求的Header是否具有给定名称且值与正则表达式匹配。
使用示例

- Header=X-Request-Id, \d+
4.1.1.5 基于Host的断言

HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。
使用示例

- Host=**.mawenda.cn
4.1.1.6 基于Method请求方法的断言

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
使用示例

- Method=GET
4.1.1.17 基于Path请求路径的断言

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
使用示例

- Path=/binghe/{segment}
4.1.1.18 基于Query请求参数的断言

QueryRoutePredicateFactory :接收两个参数,请求参数和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。
使用示例

- Query=name, binghe.
4.1.1.19 基于路由权重的断言

WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发。
使用示例

- id: weight1
  uri: http://localhost:8080
  predicates:
    - Path=/api/**
    - Weight=group1,2
  filters:
    - StripPrefix=1
- id: weight2
  uri: http://localhost:8081
  predicates:
    - Path=/api/**
    - Weight=group1,8
  filters:
    - StripPrefix=1

4.1.2 演示内置断言

我们基于Path请求路径的断言判断请求路径是否符合规则,基于远程地址的断言判断请求主机地址是否在地址段中,并且限制请求的方式为GET方式。整个演示的过程以访问用户微服务的接口为例。

  1. 在开发项目时,所有的服务都是在本地启动的,首先查看本机的IP地址,如下所示。
# mac
ipconfig getifaddr en1
# win
ipconfig

image.png

  1. 在服务网关模块shop-gateway中,将application.yml文件备份成application-sentinel.yml文件,并将application.yml文件中的内容修改成application-simple.yml文件中的内容。接下来,在application.yml文件中的spring.cloud.gateway.routes节点下的- id: user-gateway下面进行断言配置,配置后的结果如下所示。
# 此处展示的非完整配置,在原配置基础上只修改了routes下配置
spring:
  cloud:
    gateway:
      routes:
        - id: user-gateway
          uri: http://localhost:8060
          order: 1
          predicates:
            - Path=/user/**
            - RemoteAddr=192.168.3.1/24
            - Method=GET
          filters:
            - StripPrefix=1
 
  1. 配置完成后启动用户微服务和网关服务,通过网关服务访问用户微服务,在浏览器中输入http://localhost:10001/server-user/user/get/1001,如下所示。

image.png
可以看到,访问 http://localhost:10001/server-user/user/get/1001 链接不能正确的请求不能正确获取到用户信息。
接下来,在浏览器中输入 http://192.168.3.45:10001/user/user/get/1001 ,能正确的获取到用户信息,如下所示。
image.png

  1. 停止网关微服务,将基于远程地址的断言配置成- RemoteAddr=192.168.1.1/24,也就是将基于远程地址的断言配置成与我本机IP地址不在同一个网段,这样就能演示请求主机地址不在地址段中的情况,修改后的基于远程地址的断言配置如下所示。
# 此处展示的非完整配置,在原配置基础上只修改了routes下配置
spring:
  cloud:
    gateway:
      routes:
        - id: user-gateway
          uri: http://localhost:8060
          order: 1
          predicates:
            - Path=/user/**
            - RemoteAddr=192.168.1.1/24
            - Method=GET
          filters:
            - StripPrefix=1
  1. 重启网关,浏览器重新访问http://localhost:10001/server-user/user/get/1001,如下所示。

image.png
可以看到,访问http://localhost:10001/server-user/user/get/1001链接依旧不能访问。
在使用 http://192.168.3.45:10001/user/user/get/1001 访问该链接,也不能正确访问了。
image.png

4.1.3 自定义断言

SpringCloud Gateway支持自定义断言功能,我们可以在具体业务中,基于SpringCloud Gateway自定义特定的断言功能。

4.1.3.1 自定义断言概述

SpringCloud Gateway虽然提供了多种内置的断言功能,但是在某些场景下无法满足业务的需要,此时,我们就可以基于SpringCloud Gateway自定义断言功能,以此来满足我们的业务场景。

4.1.3.2 实现自定义断言

基于SpringCloud Gateway实现断言功能,实现后的效果是在服务网关的application.yml文件中的spring.cloud.gateway.routes节点下的- id: user-gateway下面进行如下配置。

spring:
  cloud:
    gateway:
      routes:
        - id: user-gateway
          uri: http://localhost:8060
          order: 1
          predicates:
            - Path=/user/**
            - Name=binghe
          filters:
            - StripPrefix=1

通过服务网关访问用户微服务时,只有在访问的链接后面添加?name=binghe参数时才能正确访问用户微服务。

  1. 在网关服务shop-gateway中新建cn.mawenda.shop.gateway.predicate包,在包下新建NameRoutePredicateConfig类,主要定义一个Spring类型的name成员变量,用来接收配置文件中的参数,源码如下所示。
/**
 * 接收配置文件中的参数
 * @Author: Ma.wenda
 * @Date: 2024-05-10 15:45
 * @Version: 1.0
 **/
@Data
public class NameRoutePredicateConfig implements Serializable{

    private static final long serialVersionUID = 1L;

    private String name;

}

  1. 在网关服务shop-gateway中cn.mawenda.shop.gateway.predicate包下,创建NameRoutePredicateFactory类,并继承AbstractRoutePredicateFactory类,并覆盖相关方法,如下所示。
@Component
public class NameRoutePredicateFactory extends AbstractRoutePredicateFactory<NameRoutePredicateConfig> {

    public NameRoutePredicateFactory() {
        super(NameRoutePredicateConfig.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(NameRoutePredicateConfig config) {
        return (serverWebExchange)->{
            String name = serverWebExchange.getRequest().getQueryParams().getFirst("name");
            if (StringUtils.isEmpty(name)){
                name = "";
            }
            return name.equals(config.getName());
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("name");
    }
}
  1. 在服务网关的application.yml文件中的spring.cloud.gateway.routes节点下的- id: user-gateway下面进行如下配置。
spring:
  cloud:
    gateway:
      routes:
        - id: user-gateway
          uri: http://localhost:8060
          order: 1
          predicates:
            - Path=/user/**
            - Name=binghe
          filters:
            - StripPrefix=1
  1. 分别启动用户微服务与网关服务,在浏览器中输入http://localhost:10001/server-user/user/get/1001,如下所示。

image.png

  1. 在浏览器中输入http://localhost:10001/server-user/user/get/1001?name=binghe,如下所示。

image.png
可以看到,在访问链接后添加?name=binghe参数后,能够正确访问并获取用户信息,
至此,我们就实现了自定义断言功能。

4.2 网关过滤器

过滤器可以在请求过程中,修改请求的参数和响应的结果等信息。在生命周期的角度总体上可以分为前置过滤器(Pre)和后置过滤器(Post)。在实现的过滤范围角度可以分为局部过滤器(GatewayFilter)和全局过滤器(GlobalFilter)。局部过滤器作用的范围是某一个路由,全局过滤器作用的范围是全部路由。

  • Pre前置过滤器:在请求被网关路由之前调用,可以利用这种过滤器实现认证、鉴权、路由等功能,也可以记录访问时间等信息。
  • Post后置过滤器:在请求被网关路由到微服务之后执行。可以利用这种过滤器修改HTTP的响应Header信息,修改返回的结果数据(例如对于一些敏感的数据,可以在此过滤器中统一处理后返回),收集一些统计信息等。
  • 局部过滤器(GatewayFilter):也可以称为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组。
  • 全局过滤器(GlobalFilter):这种过滤器主要作用于所有的路由。

4.2.1 局部过滤器

局部过滤器又称为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组。

4.2.1.1 局部过滤器概述

在SpringCloud Gateway中内置了很多不同类型的局部过滤器,主要如下所示。

过滤器作用参数
AddRequestHeader为原始请求添加HeaderHeader的名称及值
AddRequestParameter为原始请求添加请求参数参数名称及值
AddResponseHeader为原始响应添加HeaderHeader的名称及值
DedupeResponseHeader剔除响应头中重复的值需要去重的Header名 称及去重策略
Hystrix为路由引入Hystrix的断路器保护HystrixCommand的名 称
FallbackHeaders为fallbackUri的请求头中添加具 体的异常信息Header的名称
PrefixPath为原始请求路径添加前缀前缀路径
PreserveHostHeader为请求添加一个 preserveHostHeader=true的属 性, 路由过滤器会检查该属性以 决定是否要发送原始的Host
RequestRateLimiter用于对请求限流, 限流算法为令牌桶keyResolver、 rateLimiter、 statusCode、 denyEmptyKey、 emptyKeyStatus
RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的 url
RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的 一系列Header默认就会启用, 可以通 过配置指定仅删除哪些 Header
RemoveRequestHeader为原始请求删除某个HeaderHeader名称
RemoveResponseHeader为原始响应删除某个HeaderHeader名称
RewritePath重写原始的请求路径原始路径正则表达式以 及重写后路径的正则表 达式
RewriteResponseHeader重写原始响应中的某个HeaderHeader名称, 值的正 则表达式, 重写后的值
SaveSession在转发请求之前, 强制执行 WebSession::save操作
secureHeaders为原始响应添加一系列起安全作 用的响应头无, 支持修改这些安全 响应头的值
SetPath修改原始的请求路径修改后的路径
SetResponseHeader修改原始响应中某个Header的值Header名称, 修改后 的值
SetStatus修改原始响应的状态码HTTP 状态码, 可以是 数字, 也可以是字符串
StripPrefix用于截断原始请求的路径使用数字表示要截断的 路径的数量
Retry针对不同的响应进行重试retries、 statuses、 methods、 series
RequestSize设置允许接收最大请求包的大 小。 如果请求包大小超过设置的 值, 则返回 413 Payload Too Large请求包大小, 单位为字 节, 默认值为5M
ModifyRequestBody在转发请求之前修改原始请求体 内容修改后的请求体内容
ModifyResponseBody修改原始响应体的内容修改后的响应体内容
4.2.1.2 演示内部过滤器

演示内部过滤器时,我们为原始请求添加一个名称为IP的Header,值为localhost,并添加一个名称为name的参数,参数值为binghe。同时修改响应的结果状态,将结果状态修改为1001。

  1. 在服务网关的application.yml文件中的spring.cloud.gateway.routes节点下的- id: user-gateway下面进行如下配置。
spring:
  cloud:
    gateway:
      routes:
        - id: user-gateway
          uri: http://localhost:8060
          order: 1
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix=1
            - AddRequestHeader=IP,localhost
            - AddRequestParameter=name,binghe
            - SetStatus=1001
  1. 在用户微服务的cn.mawenda.shop.user.controller.UserController类中新增apiFilter1()方法,如下所示。
@GetMapping(value = "/api/filter1")
public String apiFilter1(HttpServletRequest request, HttpServletResponse response){
    log.info("访问了apiFilter1接口");
    String ip = request.getHeader("IP");
    String name = request.getParameter("name");
    log.info("ip = " + ip + ", name = " + name);
    return "apiFilter1";
}
  1. 分别启动用户微服务与网关服务,在浏览器访问http://localhost:10001/server-user/user/api/filter1,如下所示。

image.png
image.png
可以看到,接口请求成功,并将状态码修改为1001。
用户微服务控制台输出如下内容。

访问了apiFilter1接口
ip = localhost, name = binghe

说明使用SpringCloud Gateway的内置过滤器成功为原始请求添加了一个名称为IP的Header,值为localhost,并添加了一个名称为name的参数,参数值为binghe。同时修改了响应的结果状态,将结果状态修改为1001,符合预期效果。

4.2.1.3 自定义局部过滤器

基于SpringCloud Gateway自定义局部过滤器实现是否开启灰度发布的功能,整个实现过程如下所示。

  1. 在服务网关的application.yml文件中的spring.cloud.gateway.routes节点下的- id: user-gateway下面进行如下配置。
spring:
  cloud:
    gateway:
      routes:
        - id: user-gateway
          uri: http://localhost:8060
          order: 1
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix=1
            - Grayscale=true
  1. 在网关服务模块shop-gateway中新建cn.mawenda.shop.gateway.filter包,在包下新建GrayscaleGatewayFilterConfig类,用于接收配置中的参数,如下所示。
/**
 * 接收配置参数
 * @Author: Ma.wenda
 * @Date: 2024-05-10 16:56
 * @Version: 1.0
 **/
@Data
public class GrayscaleGatewayFilterConfig implements Serializable {

    private static final long serialVersionUID = 1L;

    private boolean grayscale;
    
}

  1. 在cn.mawenda.shop.gateway.filter包下创建GrayscaleGatewayFilterFactory类,继承org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory类,主要是实现自定义过滤器,模拟实现灰度发布。代码如下所示。
/**
 * @Author: Ma.wenda
 * @Date: 2024-05-10 16:59
 * @Version: 1.0
 **/
@Component
public class GrayscaleGatewayFilterFactory extends AbstractGatewayFilterFactory<GrayscaleGatewayFilterConfig> {

    public GrayscaleGatewayFilterFactory(){
        super(GrayscaleGatewayFilterConfig.class);
    }
    @Override
    public GatewayFilter apply(GrayscaleGatewayFilterConfig config) {
        return (exchange, chain) -> {
            if (config.isGrayscale()){
                System.out.println("开启了灰度发布功能...");
            }else{
                System.out.println("关闭了灰度发布功能...");
            }
            return chain.filter(exchange);
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("grayscale");
    }
}
  1. 分别启动用户微服务和服务网关,在浏览器中输入http://localhost:10001/server-user/user/get/1001,如下所示。

image.png
可以看到,通过服务网关正确访问到了用户微服务,并正确获取到了用户信息。

4.2.2 全局过滤器

全局过滤器是一系列特殊的过滤器,会根据条件应用到所有路由中。

4.2.2.1 全局过滤器概述

在SpringCloud Gateway中内置了多种不同的全局过滤器,如下所示。

过滤器作用
ForwardRoutingFilter用于本地forward,也就是将请求在Gateway服务内进行转发,而不是转发到下游服务。
LoadBalancerClientFilter整合Ribbon实现负载均衡。
NettyRoutingFilter使用Netty的HttpClient 转发http、https请求。
NettyWriteResponseFilter将代理响应写回网关的客户端侧。
RouteToRequestUrlFilter将从request里获取的原始url转换成Gateway进行请求转发时所使用的url。
WebsocketRoutingFilter使用Spring Web Socket将转发 Websocket 请求。
GatewayMetricsFilter整合监控相关,提供监控指标。
ForwardPathFilter解析路径,并转发路径。
WebClientHttpRoutingFilter通过WebClient客户端转发请求真实的URL。
WebClientWriteResponseFilter将响应信息写入到当前的请求响应中。
4.2.2.2 演示全局过滤器
  1. 在服务网关模块shop-gateway模块下的cn.mawenda.shop.gateway.config包下新建GatewayFilterConfig类,并在类中配置几个全局过滤器,如下所示。
/**
 * 网关过滤器配置
 * @Author: Ma.wenda
 * @Date: 2024-05-10 17:32
 * @Version: 1.0
 **/
@Configuration
@Slf4j
public class GatewayFilterConfig {
    @Bean
    @Order(-1)
    public GlobalFilter globalFilter() {
        return (exchange, chain) -> {
            log.info("执行前置过滤器逻辑");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.info("执行后置过滤器逻辑");
            }));
        };
    }
}

注意:@Order注解中的数字越小,执行的优先级越高。

  1. 启动用户微服务与服务网关,在浏览器中访问http://localhost:10001/server-user/user/get/1001,如下所示。

image.png
在网关服务终端输出如下信息。
image.png
说明全局过滤器生效。

4.2.2.3 自定义全局过滤器

SpringCloud Gateway内置了很多全局过滤器,一般情况下能够满足实际开发需要,但是对于某些特殊的业务场景,还是需要我们自己实现自定义全局过滤器。
这里,我们就模拟实现一个获取客户端访问信息,并统计访问接口时长的全局过滤器。

  1. 在网关服务模块cn.mawenda.shop.order.filter包下,新建GlobalGatewayLogFilter类,实现org.springframework.cloud.gateway.filter.GlobalFilter接口和org.springframework.core.Ordered接口,代码如下所示。
/**
 * 自定义全局过滤器,模拟实现获取客户端信息并统计接口访问时长
 * @Author: Ma.wenda
 * @Date: 2024-05-10 17:38
 * @Version: 1.0
 **/
@Slf4j
@Component
public class GlobalGatewayLogFilter implements GlobalFilter, Ordered {
    /**
     * 开始访问时间
     */
    private static final String BEGIN_VISIT_TIME = "begin_visit_time";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //先记录下访问接口的开始时间
        exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);
            if (beginVisitTime != null){
                log.info("访问接口主机: " + exchange.getRequest().getURI().getHost());
                log.info("访问接口端口: " + exchange.getRequest().getURI().getPort());
                log.info("访问接口URL: " + exchange.getRequest().getURI().getPath());
                log.info("访问接口URL参数: " + exchange.getRequest().getURI().getRawQuery());
                log.info("访问接口时长: " + (System.currentTimeMillis() - beginVisitTime) + "ms");
            }
        }));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
  1. 启动用户微服务与网关服务,在浏览器中输入http://localhost:10001/server-user/user/api/filter1?name=binghe,如下所示。
/**
 * 自定义全局过滤器,模拟实现获取客户端信息并统计接口访问时长
 * @Author: Ma.wenda
 * @Date: 2024-05-10 17:38
 * @Version: 1.0
 **/
@Slf4j
@Component
public class GlobalGatewayLogFilter implements GlobalFilter, Ordered {
    /**
     * 开始访问时间
     */
    private static final String BEGIN_VISIT_TIME = "begin_visit_time";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //先记录下访问接口的开始时间
        exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);
            if (beginVisitTime != null){
                log.info("访问接口主机: " + exchange.getRequest().getURI().getHost());
                log.info("访问接口端口: " + exchange.getRequest().getURI().getPort());
                log.info("访问接口URL: " + exchange.getRequest().getURI().getPath());
                log.info("访问接口URL参数: " + exchange.getRequest().getURI().getRawQuery());
                log.info("访问接口时长: " + (System.currentTimeMillis() - beginVisitTime) + "ms");
            }
        }));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
  1. 启动用户微服务与网关服务,在浏览器中输入http://localhost:10001/server-user/user/api/filter1?name=binghe,如下所示。

image.png
image.png
自定义全局过滤器生效。

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值