Spring Cloud Gateway学习

API网关

系统的统一入口,封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,例如认证鉴权监控路由转发等。

网关 = 路由转发 + 过滤器(编写额外功能)

作用就是把各个服务对外提供的API汇集起来,让外界看起来是一个统一的接口。

稳定与安全

  • 全局性流控
  • 日志统计
  • 防止SQL注入
  • 防止Web攻击
  • 屏蔽工具扫描
  • 黑白IP名单
  • 证书/加解密处理

提供更好的服务

  • 服务级别流控
  • 服务降级与熔断
  • 路由与负载均衡、灰度策略
  • 服务过滤、聚合与发现
  • 权限验证与用户等级策略
  • 业务规则与参数校验
  • 多级缓存策略

什么是Spring Cloud Gateway?

网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。

Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,提供了微服务网关功能。定位于取代Netflix Zuul。相比 Zuul 来说,Spring Cloud Gateway提供更优秀的性能,更强大的有功能。

Spring Cloud Gateway 是由 WebFlux+Netty+Reactor实现的响应式的API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。

Spring Cloud Gateway 旨在为微服务架构提供一种简单且有效的API路由的管理方式,并基于Filter 的方式提供网关的基本功能,例如说安全认证、监控、限流等等。

Spring Cloud Gateway 功能特性:

  • 动态路由:能够匹配任何请求属性
  • 支持路径重写
  • 继承Spring Cloud服务发现功能(Nacos、Eureka)
  • 可以集成流控降级功能(Sentinel、Hystrix)
  • 可以对路由指定易于编写的Predicate(断言)和Filter(过滤器)

Spring Cloud Gateway主要包含:

Route:路由,一个路由包含ID、URI、Predicate(附加条件和内容,如当满足某种条件再进行路由转发)集合、Filter(在Gateway运行过程中Filter负责在代理服务『之前』或『之后』去做一些事情 )集合

Spring Cloud Gateway学习_第1张图片

网关客户端访问Gateway网关,Gateway中Handler Mapping对请求URL进行处理。处理完成后交换Web Handler,Web Handler会被Filter进行过滤。Filter中前半部分代码是处理请求的代码。处理完成后调用真实被代理的服务。被代理服务响应结果,结果会被Filter中后半部分代码进行操作,操作完成后把结果返回给Web Handler,再返回给Handler Mapping,最终响应给客户端。

核心概念

  • 路由(route)

路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。Route规则参考:org.springframework.cloud.gateway.route.RouteDefinition

  • 断言(predicates)

Java8中的断言函数,Spring Cloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。

  • 过滤器(Filter)

SpringCloud Gateway中的filter分为Gateway Filler和Global Filter。Filter可以对请求和响应进行处理。

示例:

先引入依赖:

        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>

