SpringCloud Alibaba学习(十二)——GateWay网关限流

为什么要限流

项目上线后,由于每个服务器的处理效率和消耗资源并不是无限的。当指定时间段内请求过高,会导致服务资源吃不消,造成雪崩等问题。

常见的限流算法

技术器算法

做限流 (Rate Limiting/Throttling) 的时候,除了简单的控制并发,如果要准确的控制 TPS,简单的做法是维护一个单位时间内的 Counter,如判断单位时间已经过去,则将 Counter 重置零。

在这里插入图片描述
但该算法有个很致命的问题:

此做法被认为没有很好的处理单位时间的边界。

比如在前一秒的最后一秒里下一秒的第一秒都触发了最大的请求数,也就是在两秒内发生了两倍的 TPS

在这里插入图片描述
其次,最后1s和最初1s请求吃满,在指定的时间段内,其他区间时间上则存在了资源浪费问题。
在这里插入图片描述

漏桶算法

漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。
在这里插入图片描述
其底层采取队列处理的:
在这里插入图片描述

但也存在一些资源消耗问题(突发请求处理)。

1、大量请求达到网关时。由于请求处理速率平稳,导致大量请求堆积至网关中,造成网关资源浪费,压力过大。
2、请求堆积过多,导致大量的请求将会被丢弃。
3、浪费微服务的处理,比如设定1s一个请求,但一般的微服务每秒的处理效率可达几百几千,严重浪费资源。

令牌桶算法

令牌桶算法是漏桶算法的一种改进。

令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解。

随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了。

新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。
在这里插入图片描述
其流程概述如下所示:

1、假定令牌桶的容量是10个。
2、恒定速率生成令牌,生成令牌后,会判断令牌桶中的大小,如果没有容量了,则令牌丢弃;否则就保存至桶中。
3、正常来说每次请求过来时,都会从令牌桶中拿到一个令牌。
4、如果令牌拿完,并且此时令牌还在生成中,则将后续请求进行丢弃或者放入队列中缓存。

项目搭建测试

gateway RequestRateLimiter 官方文档

依赖引入

<!--引入redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--对象池 redis高版本中未使用jedis,采取的netty非阻塞型连接池-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

配置文件编写

server:
  port: 10000 # gateway的port

spring:
  application:
    name: gateway-server # 应用服务名
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #服务注册地址
    gateway:
      routes:
        - id: nacos-product # 微服务别名称
          uri: lb://nacos-product # lb://服务别名  根据服务名称从注册中心获取服务ip+port信息,lb:// 表示支持负载均衡
          predicates: # 断言
            - Path=/product/** #path规则,匹配对应的URL请求,将匹配到的请求追加至目标URI之后
          filters: # 网关过滤器
            # 限流过滤器
            - name: RequestRateLimiter
              args:
                # 令牌桶每秒填充平均速率,即行等价于允许用户每秒处理多少个请求平均数
                redis-rate-limiter.replenishRate: 1
                # 令牌桶的容量,允许在一秒钟内完成的最大请求数
                redis-rate-limiter.burstCapacity: 2
                # 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
                key-resolver: "#{@pathKeyResolver}" # pathKeyResolver是自己配置项目的bean的名称
                
  redis:
    database: 0
    host: 192.168.99.100
    port: 10000
    password: linkpower
    timeout: 10000 #连接超时时间
    lettuce:
      pool:
        #连接池最大连接数(使用负值表示没有限制)
        max-active: 300
        #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1s
        #连接池中的最大空闲连接
        max-idle: 100
        #连接池中的最小空闲连接
        min-idle: 20

请求路径限流

package cn.linkpower.config;

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

@Configuration
public class MyResolverConf {
    
    

    /**
     * 限流规则<br/>
     * pathKeyResolver 必须和application.yml文件中的 #{@pathKeyResolver} 保持一致
     * @return
     */
    @Bean
    public KeyResolver pathKeyResolver(){
    
    
//        return new KeyResolver() {
    
    
//            @Override
//            public Mono<String> resolve(ServerWebExchange exchange) {
    
    
//                return Mono.just(exchange.getRequest().getPath().toString());
//            }
//        }
        return exchange -> Mono.just(exchange.getRequest().getPath().toString());
    }
}

