限流算法
1.为什么要限流
- 系统上游流量未知,如果超载引起雪崩。
- 系统下游吞吐能力一般,直连带宽有限。
2.常见的几种限流算法
1. 计数器限流
在一段时间间隔内,对请求进行计数,与阀值进行比较判断是否需要限流,一旦到了时间临界点,将计数器清零
优点:简单
缺点:很难处理单位时间内的临界问题
demo代码示例:
public class CountLimiter implements Limiter { //时间窗口 private long timeStamp= System.currentTimeMillis(); //请求次数 private int reqCount; //时间间隔 private long interval= 1000 ; //限流次数 private long limitNum= 100 ; /** * (单机限流,分布式限流用 redis) * 返回 true 代表限流, false 代表通过 * @return */ @Override public synchronized Boolean limit(){ long now = System.currentTimeMillis(); //判断当前时 if (now<timeStamp+interval){ //判断档期窗口内请求次数是否超过每秒限流的最大请求次数 if (reqCount+ 1 >limitNum){ //限流了 return true ; } reqCount++; return false ; } else { //开启新的时间窗口 timeStamp=now; //重置计数器 reqCount= 1 ; return false ; } } } |
2. 滑动窗口
把固定时间片进行划分,并且随着时间的流逝,进行移动,固定数量的可以移动的格子,进行计数并判断阀值
优点:将固定时间段分块,时间比“计数器”复杂,适用于稍微精准的场景
缺点:格子的数量影响着滑动窗口算法的精度,依然有时间片的概念,无法根本解决临界点问题
demo代码示例
package com.example.limit; import java.util.LinkedList; public class SlideTimeWindowLimiter implements Limiter { private int reqCount; //记录滑动的窗口的10个格子 private LinkedList<Integer> slots = new LinkedList<>(); //每秒限流的最大请求次数 private int limitNum = 100 ; //滑动时间窗口里的每个格子的时间长度,单位 ms private long windowLength = 100L; //滑动时间窗口里的格子数量 private int windowNum = 10 ; @Override public synchronized Boolean limit() { if ((reqCount + 1 ) > limitNum) { return true ; } slots.set(slots.size() - 1 , slots.peekLast() + 1 ); reqCount++; return false ; } //滑动窗口实现 public SlideTimeWindowLimiter(){ slots.addLast( 0 ); new Thread(()->{ while ( true ){ try { Thread.sleep(windowLength); } catch (InterruptedException e){ e.printStackTrace(); } synchronized ( this ){ slots.addLast( 0 ); if (slots.size()>windowNum){ //滑动窗口,删除第一个格子, reqCount=reqCount-slots.peekFirst(); slots.removeFirst(); } } } }).start(); } } |
3. 漏桶算法
一个固定容量的漏桶,按照固定速率流出水滴
特点:
1.以任意速率往桶中放入水滴。
2.以固定速率从桶中流出水滴。
3.如果流入水滴超出了桶的容量,则流入的水滴溢出(新请求被拒绝)
优点:可以很好的控制消费频率
缺点:限制的是常量流出速率(即流出速率是一个固定常量值),所以最大的速率就是出水的速率,不能出现突发流量
demo代码示例
package com.example.limit; public class LeakyBucketLimiter implements Limiter { private long timeStamp=System.currentTimeMillis(); private long capacity= 100 ; //桶的容量 private long rate= 10 ; //水漏出的速度(每秒系统能处理的请求数) private long water= 20 ; //当前水量(当前累计的请求数) @Override public synchronized Boolean limit() { long now = System.currentTimeMillis(); water = Math.max( 0 , water - ((now - timeStamp) / 1000 ) * rate); timeStamp = now; if ((water + 1 ) <= capacity) { //加水 water++; return false ; } else { //水满拒绝加水; return true ; } } } |
4. 令牌桶算法
一个固定的桶,桶里存放着令牌(token)。一开始桶是空的,系统按固定的时间(rate)往桶里添加令牌,直到桶里的令牌数满,多余的请求会被丢弃。当请求来的时候,从桶里移除一个令牌,如果桶是空的则拒绝请求或者阻塞。
特点:
1. 流入桶的速率一定
2.请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务
3.能够应对突发流量
demo代码示例
package com.example.limit; public class TokenBucketLimiter implements Limiter { private long timeStamp=System.currentTimeMillis(); private long capacity= 100 ; //桶的容量 private long rate= 10 ; // 令牌放入速度 private long tokens= 20 ; // 当前令牌数量 @Override public Boolean limit() { long now = System.currentTimeMillis(); //添加令牌 tokens = Math.min(capacity, tokens + (now - timeStamp) / 1000 * rate); timeStamp = now; if (tokens < 1 ) { return true ; } else { //还有令牌,领取令牌 tokens--; return false ; } } } |
作者:guanbin —— 纵码万里千山
出处:https://www.cnblogs.com/guanbin-529/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。