4Gateway 限流 · SpringCloud微服务实战 · 看云

4. Gateway 限流

导航

前面在将Zuul 限流的时候已将介绍了一遍限流的常用方法和实现方案,这里简单再带过一遍。巩固一下记忆

举个例子,为什么每年的双11凌晨淘宝抢购的时候,有时会返回一个类似“当前拥挤”的提示。其实这是淘宝为了保护自己的服务而做的限流,防止应用挂掉后产生更严重的后果。
再比如,一些在北上广等上班的同学早上乘地铁的时候7点半到8点半也会被限流,正常会开三个门的通道,这段时间只会开通一个,排队通过,这是缓解地铁站台及列车的压力。
常见的限流方式,比如Hystrix适用线程池隔离,超过线程池的负载,走熔断的逻辑。在一般应用服务器中,比如tomcat容器也是通过限制它的线程数来控制并发的;常见的限流纬度有比如通过Ip来限流、通过uri来限流、通过用户访问频次来限流。
一般限流都是在网关这一层做,比如Nginx、Openresty、kong、Zuul、Spring Cloud Gateway等。
分布式系统架构的利器:缓存、负载、限流、降级

本节代码地址


4.1 常见的限流算法

4.1.1 计数器

简单的做法是维护一个单位时间内的 计数器,每次请求计数器加1,当单位时间内计数器累加到大于设定的阈值,则之后的请求都被拒绝,直到单位时间已经过去,再将 计数器 重置为零。此方式有个弊端:如果在单位时间1s内允许100个请求,在10ms已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象”。

常用的更平滑的限流算法有两种:漏桶算法令牌桶算法。下面介绍下二者。

4.1.2 漏桶算法

漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。
d8647ca5646e1fc1a009235b0b2127c2_MD5.webp

可见这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。

4.1.3 令牌桶算法

令牌桶算法 和漏桶算法 效果一样但方向相反的算法,更加容易理解。随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。

79e8f1c5e483db3de5c249867acbced9_MD5.webp

令牌桶的另外一个好处是可以方便的改变速度。一旦需要提高速率,则按需提高放入桶中的令牌的速率。一般会定时(比如 100 毫秒)往桶中增加一定数量的令牌,有些变种算法则实时的计算应该增加的令牌的数量。

4.2 Spring Cloud Gateway 限流实现

Spring Cloud Gateway 上实现限流是个不错的选择,只需要编写一个过滤器就可以了。有了前边过滤器的基础,写起来很轻松。
Spring Cloud Gateway 已经内置了一个RequestRateLimiterGatewayFilterFactory,我们可以直接使用。

目前RequestRateLimiterGatewayFilterFactory的实现依赖于 RedisRedis的实现基于Stripe的工作。它需要使用spring-boot-starter-data-redis-reactive Spring Boot启动器。

使用的算法是令牌桶算法

限流所使用的lua脚本见下面的截图
5b9d32458446be57f11062279e64c41a_MD5.webp

4.2.1 新建项目

为了演示方便,这里我们单独新建一个项目,用于演示限流、重试、FallBack、异常处理的例子,这里我们先介绍限流。
5de3dfd71d7b07930c3b7e619d72c099_MD5.webp

4.2.2 maven 配置

在pom文件中加入限流所需要的starter包

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

4.2.3 应用配置

这里还是使用多profile的方式来设置限流的处理,设置令牌桶总容量(burstCapacity)为1 、令牌桶每秒填充平均速率(replenishRate)为1,并且需要设置用于限流的键的解析器的 Bean 对象的名字及redis配置信息。

server:
  port: 8711
spring:
  application:
    name: fw-gateways-gateway
  profiles:
    active: rate_limiter_route
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
---
spring:
  cloud:
    gateway:
      routes:
        - id: rate_limiter_route
          uri: lb://fw-cloud-ribbon-server
          predicates:
            - After=2020-01-08T18:30:11.965+08:00[Asia/Shanghai]
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1
                key-resolver: "#{@userKeyResolver}"
  redis:
    host: ${redis.host}
    password: ${redis.pwd}
    port: 6379
  profiles: rate_limiter_route
---

**
限流配置描述**

  • filter 名称必须是 RequestRateLimiter
  • redis-rate-limiter.replenishRate:允许用户每秒处理多少个请求
  • redis-rate-limiter.burstCapacity:令牌桶的容量,允许在一秒钟内完成的最大请求数
  • key-resolver:使用 SpEL 按名称引用 bean

4.2.4 限流Bean 配置

下面的配置给出了三种,分别是基于参数、基于ip、基于请求路径。用的时候只能用一个。


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




      






}

4.2.5创建启动类

@EnableDiscoveryClient
@SpringBootApplication
public class FwGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(FwGatewayApplication.class, args);
    }

}

4.3 测试效果

Postman 输入localhost:8711/user/1?fwcloud=123456快速点击多次send
99a83b31e9c83a334deab8edd444d426_MD5.png