计数器法
public class Counter {
private long timestamp = System.currentTimeMillis(); //当前时间窗口起始时间
private int reqCount = 0; //当前时间窗口计数器(请求数)
private final int reqLimitCount = 100; //限流阈值
private final long intervalMs = 1000; //时间窗口 ms
//返回false限流,true不限流
public boolean limit() {
long now = System.currentTimeMillis();
if (now < timestamp + intervalMs) { //在当前时间窗口
reqCount++; //递增请求数
return reqCount < reqLimitCount; //判断请求数是否小于阈值
} else { //不在当前时间窗口
timestamp = now; //重置当前时间窗口起始时间
reqCount = 1; //重置当前时间窗口内请求数
return true;
}
}
}
滑动时间窗口
* 示例:
* 模拟1s限制100个请求
*
* 滑动时间窗口将1s划分10个格子,每个格子100ms,每100ms滑动时间窗口向右滑动一次(增加一个格子,移除第一个格子),每个格子计数器记录了到此刻的总请求数
* 例如这个滑动时间窗口[10,20,30,40,50,60,70,80,90,100] ,截止到第一个格子的时间请求数10,截止到第10个格子的时间请求数100,1s时间窗口累计请求数为100-10=90个请求
public class SlideTimeWindow {
private volatile long reqCount=0; //当前累计请求数,可存储在redis中,实现分布式集群请求次数统计
private long reqLimitCount = 100; //限流阈值
private volatile boolean pass=true; //是否允许请求通过,true 允许通过,false 不允许通过
public static void main(String[] args) throws InterruptedException {
SlideTimeWindow slideTimeWindow = new SlideTimeWindow();
//启动滑动时间窗口限流
new Thread(()->{
try {
slideTimeWindow.limit();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
//模拟应用请求不断的到来
while (true){ //模拟在未被限流时,随机10ms内,就会来一个请求
if(slideTimeWindow.pass){
slideTimeWindow.reqCount=slideTimeWindow.reqCount+1;
}
TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 10));
}
}
public void limit() throws InterruptedException {
LinkedList<Long> slots = new LinkedList<>(); //滑动时间窗口,假设滑动时间窗口划分了10个格子,格子存储的是截止到当前窗格累计的请求数
while (true) {
slots.add(reqCount);
if (slots.size() > 10) { //滑动时间窗口超过10个格子,向后滑动一个格子
slots.removeFirst();
}
if (slots.peekLast() - slots.peekFirst() > reqLimitCount) { //获取滑动时间窗口内累计请求数,并与限流阈值比较,决定是否限流
System.out.println("被限流了 "+slots);
pass = false;
} else {
System.out.println("未限流 "+slots);
pass = true;
}
TimeUnit.MILLISECONDS.sleep(100);
}
}
}
漏桶
public class LeakyBucket {
private long capacity; //漏桶容量(限流阈值)
private long rate; //每秒流出速率(每秒流出的请求数量)
private long water; //漏桶中当前水量 (当前的请求数)
private long timestamp=System.currentTimeMillis(); //上次加水时间(请求时间)
//是否允许请求通过 true 通过,false 拒绝
public boolean pass(){
long now=System.currentTimeMillis();
//执行漏水,更新当前水量
water= Math.max(0,water-(now-timestamp)/1000*rate);
timestamp=now;
if(water+1<capacity){
water++;
System.out.println("加水成功"); //不需要限流
return true;
}else{
System.out.println("漏桶满了 加水失败"); //需要限流
return false;
}
}
}
令牌桶
public class TokenBucket {
private long capacity; //令牌桶容量
private long rate; //颁发令牌的速率(秒)
private long tokens; //令牌桶中当前令牌数量
private long timestamp = System.currentTimeMillis(); //上次颁发令牌时间
//是否允许请求通过 true 通过,false 拒绝
public boolean pass() {
long now = System.currentTimeMillis();
//颁发令牌,更新桶中当前令牌数量
tokens = Math.min(capacity, tokens + (now - timestamp) / 1000 * rate);
timestamp = now;
if (tokens < 1) { //没有令牌,请求拒绝
return false;
} else { //还有令牌,消耗一个令牌,请求通行
tokens--;
return true;
}
}
}