限流算法

固定时间窗口算法

在这里插入图片描述

实现原理:
固定时间内限制访问次数,如1秒内限制请求100个,如果超出阈值就拒绝其他请求。如果单位时间结束,则进入下一轮计数。

临界值问题:
如果在上一秒最后100ms内请求发了100个请求,下一秒前100ms内请求了100个请求,相当于一秒内请求了200个请求,超过了阈值但是没有被限流

滑动窗口算法

滑动窗口是为了解决固定窗口计数存在问题而诞生的,滑动窗口是基于时间来划分窗口的。
实现原理
滑动窗口算法是基于固定时间窗口算法的变种,在这里我们把1秒的时间分割成多格,例如分割为5格,没过200ms向前移动一格,如下图所示
在这里插入图片描述

每格都有请求数,当请求超出当前格的数量就会被限流

个人认为这种方式也没有完全解决固定窗口算法的问题,只是通过拆分成多个时间窗口加强精确度的目的

漏桶算法

在这里插入图片描述

为了解决“突刺现象”,可以采用漏桶算法实现限流,当请求进来,不管流量多大,下面的速度始终保持不变

突刺现象
指在一定时间内的一小段时间内就用完了所有资源,后大部分时间中无资源可用。
比如在限流方法中的计算器算法,设置1s内的最大请求数为100,在前100ms已经永远了100个请求,则后面900ms将无法处理请求,这就是突刺现象。

不管客户端请求多么不稳定,通过漏桶算法限流,每10ms处理一次请求,因为处理速度是固定的,请求的数量是未知的,未来得及处理的请求存放在桶内,超出桶容量上限,新进来的请求就丢弃。

实现方案
通过队列存储请求,另外通过线程池从队列获取请求并执行,可以一次性获取多个并发执行,但是存在无法在短时间内处理突发流量。

令牌桶算法

令牌桶算法是对漏桶算法的一种改进,漏桶算法能限制请求调用速率,令牌桶算法能在限制调用的平均速度同时允许一定程度的突发调用。
在这里插入图片描述

令牌桶算法中存在一个桶用于存储固定数量的令牌,算法机制以固定速率往桶中存放令牌。每次请求调用需要先去获取令牌,只有拿到令牌才有机会继续执行请求,否则等待获取可用令牌、或者直接拒绝请求。

如果桶内令牌达到上限,就丢弃令牌,当桶内有大量可用令牌时,这是进来的请求可以直接拿到令牌执行,++比如设置QPS为100,容器初始化完成一秒后,桶内就有100个令牌了,等服务启动完成对外提供服务时,就可以抵挡瞬时的100个请求,由于令牌发放算法,会匀速处理后续请求++

令牌桶算法是漏斗算法的改进版,解决漏桶算法无法短时间内处理突发流量的问题,令牌桶算法由三个部分组成令牌流数据流令牌桶.

  • 令牌流:流通令牌的管道,用于生成令牌的流通,存放令牌桶中
  • 数据流:进入系统的数据流量
  • 令牌桶:存放令牌的区域,可以理解成缓存区:令牌保存在这里用于使用

算法原理:
令牌桶算法会按固定速率生成令牌放入令牌桶内,访问进入系统是,需要从令牌桶内获取到令牌,有令牌的可以进入,没有的被抛弃。由于令牌是不间断生成的,当请求量小于令牌发放速度时,令牌桶可以达到桶容量上线,短时间内突发大量访问时,积累的令牌可以处理这个问题。当访问量持续大量流入时,由于令牌生成的速度是固定的,最后也会变成类似漏桶算法的固定流量处理。

令牌桶和漏桶对比

  • 令牌桶
    按照固定速率往桶内添加令牌,请求需要获判断令牌是否足够,无令牌时拒绝新的请求。
    平均流入速率,在空闲时间可以积累令牌,所以可以处理许突发请求。
  • 漏桶
    按照固定速率流出请求,流入的请求数累达到桶容量后,拒绝新流入请求。
    固定流出速率。

Guava

Google Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support]、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 、I/O 等等。

Guava官方文档-RateLimiter类RateLimiter使用的是一种叫令牌桶的流控算法,RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行,比如你希望自己的应用程序QPS不要超过1000,那么RateLimiter设置1000的速率后,就会每秒往桶里扔1000个令牌,RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率。。

<dependency> 
	<groupId>com.google.guava</groupId> 
	<artifactId>guava</artifactId> 
	<version>29.0-jre</version> 
</dependency>
/*** 模拟RateLimiter限流 */
public class TestRateLimiter {
    public static void main(String[] args) { //0.5代表一秒最多多少个 
        RateLimiter rateLimiter = RateLimiter.create(0.5);
        List<Runnable> tasks = new ArrayList<Runnable>();
        for (int i = 0; i < 10; i++) {
            tasks.add(new UserRequest(i));
        }
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (Runnable runnable : tasks) {
            System.out.println("等待时间:" + rateLimiter.acquire());
            threadPool.execute(runnable);
        }
    }

    private static class UserRequest implements Runnable {
        private int id;

        public UserRequest(int id) {
            this.id = id;
        }

        public void run() {
            System.out.println("userQuestID:" + id);
        }
    }
}

Spring Cloud GateWay 令牌桶限流

Spring Cloud Gateway 基于内部过滤器工厂RequestRateLimiterGateWayFilterFactory实现。
RequestRateLimiterGateWayFilterFactory依赖于Redis,需要引入spring-boot-starter-data-redis-reactive依赖。

<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>
server:
  port: 8080
spring:
  cloud:
    gateway:
      routes:
        - id: limit_route
          uri: http://httpbin.org:80/get
          predicates:
            - After=2019-02-26T00:00:00+08:00[Asia/Shanghai]
          filters:
            - name: RequestRateLimiter
              args:
                key-resolver: '#{@userKeyResolver}' #用于限流的解析器的Bean对象名字,使用SpEL表达式根据#{@beanName}从Spring容器中获取Bean对象
                redis-rate-limiter.replenishRate: 50 # 令牌桶每秒速率
                redis-rate-limiter.burstCapacity: 300 # 令牌桶总容量
  application:
    name: gateway-limiter
  redis: 
    host: localhost 
    port: 6379 
    database: 0
@Configuration
public class KeyResolverConfiguration {
    /*** 接口限流: * 获取请求地址的uri作为限流key。 */
    @Bean
    public KeyResolver pathKeyResolver() {
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                return Mono.just(exchange.getRequest().getPath().toString());
            }
        };
    }

    /*** 用户限流: * 获取请求用户id作为限流key。 */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
    }

    /**
     * IP限流: * 获取请求用户ip作为限流key。
     */
    @Bean
    public KeyResolver hostAddrKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值