聊聊限流

2022-12-11 0 455

聊聊限流

大家好,我是楼仔!

在B2C高mammalian情景下,他们经常会使用一些常见形式,去应对网络流量高峰期,比如开闭、TNUMBERAP、降班,今天他们谈谈开闭。

什么是开闭呢?开闭是管制到达控制系统的mammalian允诺数量,保证控制系统能够正常积极响应部分使用者请求,而对少于管制的网络流量,则通过DNS的形式保证整体控制系统的易用性。

依照开闭作用范围,能分成FPS开闭和分布式系统开闭;依照开闭形式,又分成计时器、翻转询问处、漏桶涂端玉牌桶开闭,下面他们对那块详细展开传授。

文章大部分内容参照 TPaper 的《Go 实现各类开闭演算法》

,杨一!

常见开闭形式

计时器

计时器是一种最简单开闭演算法,其原理是:在一段天数间距内,对允诺展开算数,与danger展开比较推论与否需要开闭,一旦到了天数临界值,将计时器应缴。

那个就像你去坐火车一样,客舱规定了多少个位置,满了就不让下车了,要不然是超员了,被交警部门舅舅捉到了就要罚金的,假如他们的控制系统那就不是罚金的事情了,可能直接崩掉了。

程序代码逻辑:

能在程序中设置两个表达式 count,当过来两个允诺我就将那个数 +1,同时记录允诺天数。当今社会两个允诺来的这时候推论 count 的算值与否少于预设的频度,以及当前允诺的天数和第一次允诺天数与否在 1 两分钟内。假如在 1 两分钟内并且少于预设的频度则证明允诺过多,后面的允诺就拒绝掉。假如该允诺与第两个允诺的间距天数小于算数周期,且 count 值还在开闭范围内,就抹除 count。

所以难题来了,假如有位需求对某一USB /query 每两分钟最多允许出访 200 次,假定有位使用者在第 59 秒的最后几微秒一瞬间推送 200 个允诺,当 59 秒结束后 Counter 应缴了,他在下一五分钟的这时候又发送 200 个允诺。

所以在 1 五分钟内那个使用者推送了 2 倍的允诺,那个是符合他们的结构设计方法论的,这也是计时器形式的结构设计瑕疵,控制系统可能会忍受蓄意使用者的大量允诺,甚至打穿控制系统。这种形式虽然简单,但也有位大难题是没有较好的处理单位天数的边界线。

聊聊限流

不过说实话,那个算数引用了锁,在高mammalian情景,那个形式可能不太实用,我建议将锁去掉,然后将 l.count++ 的方法论通过原子算数处理,这样就能保证 l.count 自增时不会被多个线程同时执行,即通过原子算数的形式实现开闭。

为了不影响阅读,代码详见:https://github.com/lml200701158/go_demo/blob/master/current_limit/count.go

翻转询问处

翻转询问处是针对计时器存在的临界值瑕疵,所谓翻转询问处(Sliding window)是一种网络流量控制技术,那个词出现在 TCP 协议中。翻转询问处把固定天数片进行划分,并且随着天数的流逝,展开移动,固定数量的能移动的格子,展开算数并推论danger。

聊聊限流

上图中他们用红色的虚线代表两个天数询问处(一两分钟),每个天数询问处有 6 个格子,每个格子是 10 五分钟。每过 10 秒钟天数询问处向右移动一格,能看红色箭头的方向。他们为每个格子都设置两个独立的计时器 Counter,假如两个允诺在 0:45 出访了所以他们将第五个格子的计时器 +1(也是是 0:40~0:50),在推论开闭的这时候需要把所有格子的算数加起来和预设的频度展开比较即可。

所以翻转询问处如何解决他们上面遇到的难题呢?来看下面的图:

聊聊限流

当使用者在 0:59 五分钟推送了 200 个允诺就会被第六个格子的计时器记录 +200,当今社会一五分钟的这时候天数询问处向右移动了两个,此时计时器已经记录了该使用者推送的 200 个允诺,所以再推送的话就会触发开闭,则拒绝新的允诺。

其实计时器是翻转询问处啊,只不过只有两个格子而已,所以想让开闭做的更精确只需要划分更多的格子就能了,为了更精确他们也不知道到底该设置多少个格子,格子的数量影响着翻转询问处演算法的精度,依然有天数片的概念,无法根本解决临界值难题。

为了不影响阅读,代码详见:https://github.com/RussellLuo/slidingwindow

漏桶

漏桶演算法(Leaky Bucket),原理是两个固定容量的漏桶,按照固定速率流出水滴。

用过水龙头都知道,打开龙头开关水就会流下滴到水桶里,而漏桶指的是水桶下面有位漏洞能出水,假如水龙头开的特别大所以水流速就会过大,这样就可能导致水桶的水满了然后溢出。

聊聊限流

图片假如看不清,可单击图片并放大。

两个固定容量的桶,有水流进来,也有水流出去。对流进来的水来说,他们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对流出去的水来说,那个桶能固定水流出的速率(处理速度),从而达到网络流量整形和网络流量控制的效果。

漏桶演算法有以下特点:

漏桶具有固定容量,出水速率是固定常量(流出允诺)假如桶是空的,则不需流出水滴能以任意速率流入水滴到漏桶(流入允诺)假如流入水滴超出了桶的容量,则流入的水滴溢出(新允诺被拒绝)

漏桶管制的是常量流出速率(即流出速率是两个固定常量值),所以最大的速率是出水的速率,不能出现突发网络流量。

