设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间13 现在时间13 缓存数据 05|快乐缺点勇气 浪漫缺点诗意\你低头不说一句\你朝着灰色走去\你住进混沌深海\你开始无望等待|词曲/编混:陈粒

05|快乐缺点勇气 浪漫缺点诗意\你低头不说一句\你朝着灰色走去\你住进混沌深海\你开始无望等待|词曲/编混:陈粒 -- 光

查看: 1618|回复: 3

Golang限流器time/rate设计与实现详解

[复制链接]

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
31
主题
27
精华
0
金钱
95
积分
58
注册时间
2023-9-30
最后登录
2025-3-15

发表于 2024-3-7 21:14:41 | 显示全部楼层 |阅读模式
目录


  • 令牌桶算法
  • 具体设计

    • 限流器定义
    • 消费 Token

  • 具体使用
限流器是后台服务中十分重要的组件,在实际的业务场景中使用居多,其设计在微服务、网关、和一些后台服务中会经常遇到。限流器的作用是用来限制其请求的速率,保护后台响应服务,以免服务过载导致服务不可用现象出现。
限流器的实现方法有很多种,例如 Token Bucket、滑动窗口法、Leaky Bucket等。
在 Golang 库中官方给我们提供了限流器的实现
  1. golang.org/x/time/rate
复制代码
,它是基于令牌桶算法(Token Bucket)设计实现的。

令牌桶算法

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

  • 令牌:每次请求只有拿到 Token 令牌后,才可以继续访问;
  • :具有固定数量的桶,每个桶中最多只能放设计好的固定数量的令牌;
  • 入桶频率:按照固定的频率往桶中放入令牌,放入令牌不能超过桶的容量。
也就是说,基于令牌桶设计算法就限制了请求的速率,达到请求响应可控的目的,特别是针对于高并发场景中突发流量请求的现象,后台就可以轻松应对请求了,因为到后端具体服务的时候突发流量请求已经经过了限流了。

具体设计


限流器定义
  1. type Limiter struct {
  2.         mu        sync.Mutex // 互斥锁(排他锁)
  3.         limit     Limit      // 放入桶的频率  float64 类型
  4.         burst     int        // 桶的大小
  5.         tokens    float64    // 令牌 token 当前剩余的数量
  6.         last      time.Time  // 最近取走 token 的时间
  7.         lastEvent time.Time  // 最近限流事件的时间
  8. }
复制代码
limit、burst 和 token 是这个限流器中核心的参数,请求并发的大小在这里实现的。
在令牌发放之后,会存储在 Reservation 预约对象中:
  1. type Reservation struct {
  2.         ok        bool      // 是否满足条件分配了 token
  3.         lim       *Limiter  // 发送令牌的限流器
  4.         tokens    int       // 发送 token 令牌的数量
  5.         timeToAct time.Time // 满足令牌发放的时间
  6.         limit     Limit     // 令牌发放速度
  7. }
复制代码
消费 Token

Limiter 提供了三类方法供用户消费 Token,用户可以每次消费一个 Token,也可以一次性消费多个 Token。而每种方法代表了当 Token 不足时,各自不同的对应手段。
Wait、WaitN
  1. func (lim *Limiter) Wait(ctx context.Context) (err error)
  2. func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)
复制代码
其中,Wait 就是
  1. WaitN(ctx, 1)
复制代码
,在下面的方法介绍实现也是一样的。
使用 Wait 方法消费 Token 时,如果此时桶内 Token 数组不足 ( 小于 n ),那么 Wait 方法将会阻塞一段时间,直至 Token 满足条件。如果充足则直接返回。
Allow、AllowN
  1. func (lim *Limiter) Allow() bool
  2. func (lim *Limiter) AllowN(now time.Time, n int) bool
复制代码
AllowN 方法表示,截止到当前某一时刻,目前桶中数目是否至少为 n 个,满足则返回 true,同时从桶中消费 n 个 token。 反之返回不消费 Token,false。
通常对应这样的线上场景,如果请求速率过快,就直接丢到某些请求。
Reserve、ReserveN
官方提供的限流器有阻塞等待式的 Wait,也有直接判断方式的 Allow,还有提供了自己维护预留式的,但核心的实现都是下面的 reserveN 方法。
  1. func (lim *Limiter) Reserve() *Reservation
  2. func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation
