计数器
一种比较简单的限流算法,用途比较广泛,在接口层面,很多地方使用这种方式限流。在一段时间内,进行计数,与阀值进行比较,到了时间临界点,将计数器清0。
计数器限流存在时间临界点的问题,比如每分钟限速100个请求,第59秒来了100个请求,第61秒又来了100个请求,59秒到61秒瞬间来了200个请求。
public class Counter { private static long timeStamp = System.currentTimeMillis(); // 每秒限制50个请求 private static long limitCount = 50; private static long interval = 1000; private static long reqCount = 0; public static boolean grant() { long now = System.currentTimeMillis(); if (now < timeStamp + interval) { if (reqCount < limitCount) { ++reqCount; return true; } else { return false; } } else { timeStamp = System.currentTimeMillis(); reqCount = 0; return false; } } public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { if (grant()) { System.out.println("执行中"); } else { System.out.println("限流中"); } } }).start(); } } }
滑动窗口
滑动窗口的意思是说把固定时间片,进行划分,并且随着时间的流逝,进行移动,这样就巧妙的避开了计数器的临界点问题。也就是说这些固定数量的可以移动的格子,将会进行计数判断阀值,因此格子的数量影响着滑动窗口算法的精度。虽然滑动窗口有效避免了时间临界点的问题,但是依然有时间片的概念。
计数器算法其实就是滑动窗口算法的一个特例,只有一个时间窗口。当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。
滑动窗口由于需要存储多份的计数器,每个窗口一份计数器,所以滑动窗口在实现上需要更多的存储空间。也就是说,如果滑动窗口的精度越高,需要的存储空间就越大。
漏桶算法
滑动窗口虽然避免了临界点问题,但还是依赖时间片,漏桶算法在这方面比滑动窗口而言,更加先进。有一个固定的桶,进水的速率是不确定的,但是出水的速率是恒定的,当水满的时候是会溢出的。漏桶算法不支持任持续突发和最大突发,天生就限制了请求的速度。当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。所以漏桶算法天生不会出现临界问题。
import static java.lang.Long.max; public class LeakBucket { private static long timeStamp = System.currentTimeMillis(); private static long capacity = 10; // 桶的容量 private static long water = 0; // 当前水量(当前累积请求数) private static long rate = 5; // 水流出速度 public static boolean grant() { long now = System.currentTimeMillis(); water = max(0, water - (now - timeStamp) * rate); // 先执行漏水,计算剩余水量 timeStamp = now; if ((water + 1) < capacity) { water += 1; return true; } else { return false; } } public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(new Runnable() { @Override public void run() { if (grant()) { System.out.println("执行中"); } else { System.out.println("限流中"); } } }).start(); } } }
令牌桶算法
一个存放令牌的桶,以一定的速度往这个桶生成令牌,数据流出先从这个桶中拿令牌,若是拿不到令牌就另行处理(具体自己设定)。桶令牌跟漏斗最大的区别在于可以支撑一个突然的流量变化,就是满桶令牌数的峰值。
import static java.lang.Long.min; public class TokenBucket { private static long timeStamp = System.currentTimeMillis(); private static long capacity = 10; // 桶的容量 private static long rate = 20; // 令牌放入速度 private static long tokens = 5; // 当前令牌数量 private static boolean grant() { long now = System.currentTimeMillis(); tokens = min(capacity, tokens + (now - timeStamp) * rate); timeStamp = now; if (tokens < 1) { return false; } else { tokens -= 1; return true; } } public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { if (grant()) { System.out.println("执行中"); } else { System.out.println("限流中"); } } }).start(); } } }
默认从桶里移除令牌是不需要耗费时间的,如果给移除令牌设置一个延时,那么令牌桶算法就变成了漏桶算法。 虽然令牌桶算法允许突发速率,但是下一个突发速率必须要等桶内有足够的 token后才能发生
参考