令牌桶 限速_Go 限流器实战系列(2) Token Bucket 令牌桶

上一篇说到 Leaky Bucket 能限制客户端的访问速率, 但是无法应对突发流量, 本质原因就是漏斗桶只是为了保证固定时间内通过的流量是一样的. 面对这种情况, 本篇文章继续介绍另外一种限流器: Token Bucket -- 令牌桶

什么是 Token Bucket

漏斗桶的桶空间就那么大, 其只能保证桶里的请求是匀速流出, 并不关心流入的速度, 只要桶溢出了就服务拒绝, 这可能并不符合互联网行业的使用场景.

试想这样的场景, 尽管服务器的 QPS 已经达到限速阈值了, 但是并不想将所有的流量都拒之门外, 仍然让部分流量能够正常通过限流器. 这样我们的服务器在面对突发流量时能够有一定的伸缩空间, 而不是一直处于不可用状态.

基于上面的场景需求, 令牌桶采用跟漏斗桶截然不同的做法.

a67d4994d0644ba7faa581307c64aed9.png
令牌桶

令牌桶也有自己的固定大小, 我们设置 QPS 为 100, 在初始化令牌桶的时候, 就会立即生成 100 个令牌放到桶里面, 同时还按照一定的速率, 每隔一定的时间产生固定数量的令牌放入到桶中. 如果桶溢出了, 则舍弃生成的令牌.

只要有请求能够拿到令牌, 就能保证其通过限流器. 当然拿不到令牌的请求只能被无情拒绝了(或者等待令牌产生), 这个请求的命不好~

面对突然爆发的流量, 可能大部分流量都被限流器给挡住了, 但是也有部分流量刚好拿到了刚刚生成的 Token, 就这样在千军万马中通过了限流器. 相对于漏斗桶来说, 令牌桶更适合现在互联网行业的需要, 是被广泛使用的限流算法

如何设置令牌桶的大小和产生令牌的速率?

答: 多进行生产环境的压测, 根据集群的实际承受能力设置相应桶的大小和产生令牌的速率. 血的经验告诉我, 周期性的线上压测是一件很重要的事情(使用local cache的程序, 压测的时候一定要记得先临时关闭它)

juju/ratelimit

juju/ratelimit 是大部分项目都在使用的 golang 令牌桶的实现方案. 下面会结合其用法, 源码剖析令牌桶的实现的方案.

gin 中间件

package main

import (
 "fmt"
 "log"
 "net/http"
 "time"

 "github.com/gin-gonic/gin"
 "github.com/juju/ratelimit"
)

var limiter = ratelimit.NewBucketWithQuantum(time.Second, 10, 10)

func tokenRateLimiter() gin.HandlerFunc {
 fmt.Println("token create rate:", limiter.Rate())
 fmt.Println("available token :", limiter.Available())
 return func(context *gin.Context) {
  if limiter.TakeAvailable(1) == 0 {
   log.Printf("available token :%d", limiter.Available())
   context.AbortWithStatusJSON(http.StatusTooManyRequests, "Too Many Request")
  } else {
   context.Writer.Header().Set("X-RateLimit-Remaining", fmt.Sprintf("%d", limiter.Available()))
   context.Writer.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%d", limiter.Capacity()))
   context.Next()
  }
 }
}

func main() {
 e := gin.Default()
 e.GET("/test", tokenRateLimiter(), func(context *gin.Context) {
  context.JSON(200, true)
 })
 e.Run(":9091")
}

Output:

