Guava Ratelimit源码分析以及仿造优化版本

12 篇文章 0 订阅

今天介绍Guava的限流RateLimit,主要介绍2个,一个是源码分析,一个是仿造他的原理写一个优化版本。

一、源码分析

首先我个人认为RateLimit的设计思想很好很优秀,但是写的有点瑕疵,后面我会说到。

①create方法

第一个参数是Guava的秒表工具用来计时的,第二个参数是你输入的,也就是每秒生成的令牌数。SmoothBursty是RateLimit的子类,看看他的构造方法。

第二个参数默认值为1,官方意思是当ratelimit不工作时候可以保存多少秒的标签,其实我觉得真的是不清不楚的,网上很多人也都直接来过来粘贴进去。其实这个意思就是这个参数是用来计算你的桶的大小用的,你create时候只输入了每秒产生多少个,乘以这个参数就等于桶大小,也就是说默认方法是每秒产生多少个 == 桶size。

setRate方法就是校验一下参数然后加锁调用doSetRate方法,然后你就可以看到我上面说的maxBurstSeconds参数的作用了。storedPermits是当前桶内令牌数,由于我们舒适化时候都没对maxPermits赋值,所以这个方法最终storedPermits会赋0。

这样create就完成了,这里我只介绍普通模式,热启动不做介绍,其实也差不多。

②tryAcquired

第一个参数是每次消费多少个令牌,第二个是获取不到令牌时候延时多久放弃,第三个是时间工具类。可以看出来最重要其实就是canAcquire和reserveAndGetWaitLength方法,接下来我们逐一介绍着两个方法。

1、canAcquire

queryEarliestAvailable方法是获取 最近一个可以获得的令牌的时间(这个话听上去有点绕,没事有个概念即可,后面会详细介绍)。那么canAcquire方法用公式表示就是:最近可以获得令牌时间 <= 当前时间 + 超时时间。

2、reserveAndGetWaitLength 次方法是时间Ratelimit的核心中的核心,讲的比较多,公式也比较多,耐心看完!

reserveAndGetWaitLength 调用了reserveEarliestAvailable方法,看看里头实现了什么

首先第一个参数是你这次请求需要多少个令牌,第二个是当前时间。然后第一步调用了一个resync同步方法,看看这个方法

这里有几个成员变量,我先解释一下什么意思。

nextFreeTicketMicros:距离下一个可以获得令牌的时间,如果当前时间大于它,那么至于当前时间。

storedPermits:当前桶里令牌数

maxPermits:桶大小

permitsPerSecond:每秒生成的令牌数

stableIntervalMicros:生成一个令牌需要的时间,单位是微秒,计算公式是:1000000 * 1s / permitsPerSecond

这个方法,就是计算当开始生成令牌的时间到现在这段时间生成了多少令牌,这个也是Guava跟一般令牌桶实现不一样的地方,它并不是用线程异步放令牌,而是通过计算时间差而得出。这样做有一个好处就是,如果我现在要针对每个接口限流,我需要每个接口创建一个桶,如果用异步线程生产令牌,那要开多少个线程。

然后把桶最大值和计算出的令牌数对比,选一个小的赋给当前令牌数参数。并且把当前时间给nextFreeTicketMicros。

回到上一个方法。

同步完成后,比较需要的令牌和当前桶里剩余令牌,并且做差值。storePermitsToWaitTime方法在普通限流时候都是返回0,所以不用看。然后计算等待时间,公式是:(需要的令牌数 - 当前剩余令牌数)* 每个令牌生成时间。nextFreeTicketMicros此时由于上面调用过resync方法,所以现在是当前时间所以nextFreeTicketMicros = nowMics + waitMIcros;最后减少桶内令牌即可。

看到这里就问你懵不懵!,尤其是nextFreeTicketMicros这个参数,其实你回到最早我们canacquire方法

这里queryEarliestAvailable方法就是获取nextFreeTicketMicros,来比较时间,看时候有令牌。而nextFreeTicketMicros是由当前时间加上等待时间算出来的,也就是说这个参数是说下一个生成令牌的时间,如果当前时间小于这个参数,那么就是说令牌还没生成所以就返回false。

二、优化

代码我贴出来了,我简化了很多操作,但是性能还是跟Guava是一样的(做过压测),唯一要注意的点,就是计算当前令牌数的时候。

@Slf4j
public class RateLimit {
    //当前桶里ticket数量
    private double currentTicketNum;
    //桶大小
    private long bucketSize;
    //距离下一个可获得的ticket的时间
    private long nextFreeTicketMic;
    //每秒生成的ticket数量
    private double ticketPerSecond;
    private double lastConsumMic;


    private Stopwatch stopwatch;
    private Lock lock;

    //创建
    public static RateLimit create(long ticketPerSecond) {
        if (ticketPerSecond < 1) {
            throw new IllegalArgumentException("param must > 1");
        }

        RateLimit rateLimit = new RateLimit();
        rateLimit.bucketSize = ticketPerSecond;
        rateLimit.ticketPerSecond = ticketPerSecond;
        rateLimit.stopwatch = Stopwatch.createStarted();
        rateLimit.lock = new ReentrantLock();

        return rateLimit;
    }

    //非阻塞获取ticket,该方法只允许一次获取一个ticket并且不允许提前消费
    public boolean tryAcquire() {
        try {
            lock.lock();
            long nowMic = this.stopwatch.elapsed(MICROSECONDS);
            //判断如果当前时间小于可以获得ticket时间直接返回false
            if (this.nextFreeTicketMic > nowMic) {
                return false;
            } else {
                //计算这一段时间产生的ticket数量,你发现没我这里用的不是nextFreeTicketMic而是lastConsumMic,
                //确实用作者写的没问题,但是它算出来的总比实际的当前令牌数少1个,所以这里做了优化
                this.currentTicketNum = Math.min(bucketSize, currentTicketNum + (nowMic - lastConsumMic) / buildTicketPerMic());

                //该次请求消耗ticket
                if (currentTicketNum > 0) {
                    currentTicketNum--;
                }

                long waitMic = 0L;
                if (currentTicketNum <= 0) {
                    waitMic = (long) buildTicketPerMic();
                }

                //计算下一次可以获取到ticket的时间
                lastConsumMic = nowMic;
                this.nextFreeTicketMic = nowMic + waitMic;
            }
        } catch (Exception e) {
            log.error("获取ticket异常", e);
        } finally {
            lock.unlock();
        }

        return true;
    }

    private double buildTicketPerMic() {
        return SECONDS.toMicros(1L) / ticketPerSecond;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值