在高并发的系统中,限流已作为必不可少的功能,而常见的限流算法有:计数器、滑动窗口、令牌桶、漏斗(漏桶)。其中滑动窗口算法、令牌桶和漏斗算法应用最为广泛。
专注于为中小企业提供网站设计、成都做网站服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业湘东免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了上1000家企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。
这里不再对计数器算法和滑动窗口作介绍了,有兴趣的同学可以参考其它相关文章。
非常很好理解,就像有一个漏斗容器一样,漏斗上面一直往容器里倒水(请求),漏斗下方以 固定速率 一直流出(消费)。如果漏斗容器满的情况下,再倒入的水就会溢出,此时表示新的请求将被丢弃。可以看到这种算法在应对大的突发流量时,会造成部分请求弃用丢失。
可以看出漏斗算法能强行限制数据的传输速率。
令牌桶算法
从某种意义上来说,令牌算法是对漏斗算法的一种改进。对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发情况。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。
令牌桶算法是指一个固定大小的桶,可以存放的令牌的最大个数也是固定的。此算法以一种 固定速率 不断的往桶中存放令牌,而每次请求调用前必须先从桶中获取令牌才可以。否则进行拒绝或等待,直到获取到有效令牌为止。如果桶内的令牌数量已达到桶的最大允许上限的话,则丢弃令牌。
Golang标准库中的限制算法是基于令牌桶算法(Token Bucket) 实现的,库名为golang.org/x/time/rate
对于限流器的消费方式有三种,分别为 Allow()、 Wait()和 Reserve()。前两种内部调用的都是Reserve() ,每个都对应一个XXXN()的方法。如Allow()是AllowN(t, 1)的简写方式。
主要用来限速控制并发事件,采用令牌池算法实现。
使用 NewLimiter(r Limit, b int) 函数创建限速器,令牌桶容量为b。初始化状态下桶是满的,即桶里装有b 个令牌,以后再以每秒往里面填充 r 个令牌。
允许声明容量为0的限速器,此时将会拒绝所有操作。
// As a special case, if r == Inf (the infinite rate), b is ignored.
有一种特殊情况,就是 r == Inf 时,此时b参数将被忽略。
Limiter 提供了三个主要函数 Allow, Reserve, 和 Wait. 大部分时候使用Wait。其中 AllowN, ReserveN 和 WaitN 允许消费n个令牌。
每个方法都可以消费一个令牌,当没有可用令牌时,三个方法的处理方式不一样
AllowN方法表示,截止在某一时刻,目前桶中数目是否至少为n个。如果条件满足,则从桶中消费n个token,同时返回true。反之不消费Token,返回false。
使用场景:一般用在如果请求速率过快,直接拒绝请求的情况
输出
当使用Wait方法消费Token时,如果此时桶内Token数量不足(小于N),那么Wait方法将会阻塞一段时间,直至Token满足条件。否则直接返回。
// 可以看到Wait方法有一个context参数。我们可以设置context的Deadline或者Timeout,来决定此次Wait的最长时间。
输出
// 此方法有一点复杂,它返回的是一个*Reservation类型,后续操作主要针对的全是这个类型
// 判断限制器是否能够在指定时间提供指定N个请求令牌。
// 如果Reservation.OK()为true,则表示需要等待一段时间才可以提供,其中Reservation.Delay()返回需要的延时时间。
// 如果Reservation.OK()为false,则Delay返回InfDuration, 此时不想等待的话,可以调用 Cancel()取消此次操作并归还使用的token
输出
学完了 net/http 和 fasthttp 两个HTTP协议接口的客户端实现,接下来就要开始Server的开发,不学不知道一学吓一跳,居然这两个库还支持Server的开发,太方便了。
相比于Java的HTTPServer开发基本上都是使用Spring或者Springboot框架,总是要配置各种配置类,各种 handle 对象。Golang的Server开发显得非常简单,就是因为特别简单,或者说没有形成特别统一的规范或者框架,我发现了很多实现方式,HTTP协议基于还是 net/http 和 fasthttp ,但是 handle 语法就多种多样了。
先复习一下: Golang语言HTTP客户端实践 、 Golang fasthttp实践 。
在Golang语言方面,实现某个功能的库可能会比较多,有机会还是要多跟同行交流,指不定就发现了更好用的库。下面我分享我学到的六种Server开发的实现Demo。
基于 net/http 实现,这是一种比较基础的,对于接口和 handle 映射关系处理并不优雅,不推荐使用。
第二种也是基于 net/http ,这种编写语法可以很好地解决第一种的问题,handle和path有了类似配置的语法,可读性提高了很多。
第三个基于 net/http 和 github点抗 /labstack/echo ,后者主要提供了 Echo 对象用来处理各类配置包括接口和handle映射,功能很丰富,可读性最佳。
第四种依然基于 net/http 实现,引入了 github点抗 /gin-gonic/gin 的路由,看起来接口和 handle 映射关系比较明晰了。
第五种基于 fasthttp 开发,使用都是 fasthttp 提供的API,可读性尚可,handle配置倒是更像Java了。
第六种依然基于 fasthttp ,用到了 github点抗 /buaazp/fasthttprouter ,有点奇怪两个居然不在一个GitHub仓库里。使用语法跟第三种方式有点类似,比较有条理,有利于阅读。
做为本文的前言,首先向读者介绍一下降级、熔断和限流的概念与关系。也许很多人对此,早已谙熟于心,但是烦请允许我再啰嗦几句,方便第一次接触该领域的小伙伴们,都可以的理解消化本文。
所谓限流,本质就是对系统的被请求频率以及内部的部分功能的执行频率加以限制,防止因突发的流量激增,导致整个系统不可用。当流量出现激增,触发限流,那么对于那些系统暂时不想或无法处理的“流量”,我们该如何处理呢?这就自然引出了服务降级的概念,其本质就是提供降低系统正常运行所能提供的功能数,亦或是降低某些功能完成的完整度(质量)。而熔断就是众多降级手段中最常见的一种,其在流量过大时(或下游服务出现问题时),可以自动断开与下游服务的交互,并可以通过自我诊断下游系统的错误是否已经修正,或上游流量是否减少至正常水平,来恢复自我恢复。
简而言之,限流是从系统的流量入口考虑,从进入的流量上进行限制,达到保护系统的作用;降级,是从系统内部的平级服务或者业务的维度考虑,流量大了,可以干掉一些,保护其他正常使用;熔断强调的是服务之间的调用能实现自我恢复的状态;
Hystrix的golang版本项目地址是:
Hystrix是Netflix开源的一个限流熔断的项目、主要有以下功能:
项目地址为:
gobreaker是索尼的开源的一个限流熔断的项目,是基于《微软云设计模式》一书中的熔断器模式的 Golang 实现的,本质利用的还是原子计数法、主要有以下功能: