【微服务网关——Go令牌桶限流】

在这里插入图片描述

1. time/rate限速器使用

  • 令牌桶限流算法
  • rate.NewLimiter(limit,burst)产生一个新的限速器
    • limit表示每秒产生token数、burst表示最多存token数
  • Allow判断当前是否可以取到token
  • Wait阻塞等待直到取到token
  • Reverse返回等待时间(预估的等待时间),再去取token
package main

import (
	"context"
	"golang.org/x/time/rate"
	"log"
	"testing"
	"time"
)

func Test_RateLimiter(t *testing.T) {
	l := rate.NewLimiter(1, 5)
	log.Println(l.Limit(), l.Burst())
	for i := 0; i < 10; i++ {
		//阻塞等待直到,取到一个token
		log.Println("before Wait")
		c, _ := context.WithTimeout(context.Background(), time.Second*2)
		if err := l.Wait(c); err != nil {
			log.Println("limiter wait err:" + err.Error())
		}
		log.Println("after Wait")

		//返回需要等待多久才有新的token,这样就可以等待指定时间执行任务
		r := l.Reserve()
		log.Println("reserve Delay:", r.Delay())

		//判断当前是否可以取到token
		a := l.Allow()
		log.Println("Allow:", a)
		log.Println("======================")
	}
}

在这里插入图片描述

2. time/rate源码原理

  • 计算上次请求和当前请求时间差
  • 计算时间差内生成的token数+旧token数
  • 如果token为负,则计算等待时间
  • token为正,则请求后token-1
type Limit float64

type Limiter struct {
	limit Limit//每秒产生的token数
	burst int//桶的总大小
	mu     sync.Mutex//锁
	tokens float64//token总数
	last time.Time//上一次更新token的时间
	lastEvent time.Time//最后一次限速的时间
}

Allow、Reverse、Wait三个方法底层调用的都是func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation

// reserveN 是 AllowN、ReserveN 和 WaitN 的辅助方法。
// maxFutureReserve 指定了允许的最大预订等待时间。
// reserveN 返回 Reservation(而不是 *Reservation),以避免在 AllowN 和 WaitN 中进行分配。
func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
    // 加锁,保护临界区
    lim.mu.Lock()
    // 如果每秒产生的token数为无限,则无需预订直接返回
    if lim.limit == Inf {
        lim.mu.Unlock()
        return Reservation{
            ok:        true,        // 预订成功
            lim:       lim,         // 当前限流器
            tokens:    n,           // 预订的令牌数
            timeToAct: now,         // 立即生效
        }
    }
    // 更新当前时间、上次时间和现在可用令牌数
    now, last, tokens := lim.advance(now)
    // 计算请求n个tokens后的剩余令牌数
    tokens -= float64(n)
    // 计算等待时长
    var waitDuration time.Duration
    if tokens < 0 {
        // 如果令牌不够,需要等待的时间
        waitDuration = lim.limit.durationFromTokens(-tokens)
    }
    // 判断预订是否成功,请求的n是否小于等于桶的容量,且等待时间是否小于用户给的最大实践
    ok := n <= lim.burst && waitDuration <= maxFutureReserve
    // 准备预订结果
    r := Reservation{
        ok:    ok,            // 预订是否成功
        lim:   lim,           // 当前限流器
        limit: lim.limit,     // 当前限流器的限制
    }
    if ok {
        r.tokens = n               // 成功预订的令牌数
        r.timeToAct = now.Add(waitDuration) // 生效时间
    }

    // 更新限流器状态
    if ok {
        lim.last = now              // 更新上次预订时间
        lim.tokens = tokens         // 更新剩余令牌数
        lim.lastEvent = r.timeToAct // 更新上次事件时间
    } else {
        lim.last = last             // 未成功则恢复上次时间
    }

    // 解锁
    lim.mu.Unlock()
    return r  // 返回预订结果
}

