限流器

237 阅读2分钟

四大常见的限流算法: 限流的目的:通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。 简单理解突增流量超过系统承受的最大流量,会造成系统瘫痪。对于该问题,使用限流器进行限制。

1. 计数器:

  • 基本原理:即限制一段时间请求总数。假设A接口,1分钟的访问次数不能超过100个。
  • 具体实现:首先,初次调用A接口时记录初始时间startTime,并将计数器counter+1;接着后续的每一个请求来了,判断是否超过限额,如果没超过则counter+1,否则就拒绝访问。此使,当时间超过1分钟,则需要重置时间。
  • 优缺点:实现简单。但是无法应对突发流量。即假设59秒时来了100个请求,60秒时来了100个请求。这时系统可能会被瞬间击垮。
  • 代码实现:
    public class CounterLimit {
        public static int counter = 0; //计数器
        public static long startTime = System.currentTimeMillis(); // 系统启动时间
        public static long limit = 100; // 限流大小
        public static final long interval = 1000 * 60; // 时间窗口ms
    
        public static boolean acquireLimit() {
            long now = System.currentTimeMillis();
            if (now - startTime > interval) {
                startTime = now;
                counter = 1;
                return true;
            } else {
                counter++;
                return counter <= limit;
            }
        }
    }
    

2. 滑动窗口

  • 基本概念:对限流时间进行拆分,为了解决计数器法统计精度太低的问题,引入了滑动窗口算法。
  • 具体实现:恒定限流时间大小m,将时间拆分成n个块,每次过了m/n个时间,则丢弃最开始的时间,并添加最后的时间。通过判断最后时间的计数与最开始时间的计数差判断是否达到限流。
  • 优缺点:处理精度高,但是开辟更多空间。
  • 代码实现:
    public class SlidingTimeWindow {
        public static final int limit = 10;// 时间窗口内最大请求数
        public static final long interval = 1000; // 时间窗口ms
        public static long counter = 0L;// 服务访问次数
        public static LinkedList<Long> slots = new LinkedList<>(); // 记录的格子
        public static long startTime = System.currentTimeMillis(); // 第一个窗口的开始时间
    
        public static synchronized boolean acquireLimit() {
            counter++;
            long now = System.currentTimeMillis();
            if (now - startTime > interval || slots.size() == 0) {
                slots.add(counter);
            } else {
                slots.set(slots.size() - 1, counter);
            }
            if (slots.size() > 10) {
                slots.removeFirst();
                startTime = startTime+interval;
            }
            if (slots.size() == 1) {
                return slots.getLast() > limit;
            } else {
                long count = slots.getLast() - slots.getFirst();
                return count > limit;
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            ExecutorService threadPool = Executors.newFixedThreadPool(20);
            while (true) {
                threadPool.execute(() -> {
                    boolean b = SlidingTimeWindow.acquireLimit();
                    System.out.println("线程:" + Thread.currentThread().getName() + "是否限流" + b);
                });
                Thread.sleep(1000);
            }
        }
    }
    

3. 令牌桶

  • 基本概念:用于对网络流量进行整形和速率限制。
  • 具体实现:首先固定令牌桶的大小为m,并且系统以恒定速度v向令牌桶中加入令牌,桶中令牌总数最大为m,其它令牌满则溢。当请求到了会消耗一个令牌,只有拿到令牌的请求才会进行处理。
  • 代码实现:使用Guava限流工具类RateLimiter实现。
  • 优缺点:实现简单,且允许某些流量的突发。

4. 漏桶

  • 基本概念:可以粗略的认为注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。