【限流器】golang令牌桶限流源码分析

server/2024/12/23 6:20:57/

1.令牌桶限流算法

算法思想:系统以一定速率生成令牌,存放于桶中,在达到容量的最大值后停止生成令牌。用户生成请求后从令牌桶中消费令牌才能执行。否则延迟执行或被限制。

使用场景:平滑流量控制;在一定程度上可以处理突发流量,但维护了一个延时队列存放token,同时需要一个定时器定期生成token在性能上有损耗。
限流流程如下:
在这里插入图片描述

2.time/rate中的limiter核心函数

a.NewLimiter(v,cap)

初始化系统生成令牌的速度,令牌桶的容量。

func NewLimiter(r Limit, b int) *Limiter {return &Limiter{limit: r,burst: b,}
}

burst表示最大并发量
limit表示每秒能够生产token的数量

b.WaitN(ctx,n)

在这里插入图片描述

func (lim *Limite) wait(ctx context.Context, n int, t time.Time, newTimer func(d time.Duration) (<-chan time.Time, func() bool, func())) error {lim.mu.Lock()burst := lim.burstlimit := lim.limitlim.mu.Unlock()if n > burst && limit != Inf {
//   大于最大并发量,等待时间不大return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, burst)}// 检查ctx是否已经结束select {case <-ctx.Done():return ctx.Err()default:}// 计算ctx结束前,可以等待的时间waitLimit := InfDurationif deadline, ok := ctx.Deadline(); ok {waitLimit = deadline.Sub(t)}// 预留令牌r := lim.reserveN(t, n, waitLimit)if !r.ok {return fmt.Errorf("rate: Wait(n=%d) would exceed context deadline", n)}// 看是否需要等待生产令牌delay := r.DelayFrom(t)if delay == 0 {return nil}ch, stop, advance := newTimer(delay)//等待生产令牌,期间如果ctx结束会产生预留错误defer stop()advance()select {case <-ch:return nilcase <-ctx.Done():r.Cancel()return ctx.Err()}
}

c.reserveN(t, n, waitLimit)

在这里插入图片描述

func (lim *Limite) reserveN(t time.Time, n int, maxFutureReserve time.Duration) Reservation {lim.mu.Lock()defer lim.mu.Unlock()//等待无限if lim.limit == Inf {return Reservation{ok:        true,lim:       lim,tokens:    n,timeToAct: t,}} else if lim.limit == 0 {var ok bool//不等待if lim.burst >= n {//桶里容量>ok = truelim.burst -= n}return Reservation{ok:        ok,lim:       lim,tokens:    lim.burst,timeToAct: t,}}
//懒加载,两次请求之间生成的token数量t, tokens := lim.advance(t)
//token不够用,还需要再生成tokens -= float64(n)var waitDuration time.Durationif tokens < 0 {//生成足够token等待时间waitDuration = lim.limit.durationFromTokens(-tokens)}
n的数量<最大并发量 且 等待时间可接受ok := n <= lim.burst && waitDuration <= maxFutureReserver := Reservation{ok:    ok,lim:   lim,limit: lim.limit,}if ok {r.tokens = nr.timeToAct = t.Add(waitDuration)// 执行等待的限流器,更新数据lim.last = tlim.tokens = tokenslim.lastEvent = r.timeToAct}return r
}

d、advance(t time.Time)

懒加载两次请求中间生成的token数量

func (lim *Limite) advance(t time.Time) (newT time.Time, newTokens float64) {last := lim.last//上次请求时间if t.Before(last) {last = t}elapsed := t.Sub(last)//懒加载两次请求中间生成的token数量//token_n = 所有token数量/capdelta := lim.limit.tokensFromDuration(elapsed)tokens := lim.tokens + deltaif burst := float64(lim.burst); tokens > burst {tokens = burst}//返回桶里的tokenreturn t, tokens
}

e.ctx结束,请求失败使用cancel归还token

预存器:

type Reservation struct {ok        boollim       *Limitetokens    inttimeToAct time.Timelimit     Limit
}

预存器记录了当前的预存token,上一次取token操作时间,预取结果

func (r *Reservation) CancelAt(t time.Time) {if !r.ok {return}r.lim.mu.Lock()defer r.lim.mu.Unlock()if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(t) {return}//获得过的tokenrestoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct))if restoreTokens <= 0 {return}//获得桶里的tokent, tokens := r.lim.advance(t)// calculate new number of tokenstokens += restoreTokensif burst := float64(r.lim.burst); tokens > burst {tokens = burst}// update stater.lim.last = tr.lim.tokens = tokensif r.timeToAct == r.lim.lastEvent {prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens)))if !prevEvent.Before(t) {r.lim.lastEvent = prevEvent}}
}

3.使用限流器生成中间件

func Limiter(limit, cap int) MiddlewareFunc {li := rate.NewLimiter(rate.Limit(limit), cap)return func(next HandlerFunc) HandlerFunc {return func(ctx *Context) {//实现限流con, cancel := context.WithTimeout(context.Background(), time.Duration(1)*time.Second)defer cancel()err := li.WaitN(con, 1)if err != nil {//没有拿到令牌的操作li.Reserve().Cancel()ctx.String(http.StatusForbidden, "限流了")return}next(ctx)}}
}

http://www.ppmy.cn/server/114257.html

相关文章

从零开始搭建Prometheus与Grafana监控系统:实战演练

从零开始搭建 Prometheus 与 Grafana 监控系统&#xff1a;实战演练 监控系统是现代 IT 基础设施的重要组成部分&#xff0c;用于监控服务器、应用程序和服务的性能和可用性。Prometheus 和 Grafana 是目前广泛使用的开源监控和可视化工具。本篇文章将从零开始&#xff0c;手把…

云原生架构概念

云原生架构概念 云原生架构&#xff08;Cloud Native Architechtrue&#xff09;作为一种现代软件开发的革新力量&#xff0c;正在逐渐改变企业构建、部署和管理应用程序的方式。它的核心优势在于支持微服务架构&#xff0c;使得应用程序能够分解为独立、松耦合的服务&#xf…

Nginx的负载均衡

Nginx 是一个高性能的 HTTP 和反向代理服务器&#xff0c;广泛用于负载均衡。负载均衡的目的是将客户端请求分配到多个后端服务器&#xff0c;以提高应用的可用性和性能。下面详细解释 Nginx 的负载均衡特性&#xff0c;包括其工作原理、配置方法、负载均衡算法等。 工作原理 …

贝锐蒲公英远程视频监控方案:4G入网无需公网IP,跨品牌统一管理

在部署视频监控并实现集中监看时&#xff0c;常常会遇到各种挑战。比如&#xff1a;部分监控点位布线困难、无法接入有线宽带&#xff0c;或是没有固定公网IP&#xff0c;难以实现远程集中监看&#xff1b;已有网络质量差&#xff0c;传输延迟大、丢包率高&#xff0c;远程实时…

Anti-honeypot - 自动识别Web蜜罐Chrome插件,附下载链接

在我们最近的一次攻防演习中&#xff0c;我和同事们发现了一些关于蜜罐的有趣现象。很多情况下&#xff0c;红队的攻击手法虽然高超&#xff0c;但他们往往因为一个小细节而暴露了真实身份。比如&#xff0c;有些黑客在使用浏览器时&#xff0c;没有开启隐身模式&#xff0c;结…

【拥抱AI】浅谈Prompt的书写规范及要点

Prompt是什么&#xff1f; Prompt是一种技术&#xff0c;它通过自然语言处理来引导用户与机器之间的交互。在人工智能领域&#xff0c;Prompt通常用于生成文本&#xff0c;例如对话系统、机器翻译和文本摘要等应用。它也用于训练模型&#xff0c;以使其能够理解和生成人类语言…

什么是COB超微小间距会议一体机?LED智能会议一体机重塑会议体验

在当今这个快节奏、高效率的时代&#xff0c;会议作为企业日常运营中不可或缺的一环&#xff0c;其效率与体验直接影响着企业的决策速度与团队协作能力。随着科技的飞速发展&#xff0c;传统的会议设备已难以满足现代会议室的多元化需求&#xff0c;LED智能会议一体机&#xff…

Linux:从入门到放弃

目录 一、基础巩固Linux&#xff1a;常用命令 二、实战应用Linux&#xff1a;CentOS7基础配置Linux&#xff1a;CentOS7安装MySQL 三、常见问题Linux&#xff1a;yum源失效问题 一、基础巩固 Linux&#xff1a;常用命令 二、实战应用 Linux&#xff1a;CentOS7基础配置 Lin…