4种常见的限流算法

限流算法

1、固定窗口

含义: 在一个固定长度的时间窗口内限制请求数量,每来一个请求,请求次数加一,如果请求数量超过最大限制,就拒绝该请求
优点: 实现简单,容易理解。
缺点: ①限流不够平滑。例如:限流是每秒3个,在第一毫秒发送了3个请求,达到限流,窗口剩余时间的请求都将会被拒绝,体验不好。
②无法处理窗口边界问题。因为是在某个时间窗口内进行流量控制,所以可能会出现窗口边界效应,即在时间窗口的边界处可能会有大量的请求被允许通过,从而导致突发流量

public class FixWindowLimiter {
   

    /**
     * 每个窗口的最大请求数量
     */
    public static long threshold = 10;
    /**
     * 窗口大小,1000ms
     */
    public static long windowUnit = 1000;
    /**
     * 窗口内的当前请求数量
     */
    public static long count = 0;
    /**
     * 窗口的开始时间
     */
    public static long lastTime = 0;

    /**
     * 限流方法,返回true表示通过
     */
    public boolean limit() {
   
        // 获取系统当前时间
        long currentTime = System.currentTimeMillis();
        // 判断是否在当前时间窗口内,如果不在就开启一个新的时间窗口
        if (currentTime - lastTime > windowUnit) {
   
            // 计数器清零
            count = 0;
            // 开启新的时间窗口
            lastTime = currentTime;
        }
        // 判断是否超过最大请求量
        if (count < threshold) {
   
            count++;
            return true;
        }
        return false;
    }

}

2、滑动窗口

定义:每来一个请求,就向后推一个时间窗口,计算这个窗口内的请求数量。如果请求数量超过限制就拒绝请求,否则就处理请求,并记录请求的时间戳。另外还需要一个任务清理过期的时间戳。
优点: 解决了固定窗口算法的窗口边界问题,避免突发流量压垮服务器。
缺点: 还是存在限流不够平滑的问题

public class SlidingWindowLimiter {
   

    /**
     * 每个窗口的最大请求数量
     */
    public static long threshold = 10;
    /**
     * 窗口大小,1000ms
     */
    public static long windowUnit = 1000;
    /**
     * 请求集合,用来存储窗口内的请求数量
     */
    public static List<Long> requestList = new ArrayList<>();

    /**
     * 限流方法,返回true表示通过
     */
    public boolean limit() {
   
        // 获取系统当前时间
        long currentTime = System.currentTimeMillis();
        // 统计当前窗口内,有效的请求数量
        int sizeOfValid = this.sizeOfValid(currentTime);
        // 判断是否超过最大请求数量
        if (sizeOfValid < threshold) {
   
            // 把当前请求添加到请求集合里
            requestList.add(currentTime);
            return true;
        }
        return false;
    }

    /**
     * 统计当前窗口内,有效的请求数量
     */
    private int sizeOfValid(long currentTime) {
   
        int sizeOfValid = 0;
        for (Long requestTime : requestList) {
   
            // 判断是否在当前时间窗口内
            if (currentTime - requestTime <= windowUnit) {
   
                sizeOfValid++;
            }
        }
        return sizeOfValid;
    }

    /**
     * 清理过期的请求(单独启动一个线程处理)
     */
    private void clean() {
   
        // 判断是否超出当前时间窗口内
        requestList.removeIf(requestTime -> System.currentTimeMillis() - requestTime > windowUnit);
    }

}

3、漏桶算法

定义:一个固定容量的漏桶,按照固定速率出水(处理请求); 当流入水(请求数量)的速度过大会直接溢出(请求数量超过限制则直接拒绝)。桶里的水(请求)不够则无法出水(桶内没有请求则不处理)
优点:
①平滑流量。由于漏桶算法以固定的速率处理请求,可以有效地平滑和整形流量,避免流量的突发和波动(类似于消息队列的削峰填谷的作用)。
②防止过载。当流入的请求超过桶的容量时,可以直接丢弃请求,防止系统过载。
缺点:
①无法处理突发流量:由于漏桶的出口速度是固定的,无法处理突发流量。例如,即使在流量较小的时候,也无法以更快的速度处理请求。
②可能会丢失数据:如果入口流量过大,超过了桶的容量,那么就需要丢弃部分请求。在一些不能接受丢失请求的场景中,这可能是一个问题。
③不适合速率变化大的场景

public class LeakyBucketLimiter {
   

    /**
     * 桶的最大容量
     */
    public static long threshold = 10;
    /**
     * 桶内当前水量
     */
    public static long count = 0;
    /**
     * 漏水速率(每秒5次)
     */
    public static long leakRate = 5;
    /**
     * 上次漏水时间
     */
    public static long lastLeakTime = System.currentTimeMillis();

    /**
     * 限流方法,返回true表示通过
     */
    public boolean limit() {
   
        // 调用漏水方法
        this.leak();
        // 判断是否超过最大请求数量
        if (count < threshold) {
   
            count++;
            return true;
        }
        return false;
    }