application.yml配置:

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    # Gateway配置
    gateway:
      # 路由规则
      routes:
        - id: order_route # 路由的唯一标识,此示例理由到订单服务
          uri: http://127.0.0.1:8082 # 需要转发的地址
          # 断言规则,用于路由规则的匹配
          predicates:
            - Path=/order-service/** # http://localhost:8088/order-service/order 会路由到 http://127.0.0.1:8082/order-service/order
          filters:
            - StripPrefix=1 # 转发之前去掉第一层路径,去掉后变成http://127.0.0.1:8082/order

集成Nacos注册中心:

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>

修改配置文件

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    # Gateway配置
    gateway:
      # 路由规则
      routes:
        - id: order_route # 路由的唯一标识,此示例理由到订单服务
          uri: lb://order-service # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡器
          # 断言规则,用于路由规则的匹配
          predicates:
            - Path=/order-service/** # http://localhost:8088/order-service/order 会路由到 http://127.0.0.1:8082/order-service/order
          filters:
            - StripPrefix=1 # 转发之前去掉第一层路径,去掉后变成http://127.0.0.1:8082/order

或使用:

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    # Gateway配置
    gateway:
      discovery:
        locator:
          enabled: true # 是否启动自动识别nacos服务,约定大于配置
          lower-case-service-id: true # 把服务名称转换为小写,Eureka中默认都是大写

路由断言工厂(Route Predicate Factories)配置

当请求gateway的时候,使用断言对请求进行匹配,如果匹配成功就路由转发,如果匹配失败就返回404。

内置路由断言工厂

断言Predicate:在Spring Cloud Gateway中断言实现org.springframework.cloud.gateway.handler.predicate.GatewayPredicate接口。其中类名符合XXXRoutePredicateFactory,其中XXX就是在配置文件中断言名称。Path=/server/book/** 实际使用的就是PathRoutePredicateFactory。

参考示例:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

  • 基于时间类型的断言工厂

BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期。

AfterRoutePredicateFactory:设置在指定时间点之后。

BetweenRoutePredicateFactory:请求时必须在设定的时间范围内容,才进行路由转发。

spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: https://example.org
        predicates:
        - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
  • 基于远程地址的断言工厂

RemoteAddrRoutePredicateFactory:设置允许访问的客户端地址。接收一个IP地址端,判断请求主机地址是否在地址段中。

- RemoteAddr=192.168.1.1/24
  • 基于Cookie的断言工厂

CookieRoutePredicateFactory:接收两个参数,cookie名字和一个正则表达式,判断请求cookie是否具有给定名称且值与正则表达式匹配。设置请求中包含指定Cookie名和满足特定正则要求的值(Cookie必须有两个值,第一个Cookie包含的参数名,第二个表示参数对应的值(可以正则表达式))。

- Cookie=chocolate, ch.p
  • 基于Header的断言工厂

HeaderRoutePredicateFactory:接收两个参数,标题名称和正确表达式。判断请求Header是否具有给定名称且值与正则表达式匹配。设置请求头中必须包含的内容(参数、参数值)。

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

HostRoutePredicateFactory:接收一个参数,主机名模式,判断请求的Host是否满足匹配规则。设置匹配请求参数中Host参数的值。(支持?匹配一个字符,*匹配0个或多个字符,**匹配0个或多个目录)。

- Host=**.somehost.org,**.anotherhost.org
  • 基于Method请求方法的断言工厂

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。设置允许的请求方式。

- Method=GET,POST
  • 基于Path请求路径的断言工厂

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

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: https://example.org
        predicates:
        - Path=/red/{segment},/blue/{segment}
  • 基于Query请求参数的断言工厂

QueryRoutePredicateFactory:接收两个参数,请求param和正则表达式,判断请求参数是否具有指定名称且值与正则表达式匹配。设置必须包含的参数名。

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=green
  • 基于路由权重的断言工厂

WeightRoutePredicateFactory:接收一个[组名,权重],然后对于同一个组内的路由按照权重转发。设置负载均衡中权重。同一组中URI进行负载均衡。(语法:Weight=组名,负责均衡权重),默认条件是标准轮询。分组名称必须相同。

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

自定义路由断言工厂

自定义路由断言工厂需要继承AbstractRoutePredicateFactory类,重写apply方法的逻辑,在apply方法中可以通过exchange.getRequest()拿到ServletHttpRequest对象,从而可以获取到请求的参数、请求方式、请求头等信息。

  1. 必须Spring组件Bean
  2. 类必须加上RoutePredicateFactory作为结尾
  3. 必须继承AbstractRoutePredicateFactory类
  4. 必须在类中实现一个静态内部类,声明属性来接收配置文件中对应的断言信息
  5. 需要结合shortcutFieldOrder进行绑定
  6. 通过apply方法进行逻辑判断,true就是匹配成功,false表示匹配失败
  7. 泛型为声明的静态内部类
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

@Slf4j
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
    // 构造函数
    public CheckAuthRoutePredicateFactory() {
        super(CheckAuthRoutePredicateFactory.Config.class);
    }

    // 接收的参数需要结合shortcutFieldOrder进行绑定
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("name");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                String name = config.getName();
                if (Objects.equals(name, "zhangsan")) {
                    return true;
                }
                return false;
            }
        };
    }

    // 用于接收配置文件中断言的信息
    @Validated
    public static class Config {
        private String name;

        public String getName() {
            return name;
        }

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

过滤器工厂(GatewayFilter Factories)配置

在路由转发到代理服务之前和代理服务返回结果之后额外做的事情。Filter执行了说明断言条件通过了。

在Spring Cloud Gateway的路由中Filter分为:

内置Filter工厂,都是org.springframework.cloud.gateway.filter.GatewayFilter实现类

  • 自定义GlobalFilter,所有的路由都会运行
  • 自定义GatewayFilter,在特定的路由上运行

Gateway内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加、剔除响应头,添加、去除参数等。

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

内置Filter工厂

过滤器工厂 作用 参数
AddRequestHeader 为原始请求添加Header Header的名称及值,参数和值之间使用逗号分隔
AddRequestParameter 为原始请求添加请求参数 添加请求表单参数,多个参数需要有多个过滤器
AddResponseHeader 添加响应头 参数和值之间使用逗号分隔
DedupeResponseHeader 剔除响应头中重复的值 DedupeResponseHeader=响应头参数,策略(RETAIN_FIRST:默认值,保留第一个;RETAIN_LAST:保留最后一个;RETAIN_UNIQUE:保留唯一的)
CircuitBreaker 实现熔断时使用 支持Resilience4J
FallbackHeaders 为FallbackUri的请求头中添加具体的异常信息,降级的异常信息 Header的名称
PrefixPath 为满足条件的原始请求路径添加前缀 前缀路径
PreserveHostHeader 为请求添加一个preserveHostHeader=true的属性,路油过滤器会检查该属性以决定是否要发送原始Host
RequestRateLimiter 限流过滤器,对于请求限流,限流算法为令牌桶 参考文档
RedirectTo 将原始请求重定向到指定的URL 有两个参数,status(http状态码)和url(重定向url)。其中status应该是300系列重定向状态码
RemoveHopByHopHeadersFilter 为原始请求删除IETF组织规定的一系列Header 默认就会启用,可以通过配置指定近删除哪些Header
RemoveRequestHeader 为原始请求删除某个Header Header名称
RemoveResponseHeader 为原始响应删除某个Header Header名称
RewritePath 重写原始的请求路径 原始路径正则表达式以及重写后路径的正则表达式
RewriteResponseHeader 重写原始响应中的某个Header Header名称,值的正则表达式,重写后的值
SaveSession 在转发请求之前,强制执行WebSession::save操作
SecureHeaders 为原始响应添加一系列起安全作用的响应头 无,支持修改这些安全响应头的值
SetPath 修改原始的请求路径,功能与StripPrefix类似,语法更贴近restful(SetPath=/{segment}) 修改后的路径
SetResponseHeader 修改原始响应中某个Header的值 Header名称,修改后的值
SetStatus 修改原始响应的状态码 Http状态码,可以是数字,也可以是字符串
StripPrefix 用于截断原始请求的路径,跳过路由URI中前几段后发送给下游 使用数字表示要截断的路径的数量,(StripPrefix=1,跳过第一段URI )
Retry 针对不同的响应进行重试 retries、statuses、methods、senes
RequestSize 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回413 Payload Too Large 请求包大小,单位为字节,默认值为5M
ModifyRequestBody 在转发请求之前修改原始请求体内容 修改后的请求体内容
ModifyResponseBody 修改原始响应体的内容 修改后的响应体内容

AddRequestHeader示例,在order-service的Controller中添加hello方法

    /**
     * 通过@RequestHeader注解来接收请求头参数
     *
     * @param color X-Request-color请求头参数的值
     * @return X-Request-color请求头参数的值
     */
    @GetMapping("/hello")
    public String hello(@RequestHeader("X-Request-color") String color) {
        log.info("请求头X-Request-color的值为:{}", color);
        return color;
    }

