微服务网关组件 - Spring Cloud Gateway 扩展(二十五)

Spring Cloud Gateway的监控端点

说到监控,就应该能想到Spring Boot Actuator。而Spring Cloud Gateway基于Actuator提供了许多的监控端点。只需要在项目中添加spring-boot-starter-actuator依赖,并将 gateway 端点暴露,即可获得若干监控端点。配置示例:

management:
  endpoints:
    web:
      exposure:
        include: gateway  # 或者配置“*”暴露全部端点

Spring Cloud Gateway的监控端点如下表:

端点 请求方法 描述
globalfilters GET 展示所有的全局过滤器信息
routefilters GET 展示所有的过滤器工厂信息
refresh POST(无消息体) 清空路由缓存,即刷新路由信息
routes GET 展示所有的路由信息列表
routes/{id} GET 展示指定id的路由的信息
routes/{id} POST(有消息体) 新增一个路由
routes/{id} DELETE(无消息体) 删除一个路由

Gateway所有的监控端点都挂载在 /actuator/gateway 路径下,例如globalfilters端点的完整访问路径是 /actuator/gateway/globalfilters。该端点主要是查看Gateway启用了哪些全局过滤器以及它们的执行顺序(数字越小越优先执行)。所以当我们不知道Gateway启用了哪些全局过滤器,或者不知道这些全局过滤器的执行顺序,就可以访问该端点进行查看:

Spring Cloud Gateway - 扩展

同理,如果不知道Gateway启用了哪些过滤器工厂,则可以访问routefilters端点查看:

Spring Cloud Gateway - 扩展

若想知道Gateway里定义了哪些路由又不想查看配置文件的话,那么就可以通过routes端点查看所有的路由信息列表:

Spring Cloud Gateway - 扩展

如果出现自定义的路由配置不生效或行为与预期不符,那么可以通过routes/{id}端点查看该路由具体的详细信息:

Spring Cloud Gateway - 扩展

routes/{id}端点还可以用于动态添加路由,只需发送POST请求并定义一个消息体即可。消息体示例:

{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/test"
      }
    }
  ],
  "filters": [
    {
      "name": "AddRequestHeader",
      "args": {
        "_genkey_0": "X-Request-Foo",
        "_genkey_1": "Bar"
      }
    },
    {
      "name": "PreLog",
      "args": {
        "_genkey_0": "a",
        "_genkey_1": "b"
      }
    }
  ],
  "uri": "https://www.example.com",
  "order": 0
}

消息体其实是有规律的,你可以先在配置文件中配置一个路由规则,然后访问 routes 端点,route_definition字段里的内容就是消息体,如下:

Spring Cloud Gateway - 扩展

接下来我们实际测试一下,复制该消息体,然后稍微修改一下并进行发送,如下:


Spring Cloud Gateway - 扩展

路由添加成功后,访问 routes 端点,就可以看到新添加的路由:

Spring Cloud Gateway - 扩展

注:如果没有实时生效,使用 refresh 端点刷新一下路由信息即可

官方文档:


关于Gateway的调试、排错

1、Gateway的监控端点:

上一小节介绍了Gateway的监控端点,这些监控端点可以帮助我们分析全局过滤器、过滤器工厂、路由详情等

2、日志:

设置一些相关包的日志级别,打印更详细的日志信息,可按需将如下包的日志级别设置成 debugtrace

  • org.springframework.cloud.gateway
  • org.springframework.http.server.reactive
  • org.springframework.web.reactive
  • org.springframework.boot.autoconfigure.web
  • reactor.netty
  • redisratelimiter

配置示例:

logging:
  level:
    org.springframework.cloud.gateway: trace

3、Wiretap Logger【需Greenwich SR3及更高版本才会支持】:

Reactor Netty的 HttpClient 以及 HttpServer 可启用 Wiretap。需将 reactor.netty 包设置成 debugtrace ,然后在配置文件中添加如下配置:

  • spring.cloud.gateway.httpserver.wiretap=true:开启 HttpServer 的Wiretap
  • spring.cloud.gateway.httpclient.wiretap=true:开启 HttpClient 的Wiretap

wiretap其实是Reactor Netty的概念,用于打印对端之间的流量详情,相关文档:


过滤器执行顺序

我们都知道全局过滤器使用@Order 注解或实现 Ordered 接口来配置一个决定执行顺序的数字,该数字越小的过滤器越靠前执行。

但是在路由规则上所配置的过滤器工厂并没有配置类似Order之类的东西,那么是如何决定执行顺序的呢?其实,过滤器工厂默认也会被设置一个Order,该Order按配置顺序从1开始递增,也是Order越小越靠前执行。如下:

routes:
  - id: test-route
    uri: lb://user-center
    predicates:
      - TimeBetween=上午9:00,下午5:00
    filters:
      # 按配置顺序从1开始递增
      - AddRequestHeader=Y-Header, Bar    # Order为1
      - AddResponseHeader=X-Header, Bar   # Order为2
      - PreLog=testName,testValue         # Order为3

使用default-filters配置的默认过滤器也是同理,但如果配置了默认过滤器,则会先执行相同Order的默认过滤器:

default-filters: 
  - AddRequestHeader=Y-Foo, Bar    # Order为1
  - AddResponseHeader=X-Foo, Bar   # Order为2