请求测试:

http://localhost:10000/product/getProduct/5
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

参数限流

相对于上面的配置文件,只需要修改ymljava 配置类中对应的配置即可,配置如下所示:

server:
  port: 10000 # gateway的port

spring:
  application:
    name: gateway-server # 应用服务名
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #服务注册地址
    gateway:
      routes:
        - id: nacos-product # 微服务别名称
          uri: lb://nacos-product # lb://服务别名  根据服务名称从注册中心获取服务ip+port信息,lb:// 表示支持负载均衡
          predicates: # 断言
            - Path=/product/** #path规则,匹配对应的URL请求,将匹配到的请求追加至目标URI之后
          filters: # 网关过滤器
            # 限流过滤器
            - name: RequestRateLimiter
              args:
                # 令牌桶每秒填充平均速率,即行等价于允许用户每秒处理多少个请求平均数
                redis-rate-limiter.replenishRate: 1
                # 令牌桶的容量,允许在一秒钟内完成的最大请求数
                redis-rate-limiter.burstCapacity: 2
                # 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
                #key-resolver: "#{@pathKeyResolver}" # pathKeyResolver是自己配置项目的bean的名称 ——请求路径限流
                key-resolver: "#{@paramKeyResolver}" # pathKeyResolver是自己配置项目的bean的名称 ——参数限流

  redis:
    database: 0
    host: 192.168.99.100
    port: 10000
    password: linkpower
    timeout: 10000 #连接超时时间
    lettuce:
      pool:
        #连接池最大连接数(使用负值表示没有限制)
        max-active: 300
        #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1s
        #连接池中的最大空闲连接
        max-idle: 100
        #连接池中的最小空闲连接
        min-idle: 20

#  redis:
#    database: 0
#    host: 192.168.99.100
#    port: 10000
#    password: linkpower
#    timeout: 10000 #连接超时时间
#    jedis: ## jedis配置
#      pool: ## 连接池配置
#        max-idle: 8 # 最大空闲连接数
#        max-active: 8 # 最大连接数
#        max-wait: 3000 # 最大阻塞等待时间
#        min-idle: 0 # 最小空闲连接数


package cn.linkpower.config;

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

@Configuration
public class MyResolverConf {
    
    

    /**
     * 限流规则<br/>
     * pathKeyResolver 必须和application.yml文件中的 #{@pathKeyResolver} 保持一致
     * @return
     */
    //@Bean
   // @Primary //添加这个注解是为了防止多个限流规则注册bean报错
    public KeyResolver pathKeyResolver(){
    
    
//        return new KeyResolver() {
    
    
//            @Override
//            public Mono<String> resolve(ServerWebExchange exchange) {
    
    
//                return Mono.just(exchange.getRequest().getPath().toString());
//            }
//        }
        return exchange -> Mono.just(exchange.getRequest().getPath().toString());
    }

    /**
     * 参数限流
     * @return
     */
    @Bean
    public KeyResolver paramKeyResolver(){
    
    
//        return new KeyResolver() {
    
    
//            @Override
//            public Mono<String> resolve(ServerWebExchange exchange) {
    
    
//                return Mono.just(exchange.getRequest().getPath().toString());
//            }
//        }
        // 随便指明一个参数
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userid"));
    }
}

此时请求测试,必须携带参数信息,因为在yml中针对该api追加了参数限流:
在这里插入图片描述
如果不添加参数请求时,如下所示:

http://localhost:10000/product/getProduct/5在这里插入图片描述
在这里插入图片描述

增加参数,分别定义不同的参数请求:

