小白学习spring-cloud(十一): Spring Cloud Gateway限流实战

314 阅读4分钟

前言

已经通过案例实现了通过 Spring Cloud Gateway 网关调用服务 API,并已经了解了 Gateway 网关的几个核心概念:routespredicatesfilters

这几个核心概念串起来就是 Gateway 的执行流程:客户端发请求,通过断言 predicates 进行匹配,若匹配上了则请求就会被发送到网关处理程序,并执行特定的请求过滤器链 fliters

Spring Cloud Gateway 提供了多种断言 predicate 工厂和过滤器 filter 工厂,也可以自定义断言工厂和过滤器工厂。

RequestRateLimiter GatewayFilter 工厂使用实现 RateLimiter 的限流器来确定当前请求是否被限流。如果被限流了,则默认返回 HTTP 429 - Too Many Requests 状态。

RequestRateLimiter 网关过滤器工厂采用可选的 keyResolver 参数和特定于速率限制器的参数。

KeyResolver 在源码中的定义:

public interface KeyResolver {
	Mono<String> resolve(ServerWebExchange exchange);
}

我们可以通过 KeyResolver 来指定限流的 key ,比如可以根据用户做限流,也可以根据 IP 来做限流,或者根据接口进行限流。

基于 Redis 的限流器

默认的限流器是基于redis实现的,限流算法是大家熟悉的令牌桶(Token Bucket Algorithm),关于令牌捅的原理就不在此展开了,聪明的您看一眼下图应该就懂了:装令牌的桶容量有限,例如最多20个,令牌进入桶的速度恒定(注意,这里是和漏桶算法的区别),例如每秒10个,底部每个请求能拿到令牌才会被处理: image.png

基于 Redis 的限流,需要引入 spring-boot-starter-data-redis-reactive 依赖:

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

要实现限流,需要配置 Redis 连接以及在配置网关路由的时候添加 RequestRateLimiter 过滤器:

spring:
  application:
    name: circuitbreaker-gateway
  # redis配置
  redis:
    host: localhost
    port: 6379

  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://localhost:9001
          predicates:
            - Path=/nacos/**
          filters:
            - name: RequestRateLimiter
              args:
                # 令牌入桶的速度为每秒100个,相当于QPS
                redis-rate-limiter.replenishRate: 100
                # 桶内能装200个令牌,相当于峰值,要注意的是:第一秒从桶内能去200个,但是第二秒只能取到100个了,因为入桶速度是每秒100个
                redis-rate-limiter.burstCapacity: 200
                # 每个请求需要的令牌数
                redis-rate-limiter.requestedTokens: 1

这里配置的是按照用户限流,其中参数 key-resolver: "#{@userKeyResolver}" 是一个 KeyResolver bean

package com.example.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

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

#{@userKeyResolver} 是一个引用名为 userKeyResolver 的 bean 的 SpEL 表达式 。

其他配置项的含义:

  • filter 的名称 name 必须为 RequestRateLimiter
  • redis-rate-limiter.replenishRate :允许用户每秒处理的请求数。设置的数值就代表 每秒向令牌桶添加多少个令牌
  • redis-rate-limiter.burstCapacity :令牌桶的容量,即允许在 1 秒内完成的最大请求数。设置为 0 则表示拒绝所有请求。

准备工作

  • 为了更好的演示Gateway的效果,在服务提供者nacos-provider的代码(NacosController.java)中新增一个web接口,可以接受一个入参:
@GetMapping("/userinfo")
public String userInfo(@RequestParam("username") String username) {
    return  "hello " + username + ", " + dateStr();
}

测试

下面开始测试,根据配置的路由,我们通过网关访问 http://localhost:10013/nacos/userinfoimage.png 直接报错了,看一下异常信息: image.png 根据 userKeyResolver 的配置,我们必须传入username 参数才能正常访问: image.png 访问接口测试后, Redis 中会有对应的数据: image.png 大括号中的就是限流的key,这里是username,就是我们访问接口时传入的参数值。Redis 的 key 中还有两个定义:

  • tokens:代表当前秒对应的可用的令牌数量;
  • timestamp:当前时间的秒数。

再来通过 JMeter 模拟一下同时有大量请求的情况,当请求数量超过令牌桶的容量的时候,将会限流。 1.  设置每秒有 100 个请求过来: image.png 2.  设置通过网关请求的 url,注意要带上 username 参数,这是根据用户限流的: image.png 3.  执行,观察结果树: image.png

image.png

当令牌桶容量满了的时候,就不允许其他请求进来了,将返回 Too Many Requests ,限流生效。