go.uber.org/ratelimit 源码分析

news2024/11/16 15:47:40

go.uber.org/ratelimit 源码分析

go 提供了一用来接口限流的包。其中"go.uber.org/ratelimit" 包正是基于漏桶算法实现的。

使用方式:

  1. 通过 ratelimit.New 创建限流器对象,参数为每秒允许的请求数(RPS)。
  2. 使用 Take() 方法来获取限流许可,该方法会阻塞请求知道满足限速要求。

官方示例:

import (
	"fmt"
	"time"

	"go.uber.org/ratelimit"
)

func main() {
    rl := ratelimit.New(100) // 每秒多少次

    prev := time.Now()
    for i := 0; i < 10; i++ {
        now := rl.Take()	// 平均时间
        fmt.Println(i, now.Sub(prev))
        prev = now
    }

    // Output:
    // 0 0
    // 1 10ms
    // 2 10ms
    // 3 10ms
    // 4 10ms
    // 5 10ms
    // 6 10ms
    // 7 10ms
    // 8 10ms
    // 9 10ms
}

ratelimit.New()指的是每秒平均多少次,在运行程序后,并不会严格按照官方给的样例输出。

源码分析

不仅知其然,还要知其所以然。

最大松弛量

传统的漏桶算法每隔请求的间隔是固定的,然而在实际上的互连网应用中,流量经常是突发性的。对于这种情况,uber引入了最大松弛量的概念。

假如我们要求每秒限定100个请求,平均每个请求间隔 10ms。但是实际情况下,有些间隔比较长,有些间隔比较短。如下图所示:

在这里插入图片描述

请求 1 完成后,15ms 后,请求 2 才到来,可以对请求 2 立即处理。请求 2 完成后,5ms 后,请求 3 到来,这个时候距离上次请求还不足 10ms,因此还需要等待 5ms。

但是,对于这种情况,实际上三个请求一共消耗了 25ms 才完成,并不是预期的 20ms。在 uber-go 实现的 ratelimit 中,可以把之前间隔比较长的请求的时间,匀给后面的使用,保证每秒请求数 (RPS) 即可。

了解完这个前缀知识就可以查看源码了。

New()

ratelimit.New() 内部调用的是 newAtomicInt64Based 方法。

type atomicInt64Limiter struct {
	prepadding [64]byte // 填充字节,确保state独占一个缓存行
	state      int64    // 最后一次权限发送的纳秒时间戳,用于控制请求的速度
	postpadding [56]byte // 填充字节,确保state独占一个缓存行

	perRequest time.Duration	// 限流器放行周期,用于计算下一个权限发送的state的值
	maxSlack   time.Duration	// 最大松弛量
	clock      Clock	// 指向当前时间获取函数的指针
}

// newAtomicBased返回一个新的基于原子的限制器。
func newAtomicInt64Based(rate int, opts ...Option) *atomicInt64Limiter {
	config := buildConfig(opts) // 加载配置,config.per 默认为 1s,config.slack 默认为 10
	perRequest := config.per / time.Duration(rate)
	l := &atomicInt64Limiter{
		perRequest: perRequest,
		maxSlack:   time.Duration(config.slack) * perRequest,	// 默认maxSlack为perRequest 10倍
		clock:      config.clock,
	}
	atomic.StoreInt64(&l.state, 0)	
	return l
}

Take()

// Take blocks to ensure that the time spent between multiple
// Take calls is on average time.Second/rate.
func (t *atomicInt64Limiter) Take() time.Time {
   var (
      newTimeOfNextPermissionIssue int64	// 下一次允许请求的时间
      now                          int64	// 当前时间
   )
   for {
      now = t.clock.Now().UnixNano()	
      timeOfNextPermissionIssue := atomic.LoadInt64(&t.state) // 上一次允许请求时间

      switch {
      case timeOfNextPermissionIssue == 0 || (t.maxSlack == 0 && now-timeOfNextPermissionIssue > int64(t.perRequest)):
        // if this is our first call or t.maxSlack == 0 we need to shrink issue time to now
         newTimeOfNextPermissionIssue = now
      case t.maxSlack > 0 && now-timeOfNextPermissionIssue > int64(t.maxSlack)+int64(t.perRequest):
         // a lot of nanoseconds passed since the last Take call
         // we will limit max accumulated time to maxSlack
         newTimeOfNextPermissionIssue = now - int64(t.maxSlack)
      default:
         // calculate the time at which our permission was issued
         newTimeOfNextPermissionIssue = timeOfNextPermissionIssue + int64(t.perRequest)
      }

      if atomic.CompareAndSwapInt64(&t.state, timeOfNextPermissionIssue, newTimeOfNextPermissionIssue) {
         break
      }
   }

   sleepDuration := time.Duration(newTimeOfNextPermissionIssue - now)
   if sleepDuration > 0 {
      t.clock.Sleep(sleepDuration)
      return time.Unix(0, newTimeOfNextPermissionIssue)
   }
   // return now if we don't sleep as atomicLimiter does
   return time.Unix(0, now)
}