// advance 计算并返回基于时间推移的 lim 的更新状态。
// lim 自身的状态不会被改变。
func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
	last := lim.last
	if now.Before(last) {
		// 如果 now 比 last 还早,则使用 now 作为 last
		last = now
	}
	// 避免 last 非常久远时导致 delta 溢出。
	// 计算多久后这个桶会自动填满
	maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
	elapsed := now.Sub(last)
	if elapsed > maxElapsed {
		// 如果实际时间间隔超过最大允许间隔,调整为最大间隔,避免由于非常大的 elapsed 造成溢出或不合理的计算。
		elapsed = maxElapsed
	}
	// 计算由于时间推移增加的令牌数
	delta := lim.limit.tokensFromDuration(elapsed)
	tokens := lim.tokens + delta
	if burst := float64(lim.burst); tokens > burst {
		// 如果计算得到的令牌数超过了 burst,则限制为 burst
		tokens = burst
	}
	// 返回更新后的时间 now, 上次时间 last 以及新的令牌数 tokens
	return now, last, tokens
}

// tokensFromDuration 是一个单位转换函数,
// 用于将时间段转换为在该时间段内以每秒 limit 个令牌的速率
// 可积累的令牌数。
func (limit Limit) tokensFromDuration(d time.Duration) float64 {
    // 自行分离整数部分和小数部分,以尽量减少舍入误差。
    // 参考 golang.org/issues/34861。
    sec := float64(d / time.Second) * float64(limit) // 计算整秒内的令牌数
    nsec := float64(d % time.Second) * float64(limit) // 计算剩余纳秒内的令牌数
    return sec + nsec / 1e9 // 返回整秒和纳秒对应的令牌数之和
}

func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation

  • AllowN(now time.Time, n int) bool
    • lim.reserveN(now, n, 0).ok
      • now表示现在
      • n表示请求n个token
      • 0表示等待时间
  • ReserveN
    • lim.reserveN(now, n, InfDuration)
      • now表示现在
      • n表示请求n个token
      • InfDuration表示无限等待
  • WaitN
// WaitN 阻塞直到 lim 允许 n 个事件发生。
// 如果 n 超过了 Limiter 的 burst 大小,Context 被取消,
// 或者预期的等待时间超过了 Context 的截止时间,它会返回一个错误。
// 如果速率限制是无限的(Inf),则忽略 burst 限制。
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) {
    // 加锁以安全地获取限流器的 burst 和 limit 值
    lim.mu.Lock()
    burst := lim.burst
    limit := lim.limit
    lim.mu.Unlock()
    // 如果 n 超过了 burst 且 limit 不是 Inf,则返回错误
    if n > burst && limit != Inf {
        return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, lim.burst)
    }
    // 检查 Context 是否已取消
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
    }
    // 查看ctx是否设定了deadline,确定最大等待时间
    now := time.Now()
    waitLimit := InfDuration
    if deadline, ok := ctx.Deadline(); ok {
        // 计算距离截止时间的剩余时间
        waitLimit = deadline.Sub(now)
    }
    // 进行预订
    r := lim.reserveN(now, n, waitLimit)
    if !r.ok {
        // 如果预订失败且等待时间超过 Context 截止时间,返回错误
        return fmt.Errorf("rate: Wait(n=%d) would exceed context deadline", n)
    }
    // 计算需要等待的时间
    delay := r.DelayFrom(now)
    if delay == 0 {
        // 如果不需要等待,直接返回
        return nil
    }
    // 启动定时器进行等待
    t := time.NewTimer(delay)
    defer t.Stop()
    select {
    case <-t.C:
        // 拿到了令牌
        return nil
    case <-ctx.Done():
        // 在等待时 Context 被取消,取消预订,允许其他事件提前进行
        r.Cancel()
        return ctx.Err()
    }
}

3. 小结

令牌桶算法广泛应用于控制 API 请求速率、限制资源访问频率、管理任务调度等场景。通过合理设置 limit 和 burst,可以有效平衡系统负载和服务质量。该算法并不会实时去维护令牌桶中的token的数量,而是通过last和lastEvent来巧妙的计算出该段时间内容桶内令牌的状态,同时通过锁来维护了对于令牌桶的访问一致性问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