    /**
     * 漏水方法,计算并更新这段时间内漏水量
     */
    private void leak() {
   
        // 获取系统当前时间
        long currentTime = System.currentTimeMillis();
        // 计算这段时间内,需要流出的水量
        long leakWater = (currentTime - lastLeakTime) * leakRate / 1000;
        count = Math.max(count - leakWater, 0);
        lastLeakTime = currentTime;
    }

}

4、令牌桶

在这里插入图片描述

定义: 统以固定的速率向桶中添加令牌; 当有请求到来时,会尝试从桶中移除一个令牌,如果桶中有足够的令牌,则请求可以被处理或数据包可以被发送;
如果桶中没有令牌,那么请求将被拒绝; 桶中的令牌数不能超过桶的容量,如果新生成的令牌超过了桶的容量,那么新的令牌会被丢弃
令牌桶算法的一个重要特性是,它能够应对突发流量。当桶中有足够的令牌时,可以一次性处理多个请求,这对于需要处理突发流量的应用场景非常有用
优点:
①可以处理突发流量:令牌桶算法可以处理突发流量。当桶满时,能够以最大速度处理请求。这对于需要处理突发流量的应用场景非常有用。
②限制平均速率:在长期运行中,数据的传输率会被限制在预定义的平均速率(即生成令牌的速率)。
③灵活性:与漏桶算法相比,令牌桶算法提供了更大的灵活性。例如,可以动态地调整生成令牌的速率。
缺点:
①可能导致过载:如果令牌产生的速度过快,可能会导致大量的突发流量,这可能会使网络或服务过载。
②需要存储空间:令牌桶需要一定的存储空间来保存令牌,可能会导致内存资源的浪费。
③实现稍复杂:相比于计数器算法,令牌桶算法的实现稍微复杂一些

public class TokenBucketLimiter {
   

    /**
     * 桶的最大容量
     */
    public static long threshold = 10;
    /**
     * 桶内当前的令牌数量
     */
    public static long count = 0;
    /**
     * 令牌生成速率(每秒5次)
     */
    public static long tokenRate = 5;
    /**
     * 上次生成令牌的时间
     */
    public static long lastRefillTime = System.currentTimeMillis();

    /**
     * 限流方法,返回true表示通过
     */
    public boolean limit() {
   
        // 调用生成令牌方法
        this.refillTokens();
        // 判断桶内是否还有令牌
        if (count > 0) {
   
            count--;
            return true;
        }
        return false;
    }

    /**
     * 生成令牌方法,计算并更新这段时间内生成的令牌数量
     */
    private void refillTokens() {
   
        long currentTime = System.currentTimeMillis();
        // 计算这段时间内,需要生成的令牌数量
        long refillTokens = (currentTime - lastRefillTime) * tokenRate / 1000;
        count = Math.min(count + refillTokens, threshold);
        lastRefillTime = currentTime;
    }

}

springboot中使用guava实现令牌桶限流

①导包

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1-jre</version>
</dependency>

②代码实现

public class LimitController {
   
    /**
     * 限流策略 :1秒钟2个请求
     */
    private final RateLimiter limiter = RateLimiter.create(2.0);

    private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @GetMapping("/test1")
    public String testLimiter() {
   
        //500毫秒内,没拿到令牌,就直接进入服务降级
        boolean tryAcquire = limiter.tryAcquire(500, TimeUnit.MILLISECONDS);

        if (!tryAcquire) {
   
            log.warn("进入服务降级,时间{}", LocalDateTime.now().format(dtf));
            return "当前排队人数较多,请稍后再试!";
        }

        log.info("获取令牌成功,时间{}", LocalDateTime.now().format(dtf));
        return "请求成功";
    }
}

相关推荐

  1. 算法

    2023-12-08 14:10:06       42 阅读
  2. 常用算法原理与实现

    2023-12-08 14:10:06       60 阅读
  3. 服务算法及其实现

    2023-12-08 14:10:06       17 阅读

最近更新

  1. MMSegmentation笔记

    2023-12-08 14:10:06       1 阅读
  2. 网络安全筑基篇——XSS、XML、XXE

    2023-12-08 14:10:06       1 阅读
  3. 语义熵:深度学习中的信息度量新指标

    2023-12-08 14:10:06       1 阅读

热门阅读

  1. Linux篇之基于Centos的everything镜像搭建yum镜像源

    2023-12-08 14:10:06       37 阅读
  2. WordPress禁止显示指定类别的文章

    2023-12-08 14:10:06       40 阅读
  3. Elasticsearch桶聚合和管道聚合

    2023-12-08 14:10:06       34 阅读
  4. Docker

    Docker

    2023-12-08 14:10:06      46 阅读
  5. 超详细数学建模论文模板分享

    2023-12-08 14:10:06       33 阅读
  6. CSS 垂直水平居中总结(全)

    2023-12-08 14:10:06       49 阅读
  7. 使用kubeadm搭建高可用的K8s集群

    2023-12-08 14:10:06       36 阅读