一、常见限流算法

1)计数器

计数器限流算法是最简单的一种限流实现方式。其本质是通过维护一个单位时间内的计数器,每次请求计数器加1,当单位时间内计数器累加到大于设定的阈值,则之后的请求都被拒绝,直到单位时间已经过去,再将计数器重置为零

 

 

 2)漏桶算法

漏桶算法可以很好地限制容量池的大小,从而防止流量暴增。漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。 在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。

 

 

 

为了更好的控制流量,漏桶算法需要通过两个变量进行控制:一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。

3) 令牌桶算法

令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。

 

 

 二、基于filter的限流

SpringCloudGateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂RequestRateLimiterGatewayFilterFactory 实现。在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。

1)环境搭建(导入redis的依赖)

首先在工程的pom文件中引入gateway的起步依赖和redis的reactive依赖,代码如下:

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

(2) 修改application.yml配置文件

spring:
  application:
    name: api-gateway #指定服务名
  cloud:
    gateway:
      routes:
      - id: order-service
        uri: lb://shop-service-order
        filters:
        - RewritePath=/order-service/(?<segment>.*), /$\{segment}
        - name: RequestRateLimiter
          args:
            # 使用SpEL从容器中获取对象
            key-resolver: '#{@pathKeyResolver}'
            # 令牌桶每秒填充平均速率
            redis-rate-limiter.replenishRate: 1
            # 令牌桶的总容量
            redis-rate-limiter.burstCapacity: 3
    redis:
     host: localhost
     port: 6379

在application.yml 中添加了redis的信息,并配置了RequestRateLimiter的限流过滤器:
1、burstCapacity,令牌桶总容量。
2、replenishRate,令牌桶每秒填充平均速率。
3、key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

(3) 配置KeyResolver

为了达到不同的限流效果和规则,可以通过实现 KeyResolver 接口,定义不同请求类型的限流键。

@Configuration
public class KeyResolverConfiguration {
    /**
     * 编写基于请求路径的限流规则
     *  //abc
     *  //基于请求ip 127.0.0.1
     *  //基于参数
     */
    //@Bean
    public KeyResolver pathKeyResolver() {
        //自定义的KeyResolver
        return new KeyResolver() {
            /**
             * ServerWebExchange :上下文参数
             */
            public Mono<String> resolve(ServerWebExchange exchange) {
                return Mono.just( exchange.getRequest().getPath().toString());
            }
        };
    }

    /**
     * 基于请求参数的限流
     *
     *  请求 abc ? userId=1
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getQueryParams().getFirst("userId")
                //exchange.getRequest().getHeaders().getFirst("X-Forwarded-For") 基于请求ip的限流
        );
    }
}

通过reids的MONITOR可以监听redis的执行过程。这时候Redis中会有对应的数据:
大括号中就是我们的限流Key,这边是IP,本地的就是localhost
timestamp:存储的是当前时间的秒数,也就是System.currentTimeMillis() / 1000或者Instant.now().getEpochSecond()
tokens:存储的是当前这秒钟的对应的可用的令牌数量
Spring Cloud Gateway目前提供的限流还是相对比较简单的,在实际中我们的限流策略会有很多种情
况,比如:

  • 对不同接口的限流
  • 被限流后的友好提示

这些可以通过自定义RedisRateLimiter来实现自己的限流策略