240626_昇思学习打卡-Day8-稀疏矩阵

240626_昇思学习打卡-Day8-稀疏矩阵 稀疏矩阵 在一些应用场景中&#xff0c;比如训练二值化图像分割时&#xff0c;图像的特征是稀疏的&#xff0c;使用一堆0和极个别的1表示这些特征即费事又难看&#xff0c;此时就可以使用稀疏矩阵。通过参考大佬博文&#xff0c;结合个人理…

读AI新生:破解人机共存密码笔记13有益机器

1. 标准模型 1.1. 我们能控制一个从外太空来的超级智能实体的概率几乎为零 1.2. 随着根据标准模型设计的机器变得更加智能&#xff0c;以及它们的行动范围遍及全球&#xff0c;关闭机器这种方法越来越不可行 1.2.1. 机器将会追求它们自己的目标&#xff0c;无论目标错得多么…

禁止浏览器对input的自动填充和填充提示(适用于谷歌、火狐、Edge(原IE浏览器)等常见浏览器)

目录 1.要解决的问题2.一技能&#xff1a;原生属性&#xff0c;小试牛刀3.二技能&#xff1a;傀儡input&#xff0c;瞒天过海4.三技能&#xff1a;JavaScript出击&#xff0c;直接开大 写在前面&#xff1a; 如有转载&#xff0c;务必注明出处&#xff0c;否则后果自负。 1.要解…

Java | Leetcode Java题解之第200题岛屿数量

题目&#xff1a; 题解&#xff1a; class Solution {void dfs(char[][] grid, int r, int c) {int nr grid.length;int nc grid[0].length;if (r < 0 || c < 0 || r > nr || c > nc || grid[r][c] 0) {return;}grid[r][c] 0;dfs(grid, r - 1, c);dfs(grid, r…

Pytorch实战(一):LeNet神经网络

文章目录 一、模型实现1.1数据集的下载1.2加载数据集1.3模型训练1.4模型预测 LeNet神经网络是第一个卷积神经网络&#xff08;CNN&#xff09;&#xff0c;首次采用了卷积层、池化层这两个全新的神经网络组件&#xff0c;接收灰度图像&#xff0c;并输出其中包含的手写数字&…

STM32之IIC(软件)

介绍 IIC &#xff08; 又称为 I2C 或 IC &#xff09;是一种串行通信协议&#xff0c; IIC使用两根线路来进行通信&#xff1a; 串行数据线&#xff08;SDA&#xff09; 和 串行时钟线&#xff08;SCL&#xff09; 。 SDA 线上的数据在 SCL 线的时钟信号下进行 同步传输。 主…

安宝特方案 | AR术者培养:AR眼镜如何帮助医生从“看”到“做”?

每一种新药品的上市都需要通过大量的临床试验&#xff0c;而每一种新的手术工具在普及使用之前也需要经过反复的实践和验证。医疗器械公司都面临着这样的挑战&#xff1a;如何促使保守谨慎的医生从仅仅观察新工具在手术中的应用&#xff0c;转变为在实际手术中实操这项工具。安…

centos7迁移部分成功

早闻CentOS不再维护的消息&#xff0c;确实有些遗憾&#xff0c;毕竟这个系统好用又简单&#xff0c;已经成为了我们工作中的一种习惯。然而&#xff0c;2024年6月30日这一天如约而至&#xff0c;CentOS 7停止维护后&#xff0c;随之而来的安全漏洞又该如何防范&#xff1f;系统…

Stirling-PDF 安装和使用教程

PDF (便携式文档格式) 目前已经成为了文档交换和存储的标准。然而&#xff0c;找到一个功能全面、安全可靠、且完全本地化的 PDF 处理工具并不容易。很多在线 PDF 工具存在隐私和安全风险&#xff0c;而桌面软件往往价格昂贵或功能有限。那么&#xff0c;有没有一种解决方案能够…

Linux安装JDk教程

