SpringCloud入门(十):网关Gateway 之 限流(Greenwich.SR2)

简介

限流:在高并发系统中,往往需要在系统中限流,一方面是为了防止大量请求使服务器过载,导致服务的不可用,另一方面是为了防止网络攻击。

一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如 nginx 的 limit_conn 模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如 Guava 的 RateLimiter、nginx 的 limit_req 模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制 MQ 的消费速率。另外还可以根据网络连接数、网络流量、CPU 或内存负载等来限流

限流算法

计数器

简单的做法计数维护一个单位时间的 计数器,每次请求计数器加1,当单位时间内计数器累加到大于设定的阈值,则之后的请求都被拒绝,直到单位时间过去,再将计数器置为0。此算法有一个弊端,例如单位时间为1s,设定的阈值为100,在10ms的时候就已经有100个请求通过,那后面的990ms,就浪费掉了,所有的请求都不会被处理,这种现象称为突刺现象

常用的更平滑的限流算法有两种:漏桶算法令牌桶算法

漏桶算法

漏桶算法思路很简单,水(请求)先进入到漏桶中,漏桶以一定的速度出水(接口响应速率),当水流入的速度过大时(访问频率超过接口响应速率)会直接溢出(直接拒接请求),可以看到漏桶算法可以强行限制数据的传输速率。
在这里插入图片描述
这里有两个变量,一个时桶的大小,支持流量突发增多时可以存放多少水(burst),另一个是水桶漏洞的大小(rate),因为漏桶的漏出速度是固定的参数,即使网络中不存在资源冲突(没有发生阻塞),漏桶算法也不能使流突发(burst)到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。

令牌桶算法

令牌桶算法 和漏桶算法 效果一样但方向相反的算法,更加容易理解。随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。
在这里插入图片描述
令牌桶的另外一个好处是可以方便的改变速度。一旦需要提高速率,则按需提高放入桶中的令牌的速率。一般会定时(比如 100 毫秒)往桶中增加一定数量的令牌,有些变种算法则实时的计算应该增加的令牌的数量。

限流实现

在 Spring Cloud Gateway 上实现限流是个不错的选择,只需要编写一个过滤器就可以了。

Spring Cloud Gateway 已经内置了一个RequestRateLimiterGatewayFilterFactory,我们可以直接使用。

目前RequestRateLimiterGatewayFilterFactory的实现依赖于 Redis,所以我们还要引入spring-boot-starter-data-redis-reactive。

pom依赖

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

yml

spring:
  application:
    name: gateway-service
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    password: 123456
  cloud:
    gateway:
      discovery:
        locator:
          #是否与服务发现组件结合,通过serviceId转发到具体的实例。默认为false,设为true开始根据serviceId创建路由功能
          enabled: false
          #是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了)
          lower-case-service-id: true
      routes:
        #自定义 全局唯一路由ID
        - id: feign-service
          #uri以lb://开头(lb代表从注册中心获取服务),后面接的就是你需要转发到的服务名称
          uri: lb://FEIGN-SERVICE
          #谓词
          predicates:
            #匹配路由   http://ip:port/feign/**  的请求
            - Path=/feign/**
          #过滤器
          filters:
            #剥离请求路径 例如 http://ip:port/feign/FEIGN-SERVICE/hello   ==>  http://ip:port/FEIGN-SERVICE/hello
            - StripPrefix=1
            #熔断降级
            - name: Hystrix
              args:
                name: feignHystrixCommand
                fallbackUri: 'forward:/fallbackCommand'
            #限流
            - name: RequestRateLimiter
              args:
                # 使用SpEL名称引用Bean,与上面新建的RateLimiterConfig类中的bean的name相同
                key-resolver: '#{@ipKeyResolver}'
                # 每秒最大访问次数  令牌桶每秒填充平均速率
                redis-rate-limiter.replenishRate: 5
                # 令牌桶最大容量
                redis-rate-limiter.burstCapacity: 10

在上面的配置文件,配置了 redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:

  • burstCapacity:令牌桶总容量。
  • replenishRate:令牌桶每秒填充平均速率。
  • key-resolver:用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
IP限流

获取请求用户ip作为限流key

@Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }

测试网关是否做到了限流,使用 jmeter 测试工具,测试配置如下,100个线程,2秒内,循环2次,总共200个请求
在这里插入图片描述在这里插入图片描述
查看结果
在这里插入图片描述
可以看到只有部分请求通过,其它请求全部被拒接。想更直观的了解,可以修改burstCapacity、replenishRate来测试一下。

还可以使用用户限流、接口限流,如果同时定义多个bean,启动的时候会报错,可以使用@Primary或@Qualifier来解决

用户限流

用户限流,使用这种方式限流,请求路径中必须携带userId参数

@Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
    }
接口限流

获取请求地址的uri作为限流key。

@Bean
    KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }

在真实场景中,限流数的调整需要依赖配置中心,当网站做活动时,动态调整限流数,新服务上线时,通过配置中心做动态路由等。

吐槽一下csdn 3级一下竟然不可以自定义标签了,这是在歧视我们啊!!!

发布了34 篇原创文章 · 获赞 5 · 访问量 1704

猜你喜欢

转载自blog.csdn.net/zhengliangs/article/details/103561784