switch 这块挺绕的,刚开始一直以为timeOfNextPermissionIssue 为下次放行的时间戳,这样的话当t.maxSlack = 0时,只要 now-timeOfNextPermissionIssue > 0 就应该放行。无法解释(t.maxSlack == 0 && now-timeOfNextPermissionIssue > int64(t.perRequest))

让我们对上面的三个 case 分析一下

case 1

case timeOfNextPermissionIssue == 0 || (t.maxSlack == 0 && now-timeOfNextPermissionIssue > int64(t.perRequest))

这个比较好理解,我们仍以每秒100个请求为例,平均间隔 10ms。当本次请求时间与上次放行时间 > 时间间隔时即可放行,并记录本次访问时间,如图:

在这里插入图片描述

case 2

case t.maxSlack > 0 && now-timeOfNextPermissionIssue > int64(t.maxSlack)+int64(t.perRequest)

这块比较巧妙,假如松弛量是3 ms,当我们在第二次请求时的时间戳 > 13 ms,此时 newTimeOfNextPermissionIssue= now - maxSlack = 12 ms。

maxSlack 较大且与上次请求相隔较长时,后续的大量请求会被直接放行,以弥补此次浪费的时间。

假设第一次请求时间为0, maxSlack 为 100 ms,perRequest为10 ms,在第二次请求时与第一次间隔为 111 ms ,newTimeOfNextPermissionIssue = 111 - 100 = 11 ms。而 now 为 111 ms,限流器在后面的10次take中都会经过default直接放行,直到 newTimeOfNextPermissionIssue > now

在这里插入图片描述

case 3

对于其它的请求, newTimeOfNextPermissionIssue = timeOfNextPermissionIssue + int64(t.perRequest)

假如maxSlack为 100ms,perRequest 为 10ms,当请求2在15ms访问后,state 更新为 10ms,这样在请求3在20ms访问时,不会出现拦截的情况。

在这里插入图片描述

小结

uber 对基于漏桶实现的 ratelimit 进行了一些优化,让其限流更加的平滑。主要体现在两点:

  1. 本次请求时间距离上次放行时间 > 时间间隔 + 松弛量时,后面10次的请求会根据情况直接放行
  2. 时间间隔 + 松弛量 >= 本次请求时间距离上次放行时间 > 时间间隔state = state + perRequest

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

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

相关文章

8.29T2 国际象棋(构造:棋盘拆分成小方阵)

http://cplusoj.com/d/senior/p/NODSX2303B 暴力显然&#xff0c;因为肯定是从奇点到偶点&#xff0c;所以二分图匹配一下就好 首先我们手模一下&#xff0c;比如&#xff08;11,11&#xff09;&#xff0c;我们可以手模出一个情况&#xff0c;也就是DInic跑出来的情况&#…

培训第三十九天(了解docker-compose,docker-compose编排容器,配置harbor服务)

一、回顾 1、拉取私有仓库镜像 # 配置dockerdocker pull 10.0.0.10:5000/centosnginx:v0 2、容器网络类型 brideg(net) default# docker启动之后会生成新的虚拟网卡&#xff0c;网卡的名称docker0# 网段默认是172.17.0.1# 所有的容器都桥接docker0&#xff0c;通过桥接共享网…

对物料分别评估

业务示例 在公司中&#xff0c;某些物料是同时在内部进行生产和在外部进行采购的。由于必须根据值区分内部零件和外部零件&#xff0c;因此应为这些物料设置分别评估。 有关工厂内的评估&#xff0c;可使用分别评估根据特定标准区分物料的部分库存&#xff0c;并且可以不同方…

Python画笔案例-019 绘制阴影丫字

1、绘制阴影丫字 通过 python 的turtle 库绘制一个阴影丫字的图案&#xff0c;如下图&#xff1a; 2、实现代码 绘制一个阴影丫字图&#xff0c;以下为实现代码&#xff1a; """阴影丫字.py """ import turtleturtle.delay(0) turtle.speed(0)d…

Linux —— 驱动——platform平台总线

platform平台总线是Linux内核中一个重要的概念&#xff0c;主要用于管理那些不通过传统物理总线&#xff08;如USB、I2C、SPI等&#xff09;连接的设备&#xff0c;特别是SoC&#xff08;System on Chip&#xff0c;片上系统&#xff09;内部集成的外设。以下是对platform平台总…

mysql8.0查询等级排名可使用窗口函数,那5.7的版本呢?

1、需求&#xff1a;查询用户详情的同时查询用户的排名 2、首先看下数据库表设计 分为会员用户表member_user和会员等级表member_level&#xff0c;升级的条件是根据经验值升级&#xff0c;表结构如下&#xff1a; 用户表 member_user CREATE TABLE member_user (id bigint(…

C#操作ms office实现office转pdf