&#x1f4d6;Linux安装JDk教程 ✅下载✅安装 ✅下载 官方Oracle地址&#xff1a;https://www.oracle.com/java/technologies/downloads/archive/ 123云盘&#xff1a;https://www.123pan.com/s/4brbVv-JdmWA.html ✅安装 1.上传安装包jdk-17_linux-x64_bin.tar.gz到指定位…

java易错题型(复习必看)

java易错题型&#xff1a; 下列符号中&#xff0c;哪个用于分隔throws关键字抛出的多个异常 逗号&#xff0c; Java中用来声明一个方法可能抛出某种异常的关键字是throw 对于catch子句的排列&#xff0c;下列哪种是正确的&#xff1a;子类异常在先&#xff0c;父类异常在后&a…

解决“Duplicate keys detected: ‘ ‘.This may cause an update error.”问题

问题原因 出现“Duplicate keys detected”的错误&#xff0c;通常表示在v-for指令中使的:key绑定值有重复。 如果前端是静态数据&#xff0c;一般能自我避免:key绑定值有重复。如果前端是绑定的动态数据&#xff0c;那么需要另外提供一个唯一的键。 在这个例子中&#xff0c…

CV每日论文--2024.6.26

1、StableNormal: Reducing Diffusion Variance for Stable and Sharp Normal 中文标题&#xff1a;StableNormal&#xff1a;减少扩散方差以实现稳定且锐利的法线 简介&#xff1a;本文介绍了一种创新解决方案&#xff0c;旨在优化单目彩色输入&#xff08;包括静态图片与动态…

糖与蛋白质的“隐秘对话”:DeepGlycanSite如何揭示生命之谜

在生命的复杂舞台上&#xff0c;糖类与蛋白质之间的相互作用犹如一场精心编排的舞蹈&#xff0c;其背后的每一个细微动作都可能对生物体的生理与病理过程产生深远影响。然而&#xff0c;糖类分子的多样性和复杂性&#xff0c;使得科学家们对糖-蛋白质结合位点的识别和研究充满了…

数据预处理功能教程,上传文件生成知识库 | Chatopera

如何快速的生成高质量的知识库&#xff1f; 数据预处理功能教程 | Chatopera 云服务低代码定制聊天机器人 关于 Chatopera Chatopera 云服务重新定义聊天机器人&#xff0c;https://bot.chatopera.com 定制智能客服、知识库、AI 助手、智慧家居等智能应用&#xff0c;释放创新…

图形化用户界面-java头歌实训

图形化用户界面 import java.awt.*; import javax.swing.*; public class GraphicsTester extends JFrame { public GraphicsTester() { super("Graphics Demo"); setSize(480, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void paint…

Node.js 个人博客

关于该博客 这是一个自己搭建的简易的博客&#xff0c;用于记录一些学习笔记和技术分享。在大四毕业时完成了第一个版本&#xff0c;后续会不断完善和更新。欢迎大家提出宝贵意见和建议。 详细介绍在 blog/posts/博客/博客搭建.md 中: https://github.com/ximingx/blog/blob/m…

php goto解密脚本源码

php goto解密脚本源码 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89426171 更多资源下载&#xff1a;关注我。

【Java Web】Servlet控制器

目录 一、Servlet简介 二、Servlet运行流程 三、Servlet开发流程 四、Servlet-api.jar包导入和Content-Type问题 4.1 Servlet-api.jar导入问题 4.2 Http报文头中的Content-Type属性 五、Servlet_url-pattern请求映射路径设置 5.1 url-pattern方式 5.2 注解方式配置servlet 六、…

Linux系统之nice命令的基本使用

Linux系统之nice命令的基本使用 一、nice命令介绍1.1 nice命令简介1.2 进程优先级介绍 二、nice命令基本语法2.1 nice命令的help帮助信息2.2 nice命令选项解释 三、nice命令的基本使用3.1 查看进程优先级3.2 使用nice启动进程3.3 提高优先级 四、注意事项 一、nice命令介绍 1.…