网关配置如下:

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    # Gateway配置
    gateway:
      # 路由规则
      routes:
        - id: order_route # 路由的唯一标识,此示例理由到订单服务
          uri: lb://order-service # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡器
          # 断言规则,用于路由规则的匹配
          predicates:
            - Path=/**
          filters:
            - AddRequestHeader=X-Request-color, red # 添加请求头

PrefixPath示例:

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    # Gateway配置
    gateway:
      # 路由规则
      routes:
        - id: order_route # 路由的唯一标识,此示例理由到订单服务
          uri: lb://order-service # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡器
          # 断言规则,用于路由规则的匹配
          predicates:
            - Path=/**
          filters:
            - AddRequestHeader=X-Request-color, red # 添加请求头
            - PrefixPath=/myorder # 添加前缀,对应微服务需要配置context-path

自定义Filter工厂

继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给Spring管理。

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    # Gateway配置
    gateway:
      # 路由规则
      routes:
        - id: order_route # 路由的唯一标识,此示例理由到订单服务
          uri: lb://order-service # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡器
          # 断言规则,用于路由规则的匹配
          predicates:
            - Path=/**
          filters:
            - PrefixPath=/myorder
            - CheckAuth=zhangsan

自定义CheckAuthGatewayFilterFactory:

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.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

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

@Component
public class CheckAuthGatewayFilterFactory
        extends AbstractGatewayFilterFactory<CheckAuthGatewayFilterFactory.Config> {

    public CheckAuthGatewayFilterFactory() {
        super(CheckAuthGatewayFilterFactory.Config.class);
    }

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

    /**
     * 最好使用断言,name必须为zhangsan才能正常访问,如果没有带参数也可以正常访问
     */
    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // 获取name参数
                String name = exchange.getRequest().getQueryParams().getFirst("name");
                if (StringUtils.hasText(name)) {
                    if (Objects.equals(name, config.getName())) {
                        // 正常访问
                        return chain.filter(exchange);
                    } else {
                        // 如果不等于zhangsan就失败返回404
                        exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
                        return exchange.getResponse().setComplete();
                    }
                }
                // 正常请求
                return chain.filter(exchange);
            }
        };
    }

    @Validated
    public static class Config {
        private String name;

        public String getName() {
            return name;
        }

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

}

