SpringCloud(15)之SpringCloud Gateway

一、Spring Cloud Gateway介绍

Spring Cloud Gateway 是Spring Cloud团队的一个全新项目,基于Spring 5.0、SpringBoot2.0、 Project Reactor 等技术开发的网关。旨在为微服务架构提供一种简单有效统一的API路由管理方式。

Spring Cloud Gateway 作为SpringCloud生态系统中的网关,目标是替代Netflix Zuul。Gateway不仅 提供统一路由方式,并且基于Filter链的方式提供网关的基本功能。例如:安全,监控/指标,和限流。

本节我们主要是来学一下SpringCloud Gateway的工作原理以及编写路由方式、拦截器介绍以及用实现对应的限流算法;

二、SpringCloud Gateway的工作原理

我们在学习Gateway之前,先弄清楚Gateway的工作原理,后面使用它的各个功能时,就知道该如何使 用了,工作流程图如下:

gateway的执行流程如下:

java 复制代码
1:Gateway的客户端回向 Spring Cloud Gateway发起请求,请求首先会被HttpwebHandlerAdapter 进行提取组装成网关的上下文,然后网关的上下文会传递到DispatcherHandler。

2:DispatcherHandler 是所有请求的分发处理器, DispatcherHandler 主要负责分发请求对应的处理器,比如将请求分发到对应RoutePredicateHandlerMapping ( 路 由 断 言 处 理 器 映 射 器 ) 。

3: 路由断言处理映射器主要用于路由的查找,以及找到路由后返回对应的FilteringwebHandler。

4:FilteringwebHandler主 要 负 责 组 装Filter链表并调 用Filter 执 行 一 系 列Filter 处理,然后把请求 转到后端对应的代理服务处理,处理完毕后,将Response返回到Gateway客户端 。在Filter链 中 , 通 过 虚 线 分 割Filter  的原因是,过滤器可以在转发请求之前处理或者接收到被代理服务的 返回结果之后处理。所有的Pre 类 型 的Filter  执行完毕之后,才会转发请求到被代理的服务处理。被代理
的服务把所有请求完毕之后,才会执行Post类型的过滤器 。

2.2Gateway的动态路由

Gateway 路由配置分为基于配置的由设置和基于代码动态路由配置,静态路由是指在application.yml中 把路由信息配置好了,而动态路由则是从数据库中加载而来,我们接下来把这2种路由操作都实现一次。

2.2.1 业务说明

如上图所示,我们要实现一下功能:

|----|---------------------------------------------------------------------------------|
| 1: | 用 户 所 有 请 求 以 /order 开始的请求,都路由到 hailtaxi - order 服 务 |
| 2: | 用 户 所 有 请 求 以 /driver 开始的请求,都路由到 hailtaxi - driv er 服 务 |
| 3: | 用 户 所 有 请 求 以 /pay开始的请求,都路由到 hailtaxi-pay 服 务 |

2.2.2 基于配置的路由设置

如上图所示,正是gateway静态路由配置,这种方式的缺点就是硬编码写死的路由配置:

java 复制代码
1:用户所有请求以/order 开始的请求,都路由到 hailtaxi-order   服务
2:用户所有请求以/driver 开始的请求,都路由到 hailtaxi-driver   服务
3:用户所有请求以/pay开始的请求,都路由到 hailtaxi-pay  服务

配置代码如下所示:

java 复制代码
server:
  port: 8001
