限流器是后台服务中十分重要的组件,在实际的业务场景中使用居多,其设计在微服务、网关、和一些后台服务中会经常遇到。限流器的作用是用来限制其请求的速率,保护后台响应服务,以免服务过载导致服务不可用现象出现。

限流器的实现方法有很多种,例如 Token Bucket、滑动窗口法、Leaky Bucket等。

在 Golang 库中官方给我们提供了限流器的实现golang.org/x/time/rate,它是基于令牌桶算法(Token Bucket)设计实现的。

参考:go语言中文文档:www.topgoer.com

转自:https://segmentfault.com/a/1190000023824362

令牌桶算法

令牌桶设计比较简单,可以简单的理解成一个只能存放固定数量雪糕!的一个冰箱,每个请求可以理解成来拿雪糕的人,有且只能每一次请求拿一块,那雪糕拿完了会怎么样呢?这里会有一个固定放雪糕的工人,并且他往冰箱里放雪糕的频率都是一致的,例如他 1s 中只能往冰箱里放 10 块雪糕,这里就可以看出请求响应的频率了。

令牌桶设计概念:

  • 令牌:每次请求只有拿到 Token 令牌后,才可以继续访问;
  • :具有固定数量的桶,每个桶中最多只能放设计好的固定数量的令牌;
  • 入桶频率:按照固定的频率往桶中放入令牌,放入令牌不能超过桶的容量。

也就是说,基于令牌桶设计算法就限制了请求的速率,达到请求响应可控的目的,特别是针对于高并发场景中突发流量请求的现象,后台就可以轻松应对请求了,因为到后端具体服务的时候突发流量请求已经经过了限流了。

具体设计

限流器定义

