利用Redis如何实现令牌桶算法

60次阅读
没有评论

共计 3762 个字符,预计需要花费 10 分钟才能阅读完成。

丸趣 TV 小编给大家分享一下利用 Redis 如何实现令牌桶算法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

在限流算法中有一种令牌桶算法,该算法可以应对短暂的突发流量,这对于现实环境中流量不怎么均匀的情况特别有用,不会频繁的触发限流,对调用方比较友好。

例如,当前限制 10qps,大多数情况下不会超过此数量,但偶尔会达到 30qps,然后很快就会恢复正常,假设这种突发流量不会对系统稳定性产生影响,我们可以在一定程度上允许这种瞬时突发流量,从而为用户带来更好的可用性体验。这就是使用令牌桶算法的地方。

令牌桶算法原理

如下图所示,该算法的基本原理是:有一个容量为 X 的令牌桶,每 Y 单位时间内将 Z 个令牌放入该桶。如果桶中的令牌数量超过 X,那么它将被丢弃。处理请求时,需要先从令牌桶中取出令牌,如果拿到了令牌,则继续处理;如果拿不到令牌,则拒绝请求。

可以看出,在令牌桶算法中设置 X,Y 和 Z 的数量尤为重要。Z 应该比每 Y 单位时间内的请求数稍大,系统将长时间处于此状态;X 是系统允许的瞬时最大请求数,并且系统不应该长时间处于此状态,否则就会频繁触发限流,此时表明流量出现了超预期的情况,需要及时调查原因并采取相应措施。

Redis 实现令牌桶算法

之前看过有些程序实现的令牌桶,其向桶中放入令牌的方法是启动一个线程,每隔 Y 单位时间增加一次令牌数量,或者在 Timer 中定时执行这一过程。我不太满意这种方法,原因有二,一是浪费线程资源,二是因为调度的问题执行时间不精确。【相关推荐:Redis 视频教程】

这里确定令牌桶中令牌数量的方法是通过计算得出,首先算出从上次请求到这次请求经过了多长时间,是否达到发令牌的时间阈值,然后增加的令牌数是多少,这些令牌能够放到桶中的是多少。

Talk is cheap!

下边就来看看 Redis 中怎么实现的,因为涉及到多次与 Redis 的交互,这里为了提高限流处理的吞吐量,减少程序与 Redis 的交互次数,采用了 Redis 支持的 Lua script,Lua script 的执行是原子的,所以也不用担心出现脏数据的问题。

代码节选自 FireflySoft.RateLimit,它不仅支持普通主从部署 Redis,还支持集群 Redis,所以吞吐量可以通过水平扩展的方式进行提升。为了方便阅读,这里增加一些注释,实际是没有的。

--  定义返回值,是个数组,包含:是否触发限流(1 限流  0 通过)、当前桶中的令牌数
local ret={}
ret[1]=0
-- Redis 集群分片 Key,KEYS[1]是限流目标
local cl_key =  { .. KEYS[1] ..  } 
--  获取限流惩罚的当前设置,触发限流惩罚时会写一个有过期时间的 KV
--  如果存在限流惩罚,则返回结果[1,-1]
local lock_key=cl_key ..  -lock 
local lock_val=redis.call(get ,lock_key)
if lock_val ==  1  then
 ret[1]=1
 ret[2]=-1
 return ret;
--  这里省略部分代码
--  获取[上次向桶中投放令牌的时间],如果没有设置过这个投放时间,则令牌桶也不存在,此时:--  一种情况是:首次执行,此时定义令牌桶就是满的。--  另一种情况是:较长时间没有执行过限流处理,导致承载这个时间的 KV 被释放了,--  这个过期时间会超过自然投放令牌到桶中直到桶满的时间,所以令牌桶也应该是满的。local last_time=redis.call(get ,st_key)
if(last_time==false)
 --  本次执行后剩余令牌数量:桶的容量 -  本次执行消耗的令牌数量
 bucket_amount = capacity - amount;
 --  将这个令牌数量更新到令牌桶中,同时这里有个过期时间,如果长时间不执行这个程序,令牌桶 KV 会被回收
 redis.call(set ,KEYS[1],bucket_amount, PX ,key_expire_time)
 --  设置[上次向桶中放入令牌的时间],后边计算应放入桶中的令牌数量时会用到
 redis.call(set ,st_key,start_time, PX ,key_expire_time)
 --  返回值[当前桶中的令牌数]
 ret[2]=bucket_amount
 --  无需其它处理
 return ret