spring:
  application:
    name: hailtaxi-gateway
  main:
    allow-bean-definition-overriding: true
  cloud:
    #Consul配置
    consul:
      host: localhost
      port: 8500
      discovery:
        #注册到Consul中的服务名字
        service-name: ${spring.application.name}
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - PUT
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/**
        filters:
          - name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatory
            args:
              key-resolver: "#{@ipKeyResolver}"
              redis-rate-limiter.replenishRate: 1
              redis-rate-limiter.burstCapacity: 1
          #- StripPrefix=1
        #- Cookie=username,itheima
        #- Header=token,^(?!\d+$)[\da-zA-Z]+$
        #- Method=GET
      #默认全局过滤器
      default-filters:
        # 往响应过滤器中加入信息
        - AddResponseHeader=X-Response-Default-MyName,itheima

参数配置说明:

  • -id:这是唯一的标记,不能出现重复;
  • uri:路由的地址,可以是lb:ip:端口,也是可以lb://${spring.application.name},其中的lb表示支持负载均衡;
  • predicates:这个表示是一个断言,其实就是一个判断条件,后面会介绍一共有那些断言判断;
  • -path=/driver/**:这是路由条件,表示以/driver开头的所有请求路径到路由到driver服务去;
  • filters:这是配置的是局部过滤器,表示只对当前的路由配置生效,后面会介绍到具体的拦截器;
  • -StripPrefix=1:去掉第一个路径,这种一般是用于将请求路径进行包装,不暴露真实路径出去,然后在网关层还原真实路径,一般是用作放爬虫的;

2.2.3 基于代码的动态路由配置

我们同样实现上面的功能, ++++但++++ ++++这++++ ++++里++++ ++++基++++ 于代码方式实现。所有路由规则我们可以从数据库中读取并加载到 程序中。基于代码的路由配置我们只需要创建++++RouteLocator++++ 并添加路由配置即可,代码如下:

java 复制代码
    /***
     * 路由配置
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/driver/**").uri("lb://hailtaxi-driver"))
                .route(r -> r.path("/order/**").uri("lb://hailtaxi-order"))
                //使用GatewayFilter过滤器
                .route(r -> r.path("/pay/**").uri("lb://hailtaxi-pay").filter(new PayFilter()))
                .build();
    }

在真实场景中,基于配置文件的方式更直观、简介,但代码的路由配置是更强大,可以实现很丰富的功 能,可以把路由规则存在数据库中,每次直接从数据库中加载规则,这样的好处是可以动态刷新路由规 则,通常应用于权限系统动态配置新系统。

2.3.4 Gateway-Predicate/Filter

上面路由匹配规则中我们都用了-Path 方式,其实就是路径匹配方式,除了路径匹配方式, Gateway还支持很多丰富的匹配方式,我们对这些方式分别进行讲解。

具体的详见官网:Spring Cloud Gateway

routes下面的属性含义如下:

bash 复制代码
id : 我们自定义的路由 ID , 保持唯一
uri  : 目标服务地址

predicates:  路由条件, Predicate   接受一个输入参数,返回一个布尔值结果。该属性包含多种默认方
法来将 Predicate  组合成其他复杂的逻辑(比如:与,或,非)

Predicate 来源于Java8,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方 法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。

在 Spring Cloud Gateway 中 Spring 利 用 Predicate 的特性实现了各种路由匹配规则,通过 Header、 请求参数等不同的条件来作为条件匹配到对应的路由。

下面的一张图(来自网络)总结了 Spring Cloud 内置的几种 Predicate 的实现:

我们在这里讲解几个断言匹配 方式。
Cookie

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

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

这里表示请求携带了cookie为chocolate的数据,并且值为ch.p,就允许通过。
Header 匹配:

Header 匹配 和 Cookie 匹配 一样,也是接收两个参数,一个 header 中属性名称和一个正则表达式, 这个属性值和正则表达式匹配则执行。配置如下:

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

这里表示,请求头必要要有一个X-Request-Id,并且是数字的才能通过;
请求方式匹配:

通过请求的方式是 POST、GET、PUT、DELETE 等进行路由。配置如下:

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

表示只有请求方式是GET跟POST的才允许通过;

三、Gateway过滤器

Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter,二者区别如下:

  • GatewayFilter : 需要通过 spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或 通过spring.cloud.default-filters 配置在全局,作用在所有路由上;
    • GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成 GatewayFilterChain 可识别的过滤器,它为请求业务以及路由的 URI 转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路 由上。

过滤器作为Gateway 的重要功能。常用于请求鉴权、服务调用时长统计、修改请求或响应 header 、限 流、去除路径等等。
更多过滤器的使用大家可以参考官网: Spring Cloud Gateway

3.1 过滤器的分类

java 复制代码
默认过滤器:出厂自带,实现好了拿来就用,不需要实现
    全局默认过滤器
    局部默认过滤器
自定义过滤器:根据需求自己实现,实现后需配置,然后才能用哦。
    全局过滤器:作用在所有路由上。
    局部过滤器:配置在具体路由下,只作用在当前路由上。

3.2过滤器的使用

1)默认过滤器配置

默认过滤器有两种:全局默认过滤器和局部默认过滤器;

全局过滤器:对输出的响应头设置属性;

对输出的响应设置其头部属性名称为X-Request-Foo,值为hello
修改配置文件,配置如下:

java 复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://example.org
        filters:
        - AddRequestHeader=X-Request-Foo, Hello

局部过滤器: 通过局部默认过滤器,修改请求路径。局部过滤器在这里介绍两种:添加路径前缀、去除 路径前缀。

2)前缀处理

在项目中做开发对接接口的时候,我们很多时候需要统一API路径,比如统一以 /api 开始的请求调用 hailtaxi-driver 服务,但真实服务接口地址又没有 /api 路径,我们可以使用Gateway的过滤器处理 请求路径。

在gateway中可以通过配置路由的过滤器StripPrefix实现映射路径中的前缀处理,我们来使用一下该过 滤器,再进一步做说明。

java 复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: prefixpath_route
        uri: http://example.org
        filters:
        - StripPrefix=1

有时候为了简化用户请求地址,比如用户请求 http://localhost:8001/info/1 我们想统一路由到
http://localhost:18081/driver/info/1 ,可以使用 PrefixPath 过滤器增加前缀。

java 复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: prefixpath_route
        uri: http://example.org
        filters:
        - PrefixPath=/api

3.3自定义全局过滤器

自定义过滤器也有两类:全局自定义过滤器,和局部自定义过滤器。全局过滤器在项目中很多地方都有可能会使用,因此我们来学习下如何定义全局过滤器,并使用全局过 滤器。

全局过滤器需要实现:GlobalFilter,Ordered接口

java 复制代码
GlobalFilter:过滤器拦截处理方法
Ordered:过滤器也有多个,这里主要定义过滤器执行顺序,里面有个方法getOrder()会返回过滤器执行
顺序,返回值越小,越靠前执行

我们创建全局过滤器并完成常见业务用户权限校验。

基本逻辑:如果请求中有Token参数,则认为请求有效放行,如果没有则拦截提示授权无效。

java 复制代码
@Component
public class RouterFilter implements GlobalFilter,Ordered {

    /***
     * 路由拦截
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("GlobalFilter拦截器执行");
        //获取请求参数
        String token = exchange.getRequest().getQueryParams().getFirst("token");

        //如果token为空,则表示没有登录
        if(StringUtils.isEmpty(token)){
            //没登录,状态设置403
            exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE);
            //结束请求
            return exchange.getResponse().setComplete();
        }
        //放行
        return chain.filter(exchange);
    }

    /***
     * 拦截器顺序
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

此时请求,我们不携带token参数,效果如下:

3.4 自定义局部过滤器

局部过滤器一般是作用在某一个路由上,需要实例化创建才能使用,局部过滤器需要实现接口GatewayFilter,代码如下:

java 复制代码
public class PayFilter implements GatewayFilter, Ordered {

    /***
     * 过滤器执行拦截
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("GatewayFilter拦截器执行-----PayFilter");
        return chain.filter(exchange);
    }

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


}

然后我们再配置动态路由的时候加上这个局部过滤器即可:

java 复制代码
    /***
     * 路由配置
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/driver/**").uri("lb://hailtaxi-driver"))
                .route(r -> r.path("/order/**").uri("lb://hailtaxi-order"))
                //使用GatewayFilter过滤器
                .route(r -> r.path("/pay/**").uri("lb://hailtaxi-pay").filter(new PayFilter()))
                .build();
    }

这里需要注意一点的是,如果我们是在配置文件中使用我们自定义的局部过滤器就不能实现GatewayFilter了就要继承AbstractGatewayFilterFactory,代码如下:

java 复制代码
/**
 * 记得这里的名字后缀必须是GatewayFilterFactory
 */