token create rate: 100
available token : 100
[GIN] 2020/07/03 - 17:34:37 | 200 |     157.505µs |       127.0.0.1 | GET      /test
[GIN] 2020/07/03 - 17:34:37 | 200 |     310.898µs |       127.0.0.1 | GET      /test
[GIN] 2020/07/03 - 17:34:37 | 200 |       61.64µs |       127.0.0.1 | GET      /test
[GIN] 2020/07/03 - 17:34:37 | 200 |       8.677µs |       127.0.0.1 | GET      /test
[GIN] 2020/07/03 - 17:34:37 | 200 |       6.145µs |       127.0.0.1 | GET      /test
[GIN] 2020/07/03 - 17:34:37 | 200 |      23.576µs |       127.0.0.1 | GET      /test
[GIN] 2020/07/03 - 17:34:37 | 200 |       5.617µs |       127.0.0.1 | GET      /test
.....
[GIN] 2020/07/03 - 17:35:03 | 429 |    6.193792ms |       127.0.0.1 | GET      /test
[GIN] 2020/07/03 - 17:35:03 | 200 |       8.509µs |       127.0.0.1 | GET      /test [GIN] 2020/07/03 - 17:35:03 | 429 |      10.324µs |       127.0.0.1 | GET      /test
....

有下面特点:

  1. 令牌桶初始化后里面就有 100 个令牌
  2. 每秒钟会产生 100 个令牌, 保证每秒最多有 100 个请求通过限流器, 也就是说 QPS 的上限是 100
  3. 流量过大时能够启动限流, 在限流过程中, 仍然能让部分流量通过

源码分析

初始化

建议使用初始化函数有下面三种:

  • NewBucket(fillInterval time.Duration, capacity int64): 默认的初始化函数, 每一个周期内生成 1 个令牌, 默认 quantum = 1
  • NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) : 跟 NewBucket 类似, 每个周期内生成 quantum 个令牌
  • NewBucketWithRate(rate float64, capacity int64): 每秒产生 rate 速率的令牌

其实初始化函数还有很多种, 但基本上大同小异, 最后都是调用 NewBucketWithQuantumAndClock.

func NewBucketWithQuantumAndClock(fillInterval time.Duration, capacity, quantum int64, clock Clock) *Bucket {
 // ....
 return &Bucket{
  clock:           clock,
  startTime:       clock.Now(),
  latestTick:      0,            // 上一次产生token的记录点
  fillInterval:    fillInterval, // 产生token的间隔
  capacity:        capacity,     // 桶的大小
  quantum:         quantum,      // 每秒产生token的速率
  availableTokens: capacity,     // 桶内可用的令牌的个数
 }
}

Rate

func (tb *Bucket) Rate() float64 {
 return 1e9 * float64(tb.quantum) / float64(tb.fillInterval)
}

time.Duration 实际的是以 nanosecond 试试呈现的, 就是 1e9, 1e9 / float64(tb.fillInterval) 的结果就是 1/tb.fillInterval 秒.

于是令牌桶产生令牌的速率是: 每秒内产生 float64(tb.quantum) / float64(tb.fillInterval)

TakeAvailable

func (tb *Bucket) currentTick(now time.Time) int64 {
 // 由于 tb.quantum 是每秒产生的token的数量. 于是计算从bucket初始化的startTime到现在now的时间间隔 t,
 // t/tb.fillInterval * tb.quantum 计算的是从开始到现在应该产生的 token 数量
 return int64(now.Sub(tb.startTime) / tb.fillInterval)
}

func (tb *Bucket) adjustavailableTokens(tick int64) {
 if tb.availableTokens >= tb.capacity { // 如果令牌的可用数量已经达到桶的容量, 直接返回
  return
 }
 
 // tick * tb.quantum 是从bucket初始化到本次请求应该产生的 token的数量
 // tb.latestTick 是从bucket初始化到上次请求应该产生的 token的数量
 // tick * tb.quantum - tb.latestTick 计算出两次请求间应该产生的token数量
 // tb.availableTokens += (tick - tb.latestTick) * tb.quantum: 桶内剩余的token数量 + 新产生的token数量
 tb.availableTokens += (tick - tb.latestTick) * tb.quantum
 if tb.availableTokens > tb.capacity {
  tb.availableTokens = tb.capacity // 如果产生的令牌数量超过了桶的容量, 则桶内剩余的令牌数量等于桶的size
 }
 tb.latestTick = tick
 return
}