为了不影响阅读,代码详见:https://github.com/lml200701158/go_demo/blob/master/current_limit/leaky_bucket.go

令牌桶

令牌桶演算法(Token Bucket)是网络网络流量整形(Traffic Shaping)和速率管制(Rate Limiting)中最常使用的一种演算法。典型情况下,令牌桶演算法用来控制推送到网络上的数据的数目,并允许突发数据的推送。

聊聊限流

图片假如看不清,可单击图片并放大。

他们有两个固定的桶,桶里存放着令牌(token)。一开始桶是空的,控制系统按固定的天数(rate)往桶里添加令牌,直到桶里的令牌数满,多余的允诺会被丢弃。当允诺来的这时候,从桶里移除两个令牌,假如桶是空的则拒绝允诺或者阻塞。

令牌桶有以下特点:

令牌按固定的速率被放入令牌桶中桶中最多存放 B 个令牌,当桶满时,新添加的令牌被丢弃或拒绝假如桶中的令牌不足 N 个,则不会删除令牌,且允诺将被开闭(丢弃或阻塞等待)

令牌桶管制的是平均流入速率(允许突发允诺,只要有令牌就能处理,支持一次拿3个令牌,4个令牌…),并允许一定程度突发网络流量,所以也是非常常见的开闭演算法。

为了不影响阅读,代码详见:https://github.com/lml200701158/go_demo/blob/master/current_limit/token_bucket.go

Redis + Lua 分布式系统开闭

FPS版开闭仅能保护自身节点,但无法保护应用依赖的各种服务,并且在展开节点扩容、缩容时也无法准确控制整个服务的允诺管制。

而分布式系统开闭,以集群为维度,能方便的控制那个集群的允诺管制,从而保护下游依赖的各种服务资源。

分布式系统开闭最关键的是要将开闭服务做成原子化,他们能借助 Redis 的计时器,Lua 执行的原子性,展开分布式系统开闭,大致的 Lua 脚本代码如下:

local key = “rate.limit:” .. KEYS[1] –开闭KEY local limit = tonumber(ARGV[1]) –开闭大小 local current = tonumber(redis.call(get, key) or “0”) if current + 1 > limit then –假如超出开闭大小 return 0 else –允诺数+1,并设置1秒过期 redis.call(“INCRBY”, key,”1″) redis.call(“expire”, key,”1″) return current + 1 end

开闭方法论(Java 语言):

public static boolean accquire() throws IOException, URISyntaxException { Jedis jedis = new Jedis(“127.0.0.1”); File luaFile = new File(RedisLimitRateWithLUA.class.getResource(“/”).toURI().getPath() + “limit.lua”); String luaScript = FileUtils.readFileToString(luaFile); String key = “ip:” + System.currentTimeMillis()/1000; // 当前秒 String limit = “5”; // 最大管制 List<String> keys = new ArrayList<String>(); keys.add(key); List<String> args = new ArrayList<String>(); args.add(limit); Long result = (Long)(jedis.eval(luaScript, keys, args)); // 执行lua脚本,传入参数 return result == 1; }

谈谈其它

上面的开闭形式,主要是针对服务器展开开闭,他们也能对容器展开开闭,比如 Tomcat、Nginx 等开闭手段。

Tomcat 能设置最大线程数(maxThreads),当mammalian少于最大线程数会排队等待执行;而 Nginx 提供了两种开闭手段:一是控制速率,二是控制mammalian连接数。

对 Java 语言,他们其实有相关的开闭组件,比如大家常见的 RateLimiter,其实是基于令牌桶演算法,大家知道为什么唯独选用令牌桶么?

对 Go 语言,也有该语言特定的开闭形式,比如能通过 channel 实现mammalian控制开闭,也支持第三方库 httpserver 实现开闭,详见这篇 《Go 开闭的常见形式》

在实际的开闭情景中,他们也能控制单个 IP、城市、渠道、设备 id、使用者 id 等在一定天数内推送的允诺数;假如是开放平台,需要为每个 appkey 设置独立的出访速率规则。

开闭对比

下面他们就对常见的线程策略,总结它们的优缺点,便于以后选型。

计时器:

优点:固定天数段算数,实现简单,适用不太精准的情景;缺点:对边界线没有较好处理,导致开闭不能精准控制。

翻转询问处:

优点:将固定天数段分块,天数比“计时器”复杂,适用于稍微精准的情景;缺点:实现稍微复杂,还是不能彻底解决“计时器”存在的边界线难题。

漏桶:

优点:能较好的控制消费频率;缺点:实现稍微复杂,单位天数内,不能多消费,感觉不太灵活。

令牌桶:

优点:能解决“漏桶”不能灵活消费的难题,又能避免过渡消费,强烈推荐;缺点:实现稍微复杂,其它缺点没有想到。

Redis + Lua 分布式系统开闭:

优点:支持分布式系统开闭,有效保护下游依赖的服务资源;缺点:依赖 Redis,对边界线没有较好处理,导致开闭不能精准控制。 尽信书则不如无书,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激。

往期精选:

如何看待程序员 35 岁职业危机?Java 全套学习资料(14W 字),耗时半年整理我肝了三个月,为你写出了 GO 核心手册消息队列:从选型到原理,一文带你全部掌握微服务网关选型,请收下我的膝盖!5 种注册中心如何选型?从原理给你解读!肝了两个月的 ETCD,从 Raft 原理到实践更多!更多!更多!三连击!!!

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务