@Component
public class PayGatewayFilterFactory extends AbstractGatewayFilterFactory<PayGatewayFilterFactory.Config> {


    public PayGatewayFilterFactory() {
        super(Config.class);
    }
    @Override
    public List<String> shortcutFieldOrder() {
        return Lists.newArrayList("paymethod");
    }
    /**
     * 执行拦截
     * @param config
     * @return
    配置文件中使用如下:
     */
    @Override
    public GatewayFilter apply(PayGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            String paymethod = config.getPaymethod();
            //将paymethod添加到请求头中
            exchange.getRequest().mutate().header("paymethod",paymethod);
            return chain.filter(exchange);
        };
    }

    /***
     * 获取指定属性值
     */
    @Data
    public static class Config {
        private String paymethod;
    }
}

然后使用如下:

java 复制代码
    gateway:
      #路由配置
      routes:
        #唯一标识符
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由断言
          predicates:
            - Path=/driver/**
        #唯一标识符
        - id: hailtaxi-order
          uri: lb://hailtaxi-order
          #路由断言
          predicates:
            - Path=/order/**
        #唯一标识符
        - id: hailtaxi-pay
          uri: lb://hailtaxi-pay
          #路由断言
          predicates:
            - Path=/pay/**
            - Pay=aliPay

四、跨域配置

出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本 的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源 策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另 外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol), 主机(host)和端口号(port)。

反正就一句话:ip、端口、协议三者存在一个不一致那就是跨域;

在SpringCloud Gateway中配置跨域是非常简单的,如下面配置:

java 复制代码
    gateway:
      globalcors:
        corsConfigurations: '[/**]'
        allowedOrigins: "*"
        allowedMethods:
          - GET
          - POST
          - PUT

这样子配置就能够解决跨域问题,但是在配置文件中配置会如很多局限性,如:但如果涉及到Cookie跨域,上面的配置就不生效了,如果涉及到Cookie跨域,需要创建CorsWebFilter 过滤器,所以一般不会在配置文件中去配置,会在代码中去实现配置,具体如下:

java 复制代码
    /**
     * 配置跨域
     * @return
     */
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        // cookie跨域
        config.setAllowCredentials(Boolean.TRUE);
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        // 配置前端js允许访问的自定义响应头
        config.addExposedHeader("Authorization");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }

五、限流

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

5.1漏桶算法讲解

这里只是稍微讲解一下啊漏桶算法,更多算法请自行学习:

  1. 所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
  2. 根据限流大小,设置按照一定的速率往桶里添加令牌;比如1s只允许一个请求通过,那就一秒中往令牌桶中放一个令牌;
  3. 桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
  4. 请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后, 将令牌直接删除;
  5. 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足 够的限流;

5.2令牌桶实现案例

5.2.1引入依赖

java 复制代码
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>

同时配置redis:

java 复制代码
  redis:
    host: 127.0.0.1
    port: 6379

5.2.2 定义keyResolver

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

这个好比我们做分布式锁一样,你针对那个key做锁;

我们可以根据IP来限流,比如每个IP每秒钟只能请求一次,在GatewayApplication定义key的获取,获 取客户端IP,将IP作为key,如下代码:

java 复制代码
    /***
     * IP限流
     * @return
     */
    @Bean(name="ipKeyResolver")
    public KeyResolver userKeyResolver() {
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                //获取远程客户端IP
                String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
                System.out.println("hostName:"+hostName);
                return Mono.just(hostName);
            }
        };
    }

然后再路由配置如下:

参数说明:

  • redis-rate-limiter.replenishRate:是您希望允许用户每秒执行多少请求,而不会丢弃任何请求。放令牌的速率;
复制代码
redis-rate-limiter.burstCapacity:是指令牌桶的容量,允许在一秒钟内完成的最大请求数,请该设置为0将阻止任何请求
相关推荐
瑶山12 小时前
Spring Cloud微服务搭建四、集成RocketMQ消息队列
java·spring cloud·微服务·rocketmq·dashboard
码字的字节13 小时前
Spring Cloud服务注册与发现(一):手把手搭建Eureka Server,详解高可用配置
spring·spring cloud·eureka
大厂资深架构师13 小时前
Spring Cloud Eureka在后端系统中的服务剔除策略
spring·spring cloud·ai·eureka
岁岁种桃花儿14 小时前
SpringCloud从入门到上天:Nacos做微服务注册中心(二)
java·spring cloud·微服务
SoleMotive.19 小时前
谢飞机爆笑面经:Java大厂3轮12问真题拆解(Redis穿透/Kafka分区/MCP Agent)
redis·spring cloud·kafka·java面试·mcp
MrSYJ21 小时前
Redis 做分布式 Session
后端·spring cloud·微服务
研究司马懿1 天前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
瑶山1 天前
Spring Cloud微服务搭建五、集成负载均衡,远程调用,熔断降级
spring cloud·微服务·负载均衡·远程调用·熔断降级
金牌归来发现妻女流落街头2 天前
【从SpringBoot到SpringCloud】
java·spring boot·spring cloud
Java后端的Ai之路2 天前
【Spring全家桶】-一文弄懂Spring Cloud Gateway
java·后端·spring cloud·gateway