func (tb *Bucket) takeAvailable(now time.Time, count int64) int64 {
 if count <= 0 {
  return 0
 }
 tb.adjustavailableTokens(tb.currentTick(now))
 if tb.availableTokens <= 0 { // 如果桶内剩余token数量小于等于0, 直接返回0
  return 0
 }
 if count > tb.availableTokens {
  count = tb.availableTokens
 }
 tb.availableTokens -= count
 return count
}

// 如果返回值是0, 代表桶内已经没有令牌了
func (tb *Bucket) TakeAvailable(count int64) int64 {
 tb.mu.Lock()
 defer tb.mu.Unlock()
 return tb.takeAvailable(tb.clock.Now(), count)
}

TakeAvailable 是 Token Bucket的核心函数. 从这个实现我们能看到

  1. jujue/ratelimit 计算出请求间隔中应该产生的token的数量, 并不是另外启动一个 goroutine 专门定时产生固定数量的token
  2. 桶内令牌在产生过程中是累加的, 同时减去每次调用消耗的数量
  3. 初始化后桶内的令牌数量就是桶的大小
bef975acdd0ad480b7a2fb06ac8d6fc0.png
示意图

Token Bucket的缺陷

令牌桶算法能满足绝大部分服务器限流的需要, 是被广泛使用的限流算法, 不过其也有一些缺点:

  1. 令牌桶是没有优先级的,无法让重要的请求先通过
  2. OP可能因为硬件故障去调整资源, 系统负载也会随着在变化, 如果对服务限流进行缩容和扩容,需要人为手动去修改,运维成本比较大
  3. 令牌桶只能对局部服务端的限流, 无法掌控全局资源

下一篇我们看alibaba/Sentinel, kratos 的 bbr 算法是如何做到系统自适应限流

参考

[1] 分布式服务限流实战,已经为你排好坑了 https://www.infoq.cn/article/Qg2tX8fyw5Vt-f3HH673)

[2] 维基百科--Token_bucket https://en.wikipedia.org/wiki/Token_bucket

[3] juju/ratelimit https://github.com/juju/ratelimit

[4] B 站在微服务治理中的探索与实践 https://www.infoq.cn/article/zRuGHM_SsQ0lk7gWyBgG

[5] 限流器系列(1) -- Leaky Bucket 漏斗桶 https://www.haohongfan.com/post/2020-06-27-leaky-bucket/

推荐阅读

  • Go 限流器实战系列(1) -- Leaky Bucket 漏斗桶


喜欢本文的朋友,欢迎关注“Go语言中文网”:

7889201aa06a19aae2f23ae5d6ce881c.png

Go语言中文网启用微信学习交流群,欢迎加微信:274768166,投稿亦欢迎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/497689.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里巴巴年度技术总结:人工智能在搜索的应用和实践

来源&#xff1a;雷锋网概要&#xff1a;本文梳理了过去一年多搜索在深度学习方向上的探索&#xff0c;概要的介绍了我们在深度学习系统、深度学习算法和搜索应用落地的进展和思考。以深度学习为代表的人工智能在图像、语音和 NLP 领域带来了突破性的进展&#xff0c;在信息检索…

2018 年最值得期待的学术进展——致人工智能研究者们的年终总结

来源&#xff1a;AI科技评论概要&#xff1a;这里&#xff0c;我们为大家奉上机器学习学者 Alex Honcha 所展望的 2018 年最可能产生突破的 AI 领域。2017年马上就要过去了&#xff0c;而 AI 也在2017年中得到了快速发展。研究人员们提出了很多有趣而又富有开创性的工作。而作为…

DARPA盘点2017年最受关注的十大科技新闻