前提 安装office 2019 安装vs 2022 新建项目 引入4个com包 编写代码 代码结构 代码如下 using Microsoft.Office.Interop.Excel;namespace UseMsOffice {internal class Program{static void Main(string[] args){WordToPdf();ExcelToPdf();PPTToPdf();}static void W…

SpringBoot3与AOP完美结合:轻松追踪用户操作,实现精准日志记录

程序员必备宝典https://tmxkj.top/#/ 1.pom文件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>com.alibaba</groupId&g…

LlamaIndex 实现 Agent

RAG 是在数据层面为大模型提供更多、更新的外部知识&#xff0c;而 Agent &#xff08;智能体&#xff09;&#xff0c;为大模型扩展了推理业务的能力。数据是静态的&#xff0c;数据周期可能是天、小时甚至到秒&#xff0c;通过 RAG 实现时&#xff0c;需要调用对应系统的 API…

uni-app组件

一. 什么是组件,有什么好处? 在uni-app中&#xff0c;组件是构成应用的基本单位&#xff0c;它们是用来定义用户界面的一部分&#xff0c;并且通常包含了视图和逻辑。组件的设计使得开发者能够以声明式的方式构建应用界面&#xff0c;并且通过组件化的开发方式来提高代码的复…

vue-cli搭建项目过程

一.前言 传统的前端项目架构&#xff1a; 指的就是一个项目中有很多个HTML文件&#xff0c;每一个HTML文件都是相互独立的&#xff0c;如果需要在页面中导入一些外部依赖的css,js文件&#xff0c;就需要在每一个html文件中都导入就会显得特别麻烦&#xff0c;而且这些外部依赖…

详细git使用教程以及git base here命令行

0 下载 这个是官网下载特别慢 Git - Downloads (git-scm.com) 1 最基本操作与初始配置 1.1&#xff0c;linux的基本命令可用 下载安装后鼠标右键选git base here即可打开 1.2&#xff0c;git init /git clone初始化&#xff0c;创建本地仓库 出现.git隐藏文件 git clone “…

Big Model Weekly | 第34期

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 01 MiniCPM-V: A GPT-4V Level MLLM on Your Phone 近期多模态大型语言模型&#xff08;MLLMs&#xff09;的快速发展&#xff0c;从根本上改变了人工智能研究和产业的格局&#xff0c;为实现人工智能的下一个重…

Java去掉字符串中的特殊符号只保留中文数字和字母

今天在做一个导入功能发现用户导入的数据有特殊符号&#xff0c;于是想着给他去掉&#xff0c;搜了一下发现大多数方法都只保留了字母数字&#xff0c;连中文都去掉了&#xff0c;这很明显不符合我的需求 直接上代码 /*** author Sakura* date 2024/8/27 15:18*/ public clas…

Python(C++)自动微分导图

&#x1f3af;要点 反向传播矢量化计算方式前向传递和后向传递计算方式图节点拓扑排序一阶二阶前向和伴随模式计算二元分类中生成系数高斯噪声和特征二元二次方程有向无环计算图超平面搜索前向梯度下降算法快速傅里叶变换材料应力和切线算子GPU CUDA 神经网络算术微分 Pytho…

使用谷歌浏览器查看原型

需求人员给了一个原型文件包&#xff0c;用谷歌浏览器打开提示以下内容&#xff1a; 找到需求人员发的原型文件包 进入到resources-->chrome&#xff0c;找到axure-chrome-extension.crx&#xff0c;复制一份出来命名为axure-chrome-extension.tar&#xff0c;然后在该目录下…

招联金融基于 Apache Doris 数仓升级:单集群 QPS 超 10w,存储成本降低 70%

在竞争激烈的消费金融市场中&#xff0c;有效利用海量数据、提升业务运营效率是赢得市场的关键。早期招联采用典型的 Lambda 架构提供业务报表、数据运营、个性推荐、风险控制等数据服务&#xff0c;而 Lambda 过多的技术栈也引发了数据孤岛、查询效率不足、代码复用性差以及开…

AI算法平台训练站裸土检测算法训练裸土检测算法源码

在全球化进程加快与环境问题日益突出的今天&#xff0c;裸土检测成为了环境监测和土壤管理中不可或缺的一环。裸土指的是没有植被覆盖的土壤区域&#xff0c;这些区域易受侵蚀&#xff0c;并可能导致土壤流失和环境退化。为了有效应对这些问题&#xff0c;裸土检测算法应运而生…

Redis持久化与主从同步

1 淘汰策略 127.0.0.1:6379> help expireEXPIRE key secondssummary: Set a keys time to live in secondssince: 1.0.0group: generic127.0.0.1:6379> help PEXPIREPEXPIRE key millisecondssummary: Set a keys time to live in millisecondssince: 2.6.0group: gener…

【CSP:202112-1】序列查询(Java)

题目链接 202112-1 序列查询 题目描述 求解思路 模拟&#xff1a;a数组可以看作是记录 f ( x ) f(x) f(x) 函数值发生变化出的 x x x 点&#xff08;每次自增1&#xff09;。因此将每段相同数值的 f ( x ) f(x) f(x) 用乘法计算出来即可&#xff0c;最后记得要加上最后一…