复制代码
当调用完成后,无论 Token 是否充足,都会返回一个Reservation *对象。
你可以调用该对象的 Delay() 方法,该方法返回了需要等待的时间。如果等待时间为 0,则说明不用等待。 必须等到等待时间结束之后,才能进行接下来的工作。
或者,如果不想等待,可以调用 Cancel() 方法,该方法会将 Token 归还。
  1. func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
  2.         lim.mu.Lock()

  3.         // 首先判断是否放入频率是否为无穷大
  4.         // 如果为无穷大,说明暂时不限流
  5.         if lim.limit == Inf {
  6.                 lim.mu.Unlock()
  7.                 return Reservation{
  8.                         ok:        true,
  9.                         lim:       lim,
  10.                         tokens:    n,
  11.                         timeToAct: now,
  12.                 }
  13.         }

  14.         // 拿到截至 now 时间时
  15.         // 可以获取的令牌 tokens 数量及上一次拿走令牌的时间 last
  16.         now, last, tokens := lim.advance(now)

  17.         // 更新 tokens 数量
  18.         tokens -= float64(n)

  19.         // 如果 tokens 为负数,代表当前没有 token 放入桶中
  20.         // 说明需要等待,计算等待的时间
  21.         var waitDuration time.Duration
  22.         if tokens < 0 {
  23.                 waitDuration = lim.limit.durationFromTokens(-tokens)
  24.         }

  25.         // 计算是否满足分配条件
  26.         // 1、需要分配的大小不超过桶的大小
  27.         // 2、等待时间不超过设定的等待时长
  28.         ok := n <= lim.burst && waitDuration <= maxFutureReserve

  29.         // 预处理 reservation
  30.         r := Reservation{
  31.                 ok:    ok,
  32.                 lim:   lim,
  33.                 limit: lim.limit,
  34.         }
  35.         // 若当前满足分配条件
  36.         // 1、设置分配大小
  37.         // 2、满足令牌发放的时间 = 当前时间 + 等待时长
  38.         if ok {
  39.                 r.tokens = n
  40.                 r.timeToAct = now.Add(waitDuration)
  41.         }

  42.         // 更新 limiter 的值,并返回
  43.         if ok {
  44.                 lim.last = now
  45.                 lim.tokens = tokens
  46.                 lim.lastEvent = r.timeToAct
  47.         } else {
  48.                 lim.last = last
  49.         }

  50.         lim.mu.Unlock()
  51.         return r
  52. }
复制代码
具体使用

rate 包中提供了对限流器的使用,只需要指定 limit(放入桶中的频率)、burst(桶的大小)。
  1. func NewLimiter(r Limit, b int) *Limiter {
  2.         return &Limiter{
  3.                 limit: r, // 放入桶的频率
  4.                 burst: b, // 桶的大小
  5.         }
  6. }
复制代码
在这里,使用一个 http api 来简单的验证一下
  1. time/rate
复制代码
的强大:
  1. func main() {
  2.         r := rate.Every(1 * time.Millisecond)
  3.         limit := rate.NewLimiter(r, 10)
  4.         http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
  5.                 if limit.Allow() {
  6.                         fmt.Printf("请求成功,当前时间:%s\n", time.Now().Format("2006-01-02 15:04:05"))
  7.                 } else {
  8.                         fmt.Printf("请求成功,但是被限流了。。。\n")
  9.                 }
  10.         })

  11.         _ = http.ListenAndServe(":8081", nil)
  12. }
复制代码
在这里,我把桶设置成了每一毫秒投放一次令牌,桶容量大小为 10,起一个 http 的服务,模拟后台 API。
接下来做一个压力测试,看看效果如何:
  1. func GetApi() {
  2.         api := "http://localhost:8081/"
  3.         res, err := http.Get(api)
  4.         if err != nil {
  5.                 panic(err)
  6.         }
  7.         defer res.Body.Close()

  8.         if res.StatusCode == http.StatusOK {
  9.                 fmt.Printf("get api success\n")
  10.         }
  11. }

  12. func Benchmark_Main(b *testing.B) {
  13.         for i := 0; i < b.N; i++ {
  14.                 GetApi()
  15.         }
  16. }
复制代码
效果如下:
  1. ......请求成功,当前时间:2020-08-24 14:26:52请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,当前时间:2020-08-24 14:26:52请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,但是被限流了。。。请求成功,但是被限流了。。。......
复制代码
在这里,可以看到,当使用 AllowN 方法中,只有当令牌 Token 生产出来,才可以消费令牌,继续请求,剩余的则是将其请求抛弃,当然在实际的业务处理中,可以用比较友好的方式反馈给前端。
在这里,先有的几次请求都会成功,是因为服务启动后,令牌桶会初始化,将令牌放入到桶中,但是随着突发流量的请求,令牌按照预定的速率生产令牌,就会出现明显的令牌供不应求的现象。
到此这篇关于Golang限流器time/rate设计与实现详解的文章就介绍到这了,更多相关Go限流器time/rate内容请搜索晓枫资讯以前的文章或继续浏览下面的相关文章希望大家以后多多支持晓枫资讯!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
16
积分
12
注册时间
2022-12-30
最后登录
2022-12-30

发表于 2025-2-24 15:17:12 | 显示全部楼层
感谢楼主分享。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

  • 打卡等级:即来则安
  • 打卡总天数:24
  • 打卡月天数:0
  • 打卡总奖励:295
  • 最近打卡:2025-04-12 08:41:32
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
340
积分
54
注册时间
2023-1-1
最后登录
2025-4-12

发表于 2025-3-15 05:14:10 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
17
积分
14
注册时间
2022-12-30
最后登录
2022-12-30

发表于 2025-4-21 15:49:10 | 显示全部楼层
感谢楼主,顶。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼
4楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表