全局过滤器(Global Filters)配置

Spring Cloud Gateway学习_第2张图片

局部过滤器和全局过滤器:

局部过滤器针对某个路由生效,需要在路由中配置。全局过滤器针对所有路由生效,一旦定义就会投入使用。

自定义全局过滤器:

需要实现GlobalFilter接口。

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

/**
 * 打印记录请求路径
 */
@Component
@Slf4j
public class LogFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("请求路径:{}", exchange.getRequest().getPath().value());
        return chain.filter(exchange);
    }
}

Reactor Netty 访问日志

要启用Reactor Netty 访问日志,请设置 -Dreactor.netty.http.server.accessLogEnabled=true 它必须是Java系统属性(环境变量),而不是Spring Boot 属性

在logback.xml中配置到打印文件

<appender name="accessLog" class="ch.qos.logback.core.FileAppender">
    <file>access_log.logfile>
    <encoder>
        <pattern>%msg%npattern>
    encoder>
appender>
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="accessLog"/>
appender>
<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
    <appender-ref ref="async"/>
logger>

Gateway跨域配置(CORS Configuration)

参考文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration

解决CORS policy:no 'Access-Control-Allow-Origin' header is present on the requested resource问题。

通过yml配置的方式:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 允许跨域访问的资源
            allowedOrigins: "https://docs.spring.io" # 跨域允许的来源
            allowedMethods:
            - GET
            - POST
            - PUT
            - DELETE

通过Java配置方式

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        // 允许的Method
        config.addAllowedMethod("*");
        // 允许的来源
        config.addAllowedOrigin("*");
        // 允许的请求头参数
        config.addAllowedHeader("*");
        // WebFlux,允许访问的资源
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

Gateway限流降级

常见的限流算法

  • 计数器算法

以QPS(每秒查询率)为100举例:从第一个请求开始计时,当达到100以后,其它的请求都拒绝。如果1秒钟前200ms的请求数量已经达到了100,后面800ms中多少次的请求都被拒绝了,这种情况称为『突刺现象』。

  • 漏桶算法——可以解决突刺现象

和生活中漏桶一样,有一个水桶,下面有一个『漏眼』往出漏水,不管桶里有多少水,漏水的速率都是一样的。但是既然是一个桶,桶里装的水都是有上限的。当达到了上限,新进来的水就装不了了(主要出现在突然倒进来大量水的情况)。

  • 令牌桶算法

令牌桶算法可以说是对漏桶算法的一种改进。

在桶中放令牌,请求获取令牌后才能继续执行。如果桶中没有令牌,请求可以选择进行等待或者直接拒绝。由于桶中令牌是按照一定速率放置的,所以可以一定程度解决突发访问。如果桶中令牌最多有100个,QPS最大为100。

Gateway整合Sentinel实现限流(推荐)

网关作为内部系统外的一层屏障,对内起到一定的保护作用,限流便是其中之一。网关层的限流可以简单地针对不同路由进行限流,也可以针对业务的接口进行限流,或者根据接口的特征分组限流。

参考文档:https://sentinelguard.io/zh-cn/docs/api-gateway-flow-control.html

添加依赖:

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
        dependency>

添加配置:

    # 配置Sentinel
    sentinel:
      transport:
        dashboard: 127.0.0.1:9000

使用 Gateway 中的RequestRateLimiter实现限流

使用 Gateway 中的RequestRateLimiter实现限流,RequestRateLimiter是基于Redis和Lua脚本实现的令牌桶算法。

使用Gateway实现服务降级

Spring Cloud Gateway可以使用Hystrix实现服务降级功能。

当Gateway进行路由转发时,如果发现下游服务连接超时允许进行服务降级。

实现原理:当连接超时时,使用Gateway自己的一个降级接口返回托底数据,保证程序继续运行。

你可能感兴趣的:(Spring学习整理,SpringCloud,Gateway,Sentinel)