基于信号量和令牌桶算法的限流

限流的三种算法

限流主要有三种算法:信号量、漏桶算法和令牌桶算法。

信号量限制的是并发、资源。令牌桶限制的是QPS。

信号量

Semaphore是一个计数信号量。常用于限制获取某资源的线程数量,可基于java的concurrent并发包实现。
通过acquire()方法获取许可,该方法会阻塞,直到获取许可为止。
通过release()方法释放许可。

基于java的concurrent的实现


@RestController
@RequestMapping("/semaphore")
public class SemaphoreController {
    //初始化信号量=5,true-公平信号量FIFO。
    private Semaphore semaphore = new Semaphore(5, true);

    @RequestMapping("/acquire/{par1}")
    public String acquire(@PathVariable String par1){
        try {
            semaphore.acquire(1);//获取一个许可
            //do sth
            return par1+"获取一个许可";
        }catch (InterruptedException e){
            e.printStackTrace();
            return "获取许可失败";
        }catch (Exception e1){
            return "获取许可失败";
        }finally {
            semaphore.release(1);//处理完后,释放一个许可
        }
    }
}

漏桶算法

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

 漏桶算法

令牌桶算法

令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务

 令牌桶算法

令牌桶算法比漏桶算法更加优雅,适合秒杀等场景。本示例主要描述基于google的guava令牌桶算法的实现。

pom坐标,建议使用16.0及以上版本

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>25.0-jre</version>
        </dependency>

限流实现

@RestController
@RequestMapping("/ratelimiter")
public class RateLimiterController {

    private RateLimiter rateLimiter = RateLimiter.create(0.5);//每秒发放0.5个令牌。注意:本初始化声明不要放在方法内

    //非阻塞机制
    @RequestMapping("/nonblock")
    public String rateLimiterNonBlock() {
        //如果令牌桶内有令牌,则直接发放;如果能在1000ms内能够获取一个令牌,则等待到时后发放;如果在1000ms内无法获取令牌,则直接返回false,无需等待。
        if (rateLimiter.tryAcquire(1000, TimeUnit.MILLISECONDS)) {
            //do sth
            return "成功获取令牌";
        } else {
            // do other
            return "获取失败";
        }
    }

    //阻塞机制
    @RequestMapping("/block")
    public String reateLimiterBlock(){
        //获取一个令牌,该方法会被阻塞知道获取令牌
        double waitTime = rateLimiter.acquire();
        //do sth
        return "等待"+waitTime+"秒后,成功获取令牌";
    }
}

建议使用非阻塞方式。

方法摘要

修饰符和类型方法描述
doubleacquire()从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求
doubleacquire(int permits)从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求
static RateLimitercreate(double permitsPerSecond)根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)
static RateLimitercreate(double permitsPerSecond, long warmupPeriod, TimeUnit unit)根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)
doublegetRate()返回RateLimiter 配置中的稳定速率,该速率单位是每秒多少许可数
voidsetRate(double permitsPerSecond)更新RateLimite的稳定速率,参数permitsPerSecond 由构造RateLimiter的工厂方法提供。
booleantryAcquire()从RateLimiter获取许可,如果该许可可以在无延迟下的情况下立即获取得到的话
booleantryAcquire(int permits)从RateLimiter获取许可数,如果该许可数可以在无延迟下的情况下立即获取得到的话
booleantryAcquire(int permits, long timeout, TimeUnit unit)从RateLimiter 获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可数的话,那么立即返回false (无需等待)
booleantryAcquire(long timeout, TimeUnit unit)从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)

参考资料:http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/RateLimiter.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值