--  令牌桶存在,获取令牌桶中的当前令牌数
local current_value = redis.call(get ,KEYS[1])
current_value = tonumber(current_value)
--  判断是不是该放入新令牌到桶中了:当前时间 - 上次投放的时间   =  投放的时间间隔
last_time=tonumber(last_time)
local last_time_changed=0
local past_time=current_time-last_time
if(past_time inflow_unit)
 --  不到投放的时候,直接从令牌桶中取走令牌
 bucket_amount=current_value-amount
 --  需要放入一些令牌,  预计投放数量  = (距上次投放过去的时间 / 投放的时间间隔)* 每单位时间投放的数量
 local past_inflow_unit_quantity = past_time/inflow_unit
 past_inflow_unit_quantity=math.floor(past_inflow_unit_quantity)
 last_time=last_time+past_inflow_unit_quantity*inflow_unit
 last_time_changed=1
 local past_inflow_quantity=past_inflow_unit_quantity*inflow_quantity_per_unit
 bucket_amount=current_value+past_inflow_quantity-amount
--  这里省略部分代码
ret[2]=bucket_amount
--  如果桶中剩余数量小于 0,则看看是否需要限流惩罚,如果需要则写入一个惩罚 KV,过期时间为惩罚的秒数
if(bucket_amount 0)
 if lock_seconds 0 then
 redis.call(set ,lock_key, 1 , EX ,lock_seconds, NX)
 end
 ret[1]=1
 return ret
--  来到这里,代表可以成功扣减令牌,则需要更新令牌桶 KV
if last_time_changed==1 then
 redis.call(set ,KEYS[1],bucket_amount, PX ,key_expire_time)
 --  有新投放,更新 [上次投放时间] 为本次投放时间
 redis.call(set ,st_key,last_time, PX ,key_expire_time)
 redis.call(set ,KEYS[1],bucket_amount, PX ,key_expire_time)
return ret

通过以上代码,可以看出,其主要处理过程是:

1、判断有没有被限流惩罚,有则直接返回,无则进入下一步。

2、判断令牌桶是否存在,不存在则先创建令牌桶,然后扣减令牌返回,存在则进入下一步。

3、判断是否需要投放令牌,不需要则直接扣减令牌,需要则先投放令牌再扣减令牌。

4、判断扣减后的令牌数,如果小于 0 则返回限流,同时设置限流惩罚,如果大于等于 0 则进入下一步。

5、更新桶中的令牌数到 Redis。

你可以在任何一种开发语言的 Redis 库中提交并运行这段 Lua script 脚本,如果你使用的是.NET 平台,可以参考这篇文章:ASP.NET Core 中使用令牌桶限流(https://blog.bossma.cn/dotnet/asp-net-core-token-bucket-algorithm-of-rate-limit/)。

关于 FireflySoft.RateLimit

FireflySoft.RateLimit 是一个基于 .NET Standard 的限流类库,其内核简单轻巧,能够灵活应对各种需求的限流场景。

其主要特点包括:

多种限流算法:内置固定窗口、滑动窗口、漏桶、令牌桶四种算法,还可自定义扩展。

多种计数存储:目前支持内存、Redis 两种存储方式。

分布式友好:通过 Redis 存储支持分布式程序统一计数。

限流目标灵活:可以从请求中提取各种数据用于设置限流目标。

支持限流惩罚:可以在客户端触发限流后锁定一段时间不允许其访问。

动态更改规则:支持程序运行时动态更改限流规则。

自定义错误:可以自定义触发限流后的错误码和错误消息。

普适性:原则上可以满足任何需要限流的场景。

以上是“利用 Redis 如何实现令牌桶算法”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注丸趣 TV 行业资讯频道!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-17发表,共计3762字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)