http://localhost:10000/product/getProduct/5?userid=1

也会出现限流操作。
不过是针对不同的参数值,设定了不同的令牌桶。

IP限流

ip限流的配置也和上述一样,只需要修改对应的yml和java配置类即可。

server:
  port: 10000 # gateway的port

spring:
  application:
    name: gateway-server # 应用服务名
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #服务注册地址
    gateway:
      routes:
        - id: nacos-product # 微服务别名称
          uri: lb://nacos-product # lb://服务别名  根据服务名称从注册中心获取服务ip+port信息,lb:// 表示支持负载均衡
          predicates: # 断言
            - Path=/product/** #path规则,匹配对应的URL请求,将匹配到的请求追加至目标URI之后
          filters: # 网关过滤器
            # 限流过滤器
            - name: RequestRateLimiter
              args:
                # 令牌桶每秒填充平均速率,即行等价于允许用户每秒处理多少个请求平均数
                redis-rate-limiter.replenishRate: 1
                # 令牌桶的容量,允许在一秒钟内完成的最大请求数
                redis-rate-limiter.burstCapacity: 2
                # 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
                #key-resolver: "#{@pathKeyResolver}" # pathKeyResolver是自己配置项目的bean的名称 ——请求路径限流
                #key-resolver: "#{@paramKeyResolver}" # pathKeyResolver是自己配置项目的bean的名称 ——参数限流
                key-resolver: "#{@ipKeyResolver}" # pathKeyResolver是自己配置项目的bean的名称 ——ip限流

  redis:
    database: 0
    host: 192.168.99.100
    port: 10000
    password: linkpower
    timeout: 10000 #连接超时时间
    lettuce:
      pool:
        #连接池最大连接数(使用负值表示没有限制)
        max-active: 300
        #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1s
        #连接池中的最大空闲连接
        max-idle: 100
        #连接池中的最小空闲连接
        min-idle: 20

#  redis:
#    database: 0
#    host: 192.168.99.100
#    port: 10000
#    password: linkpower
#    timeout: 10000 #连接超时时间
#    jedis: ## jedis配置
#      pool: ## 连接池配置
#        max-idle: 8 # 最大空闲连接数
#        max-active: 8 # 最大连接数
#        max-wait: 3000 # 最大阻塞等待时间
#        min-idle: 0 # 最小空闲连接数
package cn.linkpower.config;

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

@Configuration
public class MyResolverConf {
    
    

    /**
     * 限流规则<br/>
     * pathKeyResolver 必须和application.yml文件中的 #{@pathKeyResolver} 保持一致
     * @return
     */
   // @Bean
   // @Primary //添加这个注解是为了防止多个限流规则注册bean报错
    public KeyResolver pathKeyResolver(){
    
    
//        return new KeyResolver() {
    
    
//            @Override
//            public Mono<String> resolve(ServerWebExchange exchange) {
    
    
//                return Mono.just(exchange.getRequest().getPath().toString());
//            }
//        }
        return exchange -> Mono.just(exchange.getRequest().getPath().toString());
    }

    /**
     * 参数限流
     * @return
     */
    //@Bean
    public KeyResolver paramKeyResolver(){
    
    
//        return new KeyResolver() {
    
    
//            @Override
//            public Mono<String> resolve(ServerWebExchange exchange) {
    
    
//                return Mono.just(exchange.getRequest().getPath().toString());
//            }
//        }
        // 随便指明一个参数
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userid"));
    }

    @Bean
    public KeyResolver ipKeyResolver(){
    
    
//        return new KeyResolver() {
    
    
//            @Override
//            public Mono<String> resolve(ServerWebExchange exchange) {
    
    
//                return Mono.just(exchange.getRequest().getPath().toString());
//            }
//        }
        // 随便指明一个参数
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }
}

测试代码

github 代码地址

猜你喜欢

转载自blog.csdn.net/qq_38322527/article/details/113654721