对接口进行限流的四个基本算法

本文深入解析了四种常见的限流算法:计数法、滑动窗口算法、漏桶算法及令牌法,通过Java代码实例展示了每种算法的工作原理,并讨论了它们各自的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

最近想对自己的秒杀系统添加一个QPS限流的功能,自己百度了一下,发现大家的大体思路是一样的,所以边学习边自己写了下来。因为刚开始写博客,是个小白,欢迎大家指出错误。感恩!

限流的算法

先来讲一下常见的限流的算法。

计数法

计数法就是通过定义一个count,在规定的时间内(eg,每分钟之内只能访问100次)每次有人访问接口的时候count++,直到达到限流的最大值100,之后的访问都拒绝,到下一分钟count重置为0;
在这里插入图片描述

  • 下面是用java写的代码帮助大家理解一下这个思路
package com.xiaoxiao.current_limiting.basic;

import java.util.Date;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.springframework.stereotype.Service;

@Service
public class CountLimiting {
    private long count = 0;
    private long countLimit = 100; //最大访问数
    private long period = 1000 * 1; //周期 1s
    private long timeStamp = new Date().getTime();

    Lock lock = new ReentrantLock();

    //可以访问返回true
    public boolean visit() {
        long now = new Date().getTime();  //当前时间戳

        if (timeStamp + period > now) { //如果now还在这个周期里
            if (++count <= countLimit) { //访问+1  相加和判断分步写的话会导致非原子性,所以这里我就写在一起了
                System.out.println(count);
                return true;
            } else {
                count--; //因为多加了所以要减掉
                return false;
            }
        } else {
            if (timeStamp + period < now) {
                try {
                    boolean res = lock.tryLock();
                    if (res) { //这里也是为了保证原子性 更新只能由一个线程更新
                        timeStamp = new Date().getTime();
                        count = 1;
                        return true;
                    } else {
                        return visit();//如果没有获得锁,重新请求
                    }
                } finally {
                    lock.unlock();
                }
            } else {
                count++;
                return true;
            }

        }
    }

}


  • 也可以直接用redis,用然后设置过期时间,实现比较简单,redis是线程安全的,这里不细说
缺点

计数法有一个明显的缺点就是划分的间隔太大了,如果在前一分钟的59.5之后才访问,访问了100次;在后一分钟的第0.5秒内访问了100次。其实就相当于1s之内访问了200次。

滑动窗口算法

窗口算法其实就是计数法的改进版,因为计数法的粒度太大了,所以窗口法就是把计数法的粒度变小,把一个周期分为n个窗口,把时间分为n分,然后进行填充。
在这里插入图片描述

  • java代码实现
package com.xiaoxiao.current_limiting.basic;

import java.util.Date;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BucketLimiting {
    private int[] count = new int[10]; //每个窗口的count
    private int allCount = 0; //总的count
    private long countLimit = 10000; //最大访问数
    private long period = 1000; //周期 1s
    private int bucket = 10;//窗口数
    private int index = 0; //目前指向哪个窗口
    private long timeStamp = new Date().getTime();
    //可以访问返回true
    Lock lock = new ReentrantLock();

    public boolean visit() {
        long now = new Date().getTime();  //当前时间戳

        if (timeStamp + period / bucket > now) { //如果now还在这个桶里
            if (++allCount <= countLimit) { //访问+1  相加和判断分步写的话会导致非原子性,所以这里我就写在一起了
                count[index]++; //桶里的数量也要加一
                return true;
            } else {
                allCount--; //因为多加了所以要减掉
                return false;
            }
        } else {
            if (timeStamp + period / bucket < now) {
                try {
                    boolean res = lock.tryLock();
                    if (res) { //这里也是为了保证原子性 更新只能由一个线程更新
                        timeStamp = new Date().getTime();
                        index = (index + 1) % bucket; //放在哪个桶 循环利用的
                        allCount -= count[index] - 1; //减掉要覆盖的那个桶的数量
                        count[index] = 1;
                        return true;
                    } else {
                        return visit();//如果没有获得锁,重新请求
                    }
                } finally {
                    lock.unlock();
                }
            } else {
                count[index]++;
                allCount++;
                return true;
            }

        }
    }
}

漏桶算法

有一个桶,里面可以放水,用什么速度放水我不管,但是漏水的速度会控制(请求接口的速度),如果水桶满了 ,那么水直接溢出(请求直接丢弃,不处理)。
在这里插入图片描述

  • java代码实现
package com.xiaoxiao.current_limiting.basic;

public class LeakyLimiting {
    private long water = 0;//一开始水桶里面没有水
    private long rate = 100;//漏水的速度 每秒100个
    private long capacity = 100;//水桶的容量是100
    private long timestamp = System.currentTimeMillis();//上次访问的时间

    public boolean visit() {
        long now = System.currentTimeMillis();
        water = Math.max(0L, water - (now - timestamp) / 1000 * rate);//当前剩余的水量 每次有访问的时候,更新水量
        timestamp = now; //更新最后访问时间
        if (++water <= capacity) {//水桶还放的下去,允许访问
            return true;
        } else {
            water--;//丢弃这滴水
            return false; //水满了不允许访问
        }
    }
}

令牌法

令牌法和漏桶算法很类似,其实是同一个意思。令牌法是往一个空桶里按一定的速率往里面放入令牌,如果桶满了,令牌就直接丢弃,每个请求要访问之前都要在桶里拿一个令牌,如果拿不到,就不允许访问
在这里插入图片描述


public class TokenLimiting {
    private long tokenCount = 0;//令牌数
    private long timestamp = System.currentTimeMillis();
    private long capacity = 100;//桶的容量
    private long rate = 100; //速度

    public boolean visit() {
        long now = System.currentTimeMillis();
        tokenCount = Math.min(tokenCount + (now - timestamp) / 1000 * 100, capacity);//桶里的令牌数
        timestamp = now;
        if (--tokenCount >= 0) {
            return true;
        } else {
            tokenCount++;
            return false;
        }
    }
}

Guava插件的令牌法

Guava的RateLimiter使用的就是令牌桶算法,按照固定的频率往桶里放入令牌,每次响应都要取到令牌才能响应,获取有2种方式:一种是直接返回失败,一种是阻塞到拿到令牌为止

package com.xiaoxiao.current_limiting.basic;

import com.google.common.util.concurrent.RateLimiter;

public class GuavaLimiting {
    public boolean visit() {
        RateLimiter rateLimiter = RateLimiter.create(100);//每秒100个令牌

        if (rateLimiter.tryAcquire()) { //尝试获取令牌 无延迟 获取不到返回false
            return true;
        } else {
            return false;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值