来源&#xff1a;国防科技要闻2017年&#xff0c;DARPA国防颠覆性技术与能力方面的重大投资覆盖了从量子超材料、机器学习、神经技术到无人系统自主性的数十个领域约250个项目。DARPA官网全年共收获3500万次访问量。根据访问量排序&#xff0c;DARPA整理出最受关注的十大技术新…

观点 | 别忽视深度学习的种种问题,Gary Marcus 泼冷水义不容辞

来源&#xff1a;AI科技评论纽约大学心理学教授 Gary Marcus 曾是 Uber 人工智能实验室的负责人&#xff0c;他自己创立的人工智能创业公司 Geometric Intelligence 2016 年 12 月被 Uber 收购&#xff0c;自己也就加入 Uber 帮助他们建立人工智能实验室。Gary Marcus 也曾号召…

spring框架做全局异常捕获_@ControllerAdvice注解(全局异常捕获)

背景ControllerAdvice 注解 通常用于定义ExceptionHandler&#xff0c; InitBinder和ModelAttribute 适用于所有RequestMapping方法的方法。ExceptionHandler异常处理器作用:可以拦截程序抛出来的指定异常。使用场景:主要使用与项目统一异常处理&#xff0c;对于rest风格的返回…

DeepMind推出「控制套件」:为「强化学习智能体」提供性能基准

来源&#xff1a;arxiv作者&#xff1a;Yuval Tassa, Yotam Doron, Alistair Muldal, Tom Erez,Yazhe Li, Diego de Las Casas, David Budden, Abbas Abdolmaleki, Josh Merel,Andrew Lefrancq, Timothy Lillicrap, Martin Riedmiller「雷克世界」编译&#xff1a;嗯~阿童木呀、…

pytorch统计矩阵非0的个数_矩阵的三种存储方式---三元组法 行逻辑链接法 十字链表法...

在介绍矩阵的压缩存储前&#xff0c;我们需要明确一个概念&#xff1a;对于特殊矩阵&#xff0c;比如对称矩阵&#xff0c;稀疏矩阵&#xff0c;上&#xff08;下&#xff09;三角矩阵&#xff0c;在数据结构中相同的数据元素只存储一个。 [TOC] 三元组顺序表 稀疏矩阵由于其自…

【视频】2017,50个令人屏息的科技瞬间

来源&#xff1a;甲子光年概要&#xff1a;整个世界的巨变&#xff0c;肇始于一颗微尘的颤动。「甲子光年」挑选了属于2017年的50个科技瞬间。十年后再回望时&#xff0c;它们可能正是那个撼动世界的历史性时刻。即将过去的2017年&#xff0c;一定有这样的一瞬&#xff1a;整个…

机加工程序工时程序_准终工时、人工工时、机器工时,十个工程师九个会弄错...

​上一篇文章种蚂蚁先生跟大家详细分析了产品成本的组成&#xff0c;其主要分为材料成本和制造成本两个部分。 然而要得到制造成本&#xff0c;则必须有标准工时数据。那么标准工时究竟是什么呢&#xff1f;​标准工时制&#xff1a; 标准工时是在标准工作环境下&#xff0c;进…

互联网大脑的云反射弧路径选择问题,兼谈ET大脑模糊认知反演理论

互联网大脑的云反射弧路径选择问题研究2008年以来&#xff0c;我们在互联网大脑架构和互联网进化的研究中提到&#xff0c;“互联网大脑架构&#xff0c;就是互联网向与人类大脑高度相似的方向进化过程中&#xff0c;形成的类脑巨系统架构。互联网云脑架构具备不断成熟的类脑视…

如何传入比较器_typescript专题(五) 装饰器

欢迎来到我专题文章【typescript】&#xff0c;更多干货内容持续分享中&#xff0c;敬请关注&#xff01;本章目标基于webpack4.x从0开始搭建ts的开发环境ts中的装饰器的基本使用基于webpack4.x从0开始搭建ts的开发环境webpack4.x已经问世好久了&#xff0c;0配置是一大亮点&am…

