限流算法实现

并发数限流

1. 计数器并发数限流

int maxRequest=100;
AtomicInteger nowRequest=new AtomicInteger(0);
 
public void request(){
    for(;;){
        int currentReq=nowRequest.get();
        if(currentReq>=maxRequest){
            return;
        }
        if(nowRequest.compareAndSet(currentReq,currentReq+1)){
            break;
        }
    }
    //调用接口
    try{
         invokeXXX();    
    }finally{
        nowRequest.decrementAndGet();
    }
}

2. 信号量(Semaphore)

其实最简单的方法就是用信号量来实现:

int maxRequest=100;
Semaphore reqSemaphore = new Semaphore(maxRequest);
 
public void request(){
    if(!reqSemaphore.tryAcquire()){
        return ;
    }
 
    //调用接口
    try{
         invokeXXX();    
    }finally{
       reqSemaphore.release();
    }
}

QPS限流

QPS限流限制的是一段时间内(一般指1秒)的请求个数。

1. 计数器法

计数器法相对简单,如果我们限流1秒内最大100个请求,可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,

如果该请求与第一个请求的时间间隔在1秒之内,counter>100,那么说明请求数过多,counter<100,请求正常。
如果该请求与第一个请求的时间间隔大于1秒,那么就重置 counter

int maxQps=100;
int count;
long timeStamp=System.currentTimeMillis();//一个时间区间内的第一个请求
long interval=1000;

public synchronized boolean grant(){
    long now=System.currentTimeMillis();
    if(now<timeStamp+interval){
        count++;
        return count<maxQps;
    }else{
        timeStamp=now;
        count=1;
        return true;
    }
}

以上是示意代码,最大的问题是临界问题


计数器法.png

从上图中我们可以看到,假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

2.滑动窗口

我们用一个长度为10的数组表示1秒内的QPS请求,数组每个元素对应了相应100ms内的请求数。用一个sum变量代码当前1s的请求数。同时每隔100ms将淘汰过期的值。

伪代码如下:

int maxQps=100;
AtomicInteger[] count=new AtomicInteger[10];
long timeStamp=System.currentTimeMillis();
long interval=1000;
AtomicInteger sum;
volatile int index;

public void init(){
    for(int i=0;i<count.length;i++){
        count[i]=new AtomicInteger(0);
    }
    sum=new AtomicInteger(0);
}

public synchronized boolean  grant(){
    count[index].incrementAndGet();
    return sum.incrementAndGet()<maxQps;
}

//每100ms执行一次
public void run(){
    index=(index+1)%count.length;
    int val=count[index].getAndSet(0);
    sum.addAndGet(-val);
}

3. 漏桶算法

漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。

示意图如下:

漏桶算法.png
我们将算法中的水换成实际应用中的请求,我们可以看到漏桶算法天生就限制了请求的速度。

当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。所以漏桶算法天生不会出现临界问题

long timeStamp=getNowTime();
int capacity;  // 桶的容量
int rate ;     // 水漏出的速度
int water;     // 当前水量
 
bool grant() {
  //先执行漏水,因为rate是固定的,所以可以认为“时间间隔*rate”即为漏出的水量
  long now = getNowTime();
  water = max(0, water- (now - timeStamp)*rate);
  timeStamp = now;
 
  if water < capacity { // 水还未满,加水
    water ++;
    return true;
  } else {
    return false;//水满,拒绝加水
  }

4.令牌桶算法

从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。令牌桶算法则是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。桶中存放的令牌数有最大上限,超出之后就被丢弃或者拒绝。当流量或者网络请求到达时,每个请求都要获取一个令牌,如果能够获取到,则直接处理,并且令牌桶删除一个令牌。如果获取不同,该请求就要被限流,要么直接丢弃,要么在缓冲区等待。
令牌桶算法.png
public class TokenBucketDemo {
public long timeStamp = getNowTime();
public int capacity; // 桶的容量
public int rate; // 令牌放入速度
public int tokens; // 当前令牌数量
public boolean grant() {
    long now = getNowTime();
    // 先添加令牌
    tokens = min(capacity, tokens + (now - timeStamp) * rate);
    timeStamp = now;
    if (tokens < 1) {
    // 若不到1个令牌,则拒绝
    return false;
    } else {
    // 还有令牌,领取令牌
    tokens -= 1;
    return true;
    }
}

引用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 缓存 缓存比较好理解,在大型高并发系统中,如果没有缓存数据库将分分钟被爆,系统也会瞬间瘫痪。使用缓存不单单能够提升...
    阿斯蒂芬2阅读 12,031评论 1 28
  • 摘要:在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。而有些场景并不能用缓存和降级来解决,因此需有一种...
    落羽成霜丶阅读 2,138评论 0 18
  • 聊聊高并发系统限流特技-1来自开涛的博客 在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。缓存的目的是...
    meng_philip123阅读 6,544评论 1 20
  • 曾经在一个大神的blog里看到这样一句话:在开发高并发系统时,有三把利器用来保护系统:缓存、降级和限流。那么何为限...
    Johnsonxu阅读 1,969评论 0 4
  • 想象下面此场景: 你身边有10个追求者在追求你。 那么,你会不会找到一个评判的依据来给10个追求者打分,从高到低,...
    半山徐风雾自清阅读 568评论 0 0