type Limiter struct {    mu        sync.Mutex // 互斥锁(排他锁)    limit     Limit      // 放入桶的频率  float64 类型    burst     int        // 桶的大小    tokens    float64    // 令牌 token 当前剩余的数量    last      time.Time  // 最近取走 token 的时间    lastEvent time.Time  // 最近限流事件的时间}

limit、burst 和 token 是这个限流器中核心的参数,请求并发的大小在这里实现的。

在令牌发放之后,会存储在 Reservation 预约对象中:

type Reservation struct {    ok        bool      // 是否满足条件分配了 token    lim       *Limiter  // 发送令牌的限流器    tokens    int       // 发送 token 令牌的数量    timeToAct time.Time // 满足令牌发放的时间    limit     Limit     // 令牌发放速度}

消费 Token

Limiter 提供了三类方法供用户消费 Token,用户可以每次消费一个 Token,也可以一次性消费多个 Token。而每种方法代表了当 Token 不足时,各自不同的对应手段。

Wait、WaitN

func (lim *Limiter) Wait(ctx context.Context) (err error)func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)

其中,Wait 就是 WaitN(ctx, 1),在下面的方法介绍实现也是一样的。

使用 Wait 方法消费 Token 时,如果此时桶内 Token 数组不足 ( 小于 n ),那么 Wait 方法将会阻塞一段时间,直至 Token 满足条件。如果充足则直接返回。

Allow、AllowN

func (lim *Limiter) Allow() boolfunc (lim *Limiter) AllowN(now time.Time, n int) bool 

AllowN 方法表示,截止到当前某一时刻,目前桶中数目是否至少为 n 个,满足则返回 true,同时从桶中消费 n 个 token。
反之返回不消费 Token,false。

通常对应这样的线上场景,如果请求速率过快,就直接丢到某些请求。

Reserve、ReserveN

官方提供的限流器有阻塞等待式的 Wait,也有直接判断方式的 Allow,还有提供了自己维护预留式的,但核心的实现都是下面的 reserveN 方法。

func (lim *Limiter) Reserve() *Reservationfunc (lim *Limiter) ReserveN(now time.Time, n int) *Reservation

当调用完成后,无论 Token 是否充足,都会返回一个Reservation *对象。

你可以调用该对象的 Delay() 方法,该方法返回了需要等待的时间。如果等待时间为 0,则说明不用等待。
必须等到等待时间结束之后,才能进行接下来的工作。

或者,如果不想等待,可以调用 Cancel() 方法,该方法会将 Token 归还。

func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {    lim.mu.Lock()    // 首先判断是否放入频率是否为无穷大    // 如果为无穷大,说明暂时不限流    if lim.limit == Inf {        lim.mu.Unlock()        return Reservation{            ok:        true,            lim:       lim,            tokens:    n,            timeToAct: now,        }    }    // 拿到截至 now 时间时    // 可以获取的令牌 tokens 数量及上一次拿走令牌的时间 last    now, last, tokens := lim.advance(now)    // 更新 tokens 数量    tokens -= float64(n)    // 如果 tokens 为负数,代表当前没有 token 放入桶中    // 说明需要等待,计算等待的时间    var waitDuration time.Duration    if tokens < 0 {        waitDuration = lim.limit.durationFromTokens(-tokens)    }    // 计算是否满足分配条件    // 1、需要分配的大小不超过桶的大小    // 2、等待时间不超过设定的等待时长    ok := n <= lim.burst && waitDuration <= maxFutureReserve    // 预处理 reservation    r := Reservation{        ok:    ok,        lim:   lim,        limit: lim.limit,    }    // 若当前满足分配条件    // 1、设置分配大小    // 2、满足令牌发放的时间 = 当前时间 + 等待时长    if ok {        r.tokens = n        r.timeToAct = now.Add(waitDuration)    }    // 更新 limiter 的值,并返回    if ok {        lim.last = now        lim.tokens = tokens        lim.lastEvent = r.timeToAct    } else {        lim.last = last    }    lim.mu.Unlock()    return r}

具体使用

rate 包中提供了对限流器的使用,只需要指定 limit(放入桶中的频率)、burst(桶的大小)。

func NewLimiter(r Limit, b int) *Limiter {    return &Limiter{        limit: r, // 放入桶的频率        burst: b, // 桶的大小    }}

在这里,使用一个 http api 来简单的验证一下 time/rate 的强大:

func main() {    r := rate.Every(1 * time.Millisecond)    limit := rate.NewLimiter(r, 10)    http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {        if limit.Allow() {            fmt.Printf("请求成功,当前时间:%s", time.Now().Format("2006-01-02 15:04:05"))        } else {            fmt.Printf("请求成功,但是被限流了。。。")        }    })    _ = http.ListenAndServe(":8081", nil)}

在这里,我把桶设置成了每一毫秒投放一次令牌,桶容量大小为 10,起一个 http 的服务,模拟后台 API。

接下来做一个压力测试,看看效果如何:

func GetApi() {    api := "http://localhost:8081/"    res, err := http.Get(api)    if err != nil {        panic(err)    }    defer res.Body.Close()    if res.StatusCode == http.StatusOK {        fmt.Printf("get api success")    }}func Benchmark_Main(b *testing.B) {    for i := 0; i < b.N; i++ {        GetApi()    }}

效果如下:

......请求成功,当前时间:2020-08-24 14:26:52请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,当前时间:2020-08-24 14:26:52请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,但是被限流了。。。......

在这里,可以看到,当使用 AllowN 方法中,只有当令牌 Token 生产出来,才可以消费令牌,继续请求,剩余的则是将其请求抛弃,当然在实际的业务处理中,可以用比较友好的方式反馈给前端。

在这里,先有的几次请求都会成功,是因为服务启动后,令牌桶会初始化,将令牌放入到桶中,但是随着突发流量的请求,令牌按照预定的速率生产令牌,就会出现明显的令牌供不应求的现象。

开源文化

目前 time/rate 是一个独立的限流器开源解决方案,感兴趣的小伙伴可以给此项目一个 Star,谢谢。

GitHub
golang/time

参考文章

  • 限流器系列(2) — Token Bucket 令牌桶
  • Golang 限流器的使用和实现
  • Golang 标准库限流器 time/rate 使用介绍
  • https://github.com/golang/time/rate.go

golang中文文档_Golang 标准库 限流器 time/rate 设计与实现相关推荐

  1. golang中文文档_Golang开发环境搭建

    Go 语言开发包 国外:https://golang.org/dl/ 国内(推荐): https://golang.google.cn/dl/ 编辑器 Golang:https://www.jetbr ...

  2. golang中文文档_【译】Go 语言源码贡献官方指导文档

    以前给 Go 语言项目源码提交过一些 commits,期间阅读他们的官方指导文档的时候觉得这篇指导文档可以作为绝佳的关于大型软件项目的规范管理的参考,因为最近又提交了几个 commits,就又把这篇文 ...

  3. lavaral中文手册_Laravel-mix 中文文档

    概览 基本示例 larave-mix 是位于webpack顶层的一个简洁的配置层,在 80% 的情况下使用 laravel mix 会使操作变的非常简单.尽管 webpack 非常的强大,但大部分人都 ...

  4. C语言中文文档、C语言文档大全(网址)

    中文文档 C语言标准库中文文档 英文文档 cppreference.com 注意:打开网页后滑最下面才是C 微软的C语言文档 C 文档 - 入门.教程.参考. | Microsoft Learn IE ...

  5. python pptx库中文文档_基于python-pptx库中文文档及使用详解

    个人使用样例及部分翻译自官方文档,并详细介绍chart的使用 一:基础应用 1.创建pptx文档类并插入一页幻灯片 from pptx import Presentation prs = Presen ...

  6. python pptx教学_基于python-pptx库中文文档及使用详解

    个人使用样例及部分翻译自官方文档,并详细介绍chart的使用 一:基础应用 1.创建pptx文档类并插入一页幻灯片 from pptx import Presentation prs = Presen ...

  7. golang roadrunner中文文档(一)基础介绍

    2021年5月24日14:34:05 golang roadrunner中文文档(一)基础介绍 golang roadrunner中文文档(二)PHP Workers golang roadrunne ...

  8. Python 深度学习库 Keras 发布官方中文文档,这里有你需要了解的一切

    今年1月,Keras作者.谷歌AI研究员François Chollet在推特上发出召唤:讲中文的Keras用户们,是否有人愿意帮忙一起搞个Keras文档的中文版? 一个多月后,官方中文文档来了. K ...

  9. Clipper库中文文档(ClipperLib)

    中文文档链接:https://love2.io/@martinchan3/doc/ClipperDocCN Git仓库:https://github.com/MartinChan3/ClipperDo ...

最新文章

  1. 【洛谷p1058】立体图(已完结)
  2. android点九,android关于点九(.9)图片
  3. Eclipse 中 Maven 项目默认JDK版本为1.5 的解决方法
  4. 如何在SAP云平台Neo环境里进行workflow(工作流)的开发
  5. 计算机结构原理初步教案板书设计,(计算机工作原理)教学设计(教案)
  6. 基于事件驱动架构构建微服务第12部分:向Apache KAFKA生成事件
  7. create-react-app 创建react项目 多页面应用
  8. 课程 2B: 制作一款交互性应用
  9. mysql5.6.4安装_win 7 64 mysql 5.6.4 安装
  10. 一封来自老男孩的学生的辞职信!
  11. CE6870 添加ipv6 策略路由失败问题解决
  12. 如何使用SPSS进行判别分析
  13. matlab子函数中使用全局变量的使用方法
  14. hdu1166 敌兵布阵(模板题 单点修改 + 区间查询)
  15. 2021-04-29:给定一个数组 arr,代表一排有分数的气球。每打爆一个气球都能获得分数,假设打爆气 球 的分数为 X,获得分数的规则如下: 1)如果被打爆气球的左边有没被打爆的气球,找到离被打爆
  16. MySQL数据库程序设计(三)
  17. Lisp基础函数:car, cdr, cons...
  18. python全角半角的相互转换
  19. 实验6 音频放大器的仿真验证
  20. 美好只在一瞬间[frwy]

热门文章

  1. 多版本Python共存时pip给指定版本的python安装package的方法
  2. 2022-2028年中国宠物用药行业市场前瞻与投资战略规划分析报告
  3. iOS 开发经验总结
  4. 太久没来了,好尴尬呀
  5. java与.net比较学习系列(7) 属性
  6. C++:Lambda函数学习
  7. uefi怎么添加linux启动项,LINUX下EFIBOOTMGR的使用,删除UEFI主板多余启动项和添加启动项...
  8. python20191031_20191031:Python取反运算详解
  9. xp大容量u盘补丁_Win XP系统下载与安装(U盘)
  10. jenkins获取远程服务器文件,Jenkins用SSH传输文件到远程服务器