『报告』IDC:2018年物联网产业10大预测

来源&#xff1a;T客汇编译概要&#xff1a;根据市场研究公司IDC的报告&#xff0c;2018年全球物联网支出总额将达到7720亿美元。新年新气象&#xff0c;2017年被称作物联网&#xff08;IoT&#xff09;元年&#xff0c;而2018年还将续写IoT的高歌猛进。根据市场研究公司IDC的报…

hbase获取表信息_HBase的读写和javaAPI的使用

一、hbase系统管理表hbase:namespace&#xff0c;记录了hbase中所有namespace的信息 ,当前系统下有哪些namespace信息scan hbase:namespacehbase:meta&#xff0c;记录了region信息scan hbase:meta二、读写思想client(get、scan)rowkey条件(1)由于rowkey是存储在region上的(2)判…

机器学习必知的15大框架

作者 | Devendra Desale译者 | Mags来源 | 云栖社区不管你是一个研究人员&#xff0c;还是开发者&#xff0c;亦或是管理者&#xff0c;想要使用机器学习&#xff0c;需要使用正确的工具来实现。本文介绍了当前最流行15个机器学习框架。机器学习工程师是开发产品和构建算法团队…

区分大小屏幕_第一个Python程序——在屏幕上输出文本

本节我将给大家介绍最简单、最常用的 Python 程序——在屏幕上输出一段文本&#xff0c;包括字符串和数字。Python 使用 print 函数在屏幕上输出一段文本&#xff0c;输出结束后会自动换行。在屏幕上输出字符串字符串就是多个字符的集合&#xff0c;由双引号" "或者单…

2018 年物联网发展五大趋势预测

来源&#xff1a;腾股创投作者 &#xff1a;Pramod Chandrayan物联网已经开始在所有行业的企业中走向主流。 到 2018 年底&#xff0c;物联网支出预计将增长 15&#xff05;&#xff0c;达到 7725 亿美元&#xff0c;毫无疑问&#xff0c;未来一年连接设备和企业物联网项目数量…

js中立即执行函数会预编译吗_JavaScript预编译过程

什么是预编译&#xff1f;当js代码执行时有三个步骤&#xff1a;1.语法分析&#xff0c;这个过程检查出基本的语法错误。2&#xff0c;预编译&#xff0c;为对象分配空间。3&#xff0c;解释执行&#xff0c;解释一行执行一行&#xff0c;一旦出错立即停止执行。预编译发生在代…

知识图谱火了,但你知道它的发展历史吗?|赠书5本

作者&#xff1a;尼克 编辑&#xff5c;Emily版式由AI前线整理知识图谱火了&#xff0c;但你知道它的发展历史吗&#xff1f;本文节选自《人工智能简史》第 3 章&#xff0c;从第一个专家系统 DENDRAL 到语义网再到谷歌的开源知识图谱&#xff0c;对知识图谱的发展历程进行了…

英特尔爆发史诗级芯片漏洞,Linux之父痛斥英特尔公司

来源&#xff1a;CEO来信概要&#xff1a;1月4日消息&#xff0c;英特尔处理器存在芯片级漏洞的消息仍在发酵&#xff0c;恐怕将会成为计算机行业发展史上最大的安全漏洞之一。1月4日消息&#xff0c;英特尔处理器存在芯片级漏洞的消息仍在发酵&#xff0c;恐怕将会成为计算机行…

曲线均匀分布_曲线篇:深刻理解B 样条曲线(下)

前两篇中讲解了贝塞尔曲线和B样条基础。FrancisZhao&#xff1a;曲线篇: 贝塞尔曲线​zhuanlan.zhihu.comFrancisZhao&#xff1a;曲线篇&#xff1a;深刻理解B 样条曲线&#xff08;上&#xff09;​zhuanlan.zhihu.com本文讲一下B样条的进阶clamped B样条由于我们常用的B样条…