routes:
  - id: test-route
    uri: lb://user-center
    predicates:
      - TimeBetween=上午9:00,下午5:00
    filters:
      # 按配置顺序从1开始递增
      - AddRequestHeader=Y-Header, Bar    # Order为1
      - AddResponseHeader=X-Header, Bar   # Order为2
      - PreLog=testName,testValue         # Order为3

如需自行控制过滤器工厂的Order,可返回OrderedGatewayFilter,如下示例:

@Slf4j
@Component
public class PreLogGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {

    @Override
    public OrderedGatewayFilter apply(NameValueConfig config) {
        return new OrderedGatewayFilter((exchange, chain) -> {
            ...
            return chain.filter(exchange);
        }, -1);
    }
}

核心代码:

  • org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters:为过滤器设置了Order 数值,从1开始
  • org.springframework.cloud.gateway.route. RouteDefinitionRouteLocator#getFilters:加载默认过滤器 & 路由过滤器,并对过滤器做了排序
  • org.springframework.cloud.gateway.handler.FilteringWebH .andler#handle:构建过滤器链并执行

Spring Cloud Gateway跨域配置

Gateway支持CORS相关配置,可以通过不同的URL规则匹配不同的CORS策略。配置示例:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "http://docs.spring.io"
            allowedMethods:
            - GET

除此之外,还可以通过自定义过滤器来解决跨域问题,具体代码如下:

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 corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");

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

        return new CorsWebFilter(source);
    }
}

Spring Cloud Gateway限流相关

在高并发的系统中,限流往往是一个绕不开的话题,我们都知道网关是流量的入口,所以在网关上做限流也是理所当然的。Spring Cloud Gateway内置了一个过滤器工厂,用于提供限流功能,这个过滤器工厂就是是RequestRateLimiterGatewayFilterFactory,该过滤器工厂基于令牌桶算法实现限流功能。

目前,该过滤器工厂默认使用 RedisRateLimiter 作为限速器,需要依赖Redis来存储限流配置,以及统计数据等。当然你也可以实现自己的RateLimiter,只需实现 org.springframework.cloud.gateway.filter.ratelimit.RateLimiter 接口,或者继承 org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter抽象类

Tips:

Redis Rate Limiter的实现基于这篇文章:Scaling your API with rate limiters
Spring官方引用的令牌桶算法文章:Token bucket

关于令牌桶之类的限流算法可以参考另一篇文章,这里就不过多赘述了:


动手实践

1、添加Redis依赖:

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

2、添加如下配置:

spring:
  cloud:
    gateway:
      routes:
        - id: user-center
          uri: lb://user-center
          predicates:
            - Path=/user-center/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                # 令牌桶每秒填充平均速率
                redis-rate-limiter.replenishRate: 1
                # 令牌桶的上限
                redis-rate-limiter.burstCapacity: 2
                # 使用SpEL表达式从Spring容器中获取Bean对象
                key-resolver: "#{@pathKeyResolver}"
  # redis相关              
  redis:
    host: 127.0.0.1
    port: 6379

3、编写一个KeyResolver,用于定义针对什么进行限流。例如按照访问路径限流,就写一个针对访问路径的KeyResolver;按照请求参数限流,那就写一个针对请求参数的KeyResolver,以此类推。这里我们按照访问路径限流,具体实现代码如下:

@Configuration
public class RaConfiguration {

    /**
     * 按照Path限流
     *
     * @return key
     */
    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest()
                        // 获取path
                        .getPath()
                        .toString()
        );
    }
}

从代码的实现不难看出,实际就只是返回了一个访问路径,这样限流规则就会作用到访问路径上。例如访问:http://${GATEWAY_URL}/users/1,对于这个路径,它的redis-rate-limiter.replenishRate = 1redis-rate-limiter.burstCapacity = 2

访问:http://${GATEWAY_URL}/shares/1,对于这个路径,它的redis-rate-limiter.replenishRate = 1redis-rate-limiter.burstCapacity = 2;以此类推......

测试

接下来进行一个简单的测试,看看限流是否起作用了。持续频繁访问某个路径,当令牌桶的令牌被消耗完了,就会返回 429 这个HTTP状态码。如下:

Spring Cloud Gateway - 扩展

然后迅速查看Redis中存储的key,会发现其格式如下:


Spring Cloud Gateway - 扩展

从key的格式可以看出来,实际上 KeyResolver 的目的就是用来获取一个请求的唯一标识(这个标识可以是访问路径,可以是某个请求参数,总之就是可以从这个请求里获取出来的东西),并用其生成key以及解析key,以此实现针对性的限流。

拓展

如果请求会携带一个名为user的参数,其值为用户名,那么我们就可以通过这个请求参数来实现针对用户的限流。如下:

@Bean
public KeyResolver userKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest()
            .getQueryParams()
            .getFirst("user")
    );
}

同理,我们还可以针对请求的来源IP进行限流。如下:

@Bean
public KeyResolver ipKeyResolver() {
    return exchange -> Mono.just(
      exchange.getRequest()
          .getHeaders()
          .getFirst("X-Forwarded-For")
    );
}

原文:https://blog.51cto.com/zero01/2430532

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容