四种限流算法
为什么要限流
限流是为了防止系统突然收到大量请求,后台面对大量并发请求对cpu和内存,网络io产生巨大压力,可能将一些服务如mysql,redis等打崩,引发系统故障,服务瘫痪。
固定窗口:Fixed Window Rate Limiting Algorithm
将时间分为固定窗口,将请求按时间顺序放入时间窗口,如果超过设置时间窗口的阈值就返回限流结果,但是在两个时间窗口替换间隙可能会有2n请求并发。
优点:实现简单,便于理解
缺点:存在明显的临界问题,切换间隙可产生产生2n请求:0.75秒-1.0秒产生n个请求,刷新,1.0-1.5s又产生n个请求,这n个请求各自窗口满足要求,但是再它门组成的窗口中并发量达到了2n
public class FixedWindow {//窗口大小private final static Integer timeUnit=1000;//请求计数private AtomicInteger count=new AtomicInteger();private Long lastTime=0L;private Integer threshold=10;public boolean fixedWindow(){long currentTime=System.currentTimeMillis();if(currentTime-lastTime>timeUnit){count.set(0);lastTime=currentTime;}if(count.get()>=threshold) //初始计数是0 ,所以是大于等于刚好执行十次{return false;}count.getAndIncrement();return true;}public static void main(String[] args) {FixedWindow fixedWindow=new FixedWindow();for(int i=0;i<20;i++){System.out.println(fixedWindow.fixedWindow());}}}
滑动窗口:Sliding Windows Algorithm
将单位时间周期又划分成n个小周期,分别根据每个小周期内接口的访问次数,并根据时间滑动自动删除过期的小周期(tcp就是根据这个来实现对发送字节的大小控制)
优点: 简单易懂,精度高可以调整时间粒度来调节限流效果,可拓展性,可与和其他限流算法结合使用
缺点:无法应对突发请求,突发请求会被大量拒绝,会损失大量请求
public class SlidingWindow {private final int maxRequests; // 最大请求数private final long timeWindowMillis; // 时间窗口(毫秒)private final Queue<Long> requests; // 请求队列public SlidingWindow(int maxRequests, long timeWindowMillis) {this.maxRequests = maxRequests;this.timeWindowMillis = timeWindowMillis;this.requests = new LinkedList<>();}public synchronized boolean tryAcquire() {// 移除超出时间窗口的请求while (!requests.isEmpty() && System.currentTimeMillis() - requests.peek() > timeWindowMillis) {requests.poll();}// 判断当前窗口内的请求数量是否超过最大请求数if (requests.size() < maxRequests) {// 添加新请求到队列requests.offer(System.currentTimeMillis());return true;} else {return false;}}public static void main(String[] args) throws InterruptedException {SlidingWindow rateLimiter = new SlidingWindow(5, 1000); // 每秒最多5个请求for (int i = 0; i < 20; i++) {System.out.println("Request " + (i + 1) + ": " + (rateLimiter.tryAcquire() ? "Accepted" : "Rejected"));}}
}
漏桶算法:Leaky Bucket Algorithm
控制流入网络的速率速度,以防止网络堵塞。核心思想就是将请求比作小水滴,漏桶看作固定容量的水桶,数据包从顶部进入漏桶,漏桶底部以匀速形式流出数据包。水满则溢,拒绝请求。
优点:可以十分平滑的处理请求,对瞬间增加的请求有一定的承载能力
缺点:需要对请求进行缓存,会增加服务器的内存消耗。面对突发流量无法进行快速处理请求。
public class LeakyBucket {private int capacity = 10;private int rate = 1; //每ms多少个private int total = 0;private long lastTime = System.currentTimeMillis();private synchronized boolean leakBucket(Integer water) throws InterruptedException {long time=System.currentTimeMillis();long escapeTime=time-lastTime;long leakWater=escapeTime*rate;if(leakWater>0){total=(int) Math.max(0,total-leakWater);lastTime=time;}if(total+water>capacity){Thread.sleep(4);return false;}total=total+water;return true;}public static void main(String[] args) throws InterruptedException {LeakyBucket leakyBucket=new LeakyBucket();for (int i = 0; i < 20; i++) {System.out.println(leakyBucket.leakBucket(2));}}
}
令牌桶算法:Token Bucket Algorithm
系统以一定的速率向令牌桶中添加令牌,请求到来时先去尝试获取令牌,拿到令牌就正常请求并消耗这个令牌,拿不到就拒绝服务。
优点:可以应对突发流量,具有弹性处理请求的特点,稳定性好。精度高:可以根据请求动态调节生成令牌桶的速率
缺点:实现复杂,时间精度控制要求比较高
public class TokenBucket {private long capacity = 5;//令牌总数private long lastTime=System.currentTimeMillis();private long rate=5;//每毫秒发放令牌private long total=capacity;private synchronized boolean tokenBucket() throws InterruptedException {long now=System.currentTimeMillis();long gap=now-lastTime;long add=gap*rate;if(total<20){total=Math.min(total+add,capacity);lastTime=now;}if(total-1<0){Thread.sleep(1);return false;}total--;return true;}public static void main(String[] args) throws InterruptedException {TokenBucket tokenBucket=new TokenBucket();for (int i = 0; i < 20; i++) {System.out.println(tokenBucket.tokenBucket());}}
}