Guava RateLimiter 原理 理解

背景

项目中限流可以用Guava RateLimiter,不想了解原理是啥吗?!
这篇文章已经写的很好了
没啥可补充了,加一些个人思考吧

思考

如果让你来造一个限流器,有啥想法?

直观想法1,对应参考文章的漏桶算法

就是用一个固定大小的队列。比如设置限流为5qps,1s可以接受5个请求;那我们就造一个大小为5的队列,如果队列为满了,就拒绝请求;如果队列未满,就往队列添加请求。

如何控制速率呢,我们通过控制消费者的消费速率是5qps,1s消费5个即可。

问题,说的挺轻巧,具体怎么控制消费者的速率呢?又加一个定时任务来消费队列吗,也挺费劲的。

想法2,对应参考文章的令牌算法

令牌听起来挺酷的。以固定的速率往桶里发放令牌。然后消费者每次要取到令牌(acquire)才可以响应请求

优点:由于令牌是固定间隔发放的,假设还是5qps,如果我有1s内没有请求,我的令牌桶就满了,可以一瞬间响应5个请求(一次过取5个令牌),也就是可以应对瞬时流量。

那么这里也涉及一个固定间隔发放的问题,难道也是需要定时任务往”桶里“放令牌吗?

那我们来看下Guava怎么搞的,先看下文章中的例子比较好

假设限流为2qps,那么固定发放令牌的时间stableIntervalMicros就是500ms,初始化的storedPermits当前桶里的令牌数是0。

在这里插入图片描述
在这里插入图片描述

操作说明 requiredPermits请求多少令牌 storedPermits桶里还有多少令牌 nextFreeTicketMicros下一次能获得令牌的时间 当前时间
初始化 none 0 0 0
直接获取10个令牌,直接预支付成功,获取成功 10 0 0 + 500 * 10 = 5000 0
按照上一步的计算,下次可以获得令牌的时间是5000ms,过了5000ms,sleep睡醒了,这时候再执行acquire(1) 1 0 5000+500*1=5500 5000

debug看下分析对不对:
在这里插入图片描述

第1次获取10个令牌

nowMicro是刚开始运行的时间,是一个很小的数,约等于0;
resync(nowMicro),更新令牌数,由于nowMicro约等于0,其实令牌数不会更新((0-0)/5000 = 0),令牌数还是0(约等于0)
storedPermitsToSpend,其实当前并没有令牌,所以取min,约等于0;
freshPermits,需要预支付10个令牌,约等于10;
预支付之后需要等待10*interval = 10 * 500 ,约等于5000ms,5000000微秒
this.nextFreeTicketMicros 需要加上 waitMicros 也就是 下一次可以获得令牌的时间是5000ms之后。

所以我们看到输出信息的第一行在第0s获取了10个令牌之后,下一次再想获取1个令牌需要等待5000ms也就是5s。

第2次获取1个令牌

然后再一次想获取1个令牌,当前时间还是约等于0,这时候resync,nowMicros(0)比nextFreeTicketMicros(5000)小,令牌不更新。returnValue=5000,storedPermitsToSpend=0,freshPermits=1,需要再等 waitMicros=1 * 500ms,然后nextFreeTicketMicros更新为5000+500=5500,返回returnValue=5000;外层函数睡眠5000ms,返回5000(输出打印获取1个token,约5s)

第3次获取10个令牌

上面说的,睡了5000ms,当前时间nowMicros=5000; resync,nowMicros(5000)比nextFreeTicketMicros(5500)小,令牌不更新,还是欠费状态,只能预支付。returnValue=5500, storedPermitsToSpend=0,freshPermits=10,需要预支付10个令牌, waitMicros=10 * 500ms = 5000,然后nextFreeTicketMicros更新为5500+5000=10500,返回returnValue=5500;外层函数睡眠5500-5000=500ms,返回500(输出打印获取10个token,约0.5s)

小结

所以咱们这个令牌算法的思路是怎样的呢?

维护一个nextFreeTicketMicros,记录下一次可以获得令牌的时间;

每次来acquire请求,都会先更新令牌数( (当前时间-nextFreeTicketMicros)/间隔时间,同时不能大于最大值 ),然后计算waitMicros = 设定间隔 * 请求数, 并将 nextFreeTicketMicros += waitMicros,然后sleep,并返回成功。

高并发的情况

高并发的情况下是怎么样的呢?我们会在每个请求里用acuiqre()来限流,每次只获取一个令牌,那么假设1s内来了10个请求,只有前2个可以acquire成功,第3个会sleep 500*1, 第4个会sleep 500 * 2, 第5个会sleep 500 * 3,以此类推。

总结

再难啃的代码,一行一行debug,用脑子当计算机过几遍,总会有一点理解的,不要害怕不要虚,奥利给!

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章