[每周一更]-第92期:Go项目中的限流算法

在这里插入图片描述

这周五在清明假期内,提前更新文章

很多业务会有限流的场景,比如活动秒杀、社区搜索查询、社区留言功能;保护自身系统和下游系统不被巨型流量冲垮等。

在计算机网络中,限流就是控制网络接口发送或接收请求的速率,它可防止DoS攻击和限制Web爬虫。

限流,也称流量控制。是指系统在面临高并发,或者大流量请求的情况下,限制新的请求对系统的访问,从而保证系统的稳定性。限流会导致部分用户请求处理不及时或者被拒,这就影响了用户体验。所以一般需要在系统稳定和用户体验之间平衡一下。

一、场景

Go语言的限流场景非常广泛,适用于各种需要控制访问速率的场景。以下是一些常见的限流场景:

  • API服务限流: 假设你正在开发一个API服务,你可以使用限流来限制对API端点的访问速率,以防止恶意攻击或过度使用API。例如,你可以限制每个IP地址的请求速率,或者根据API密钥对用户进行限流。
  • Web爬虫控制: 如果你正在开发一个网络爬虫,你可能需要限制爬取网站的速率,以避免对目标网站造成过大的负担。你可以使用限流来控制爬取速率,以免被目标网站封禁IP地址。
  • 消息队列消费者: 在处理消息队列时,你可能需要控制消费者的处理速率,以防止过度消费消息。你可以使用限流来控制消费者从消息队列中拉取消息的速率,确保系统稳定运行。
  • 数据库访问限流: 在高并发的情况下,数据库可能成为瓶颈。你可以使用限流来控制对数据库的并发访问,以避免数据库超载或数据库连接池耗尽。
  • RPC调用限流: 如果你的应用程序依赖于其他服务的RPC调用,你可能需要限制对RPC服务的调用速率,以避免对目标服务造成过大的负荷或超出服务提供商的配额。
  • 任务调度: 控制任务调度器并发执行任务的速率,以避免过度消耗系统资源或超出服务协议。
  • 文件IO: 控制对文件系统的读写操作,以避免过度消耗系统资源。

二、常用算法

限流算法用于控制系统的流量,防止系统被过载。

  • 令牌桶算法(Token Bucket Algorithm): 令牌桶算法是一种基于令牌桶的限流算法。在这个算法中,有一个固定容量的桶,以固定的速率往桶里添加令牌。每当有请求到来时,必须从桶中获取一个令牌,如果桶中没有足够的令牌,则拒绝该请求或等待直到有足够的令牌为止。
  • 漏桶算法(Leaky Bucket Algorithm): 漏桶算法是一种基于漏桶的限流算法。在这个算法中,有一个固定容量的漏桶,以固定的速率漏水。每当有请求到来时,将请求放入漏桶中,如果漏桶已满,则拒绝该请求;否则,请求在漏桶中等待一段时间后被处理。
  • 计数器算法(Counter Algorithm)也叫固定窗口算法: 计数器算法是一种简单的限流算法。在这个算法中,统计一定时间窗口内的请求次数,如果请求次数超过了设定的阈值,则拒绝后续的请求。
  • 滑动窗口算法(Sliding Window Algorithm): 滑动窗口算法是一种动态调整窗口大小的限流算法。在这个算法中,统计一定时间窗口内的请求次数,如果请求次数超过了设定的阈值,则拒绝后续的请求。与计数器算法不同的是,滑动窗口算法可以动态调整时间窗口的大小,适应不同的流量变化。

以下看下各个算法的图及示例:

2.1、令牌桶算法

在这里插入图片描述

package main

import (
	"time"

	"golang.org/x/time/rate"
)

func main() {
	// 创建一个令牌桶,每秒产生3个令牌
	limiter := rate.NewLimiter(3, 1)

	// 模拟10次请求
	for i := 0; i < 10; i++ {
		// 获取一个令牌,如果没有可用的令牌则阻塞等待
		limiter.WaitN(time.Now(), 1)
		// 处理请求
		handleRequest()
	}
}

func handleRequest() {
	// 模拟处理请求
	println("Handling request...")
}

2.2、漏桶算法

在这里插入图片描述

package main

import (
	"time"
)

func main() {
	// 创建一个容量为3的漏桶,每秒漏水1个
	limiter := NewLeakyBucket(3, 1)

	// 模拟10次请求
	for i := 0; i < 10; i++ {
		// 获取一个漏桶令牌,如果漏桶已满则阻塞等待
		limiter.Wait()
		// 处理请求
		handleRequest()
	}
}

func handleRequest() {
	// 模拟处理请求
	println("Handling request...")
}

// 漏桶结构体
type LeakyBucket struct {
	capacity   int           // 漏桶容量
	rate       time.Duration // 漏桶速率
	lastLeak   time.Time     // 上一次漏水时间
	dripAmount int           // 漏水数量
}

// 创建一个新的漏桶
func NewLeakyBucket(capacity int, ratePerSecond int) *LeakyBucket {
	return &LeakyBucket{
		capacity:   capacity,
		rate:       time.Second / time.Duration(ratePerSecond),
		lastLeak:   time.Now(),
		dripAmount: 0,
	}
}

// 获取一个漏桶令牌,如果漏桶已满则阻塞等待
func (lb *LeakyBucket) Wait() {
	now := time.Now()
	// 计算自上一次漏水以来应该漏掉的数量
	lb.dripAmount += int(now.Sub(lb.lastLeak) / lb.rate)
	// 如果漏桶溢满,等待一段时间
	if lb.dripAmount > lb.capacity {
		time.Sleep(lb.rate)
	}
	// 更新上一次漏水时间
	lb.lastLeak = now
	// 漏水一个令牌
	lb.dripAmount--
}

2.3、计数器算法

在这里插入图片描述

package main

import (
	"time"
)

func main() {
	// 创建一个计数器限流器,每秒最多处理3个请求
	limiter := NewCounterLimiter(3, time.Second)

	// 模拟10次请求
	for i := 0; i < 10; i++ {
		// 判断是否允许进行请求
		if limiter.Allow() {
			// 处理请求
			handleRequest()
		} else {
			// 请求被限流,打印提示信息
			println("Request limited, please try again later.")
		}
	}
}

func handleRequest() {
	// 模拟处理请求
	println("Handling request...")
}

// 计数器限流器结构体
type CounterLimiter struct {
	counter    int           // 当前计数器值
	limit      int           // 计数器限制
	interval   time.Duration // 时间间隔
	lastUpdate time.Time     // 上次更新时间
}

// 创建一个新的计数器限流器
func NewCounterLimiter(limit int, interval time.Duration) *CounterLimiter {
	return &CounterLimiter{
		counter:    0,
		limit:      limit,
		interval:   interval,
		lastUpdate: time.Now(),
	}
}

// 判断是否允许进行请求
func (limiter *CounterLimiter) Allow() bool {
	// 更新计数器值
	limiter.updateCounter()
	// 判断计数器值是否超过限制
	if limiter.counter >= limiter.limit {
		return false
	}
	// 计数器值加1,表示处理一个请求
	limiter.counter++
	return true
}

// 更新计数器值
func (limiter *CounterLimiter) updateCounter() {
	// 计算距离上次更新的时间间隔
	interval := time.Since(limiter.lastUpdate)
	// 如果时间间隔大于限定的间隔,则重置计数器
	if interval >= limiter.interval {
		limiter.counter = 0
		limiter.lastUpdate = time.Now()
	}
}

2.4、滑动窗口算法

在这里插入图片描述

package main

import (
	"time"
)

func main() {
	// 初始化一个滑动窗口限流器,窗口大小为1秒,允许的请求数为3
	limiter := NewSlidingWindowLimiter(3, 1*time.Second)

	// 模拟10次请求
	for i := 0; i < 10; i++ {
		// 判断是否允许进行请求,如果超过限制则等待
		for !limiter.Allow() {
			time.Sleep(time.Millisecond * 100)
		}
		// 处理请求
		handleRequest()
	}
}

func handleRequest() {
	// 模拟处理请求
	println("Handling request...")
}

// 滑动窗口限流器结构体
type SlidingWindowLimiter struct {
	requests []time.Time // 存储每个请求的时间戳
	limit    int         // 允许的请求数
	interval time.Duration // 时间窗口大小
}

// 创建一个新的滑动窗口限流器
func NewSlidingWindowLimiter(limit int, interval time.Duration) *SlidingWindowLimiter {
	return &SlidingWindowLimiter{
		requests: make([]time.Time, 0),
		limit:    limit,
		interval: interval,
	}
}

// 判断是否允许进行请求
func (limiter *SlidingWindowLimiter) Allow() bool {
	// 移除时间窗口外的请求
	for len(limiter.requests) > 0 && time.Since(limiter.requests[0]) > limiter.interval {
		limiter.requests = limiter.requests[1:]
	}
	// 如果请求数超过限制,则拒绝请求
	if len(limiter.requests) >= limiter.limit {
		return false
	}
	// 记录当前请求时间
	limiter.requests = append(limiter.requests, time.Now())
	return true
}

在Go语言中,可以使用一些库来实现限流,例如:

Go 在 x 标准库,即 golang.org/x/time/rate 里自带了一个限流器,这个限流器是基于令牌桶算法(token bucket)实现的。

  • github.com/juju/ratelimit:提供基于令牌桶算法的限流实现。
  • github.com/golang/groupcache:提供的并发访问控制工具可以用于限制对共享资源的并发访问。
  • github.com/uber-go/ratelimit:提供了令牌桶和漏桶算法的实现,支持更复杂的限流策略。

三、优缺点对比

这四种限流算法各有优缺点,简要的比较:

  1. 令牌桶算法:
    • 优点:
      • 令牌桶算法可以在令牌足够的情况下,突发地处理一定数量的请求。
      • 算法简单,容易实现。
    • 缺点:
      • 实现相对复杂,需要维护令牌桶的状态。
      • 无法应对突发流量,因为在令牌桶为空时无法处理任何请求。
  2. 漏桶算法:
    • 优点:
      • 漏桶算法可以对突发流量进行平滑处理,稳定输出。
      • 算法简单,容易实现。
    • 缺点:
      • 无法应对突发流量,因为漏桶已满时无法处理任何请求。
      • 对于突发流量的应对能力较弱。
  3. 计数器算法(固定窗口算法):
    • 优点:
      • 计数器算法简单直观,易于实现。
      • 可以精确控制单位时间内的请求量。
    • 缺点:
      • 对于突发流量的应对能力较弱,无法动态调整限流窗口。
      • 无法应对突发流量,因为在计数器达到限制后无法处理任何请求。
  4. 滑动窗口算法:
    • 优点:
      • 滑动窗口算法可以动态调整限流窗口的大小,适应不同的流量情况。
      • 对于突发流量的应对能力较强。
    • 缺点:
      • 算法相对复杂,实现较为困难。
      • 需要维护窗口中的请求记录,可能会消耗较多的内存。

四、参考

  • x_rate
  • go-zero_limiter示例+压测
  • (微服务)服务治理:几种开源限流算法库/应用软件介绍和使用
  • 令牌桶算法限流
  • 常见限流算法

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

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

相关文章

k8s 部署 canal 集群,RocketMQ 模式

k8s 部署 canal 集群&#xff0c;RocketMQ 模式 k8s 部署 canal 集群&#xff0c;RocketMQ 模式前提MySQLRocketMQ制作 canal-admin、canal-server 镜像 部署 zookeeper部署 canal-admin部署 canal-server测试 k8s 部署 canal 集群&#xff0c;RocketMQ 模式 前提 MySQL 开启…

idea2023.2.1 java项目-web项目创建-servlet类得创建

如何创建Java项目 1.1 方式1&#xff1a; 1.2 方式&#xff1a; 1.3 方式 如何创建web项目 方式 ----- 推荐 如何创建servlet类 复制6 中得代码 给servlet 配置一个路径 启动tomcat 成功了

Plonky2.5:在Plonky2中验证Plonky3 proof

1. 引言 Plonky2.5为QED Protocol团队主导的项目&#xff0c;定位为&#xff1a; 在Plonky2 SNARK中验证Plonky3 STARK proof。 从而实现Plonky系列的递归证明。 开源代码实现见&#xff1a; https://github.com/QEDProtocol/plonky2.5https://github.com/Plonky3/Plonky3&a…

go库x/text缺陷报告CVE-2022-32149的处理方案

#问题描述 go库 golang.org/x/text &#xff0c;注意这里不是go的源码&#xff0c; 在0.3.8版本之前存在一个缺陷(Vulnerability) 缺陷ID CVE-2022-32149 具体描述 攻击者可以通过制作一个Accept-Language报头来导致拒绝服务。 具体的原因是&#xff0c;在解析这个Accept-L…

Scala第十九章节(Actor的相关概述、Actor发送和接收消息以及WordCount案例)

Scala第十九章节 章节目标 了解Actor的相关概述掌握Actor发送和接收消息掌握WordCount案例 1. Actor介绍 Scala中的Actor并发编程模型可以用来开发比Java线程效率更高的并发程序。我们学习Scala Actor的目的主要是为后续学习Akka做准备。 1.1 Java并发编程的问题 在Java并…

如何通过ArkTS卡片的Canvas自定义绘制能力实现五子棋游戏卡片

介绍 本示例展示了如何通过ArkTS卡片的Canvas自定义绘制能力实现一个简单的五子棋游戏卡片。 使用Canvas绘制棋盘和黑白棋子的落子。通过卡片支持的点击事件进行交互&#xff0c;让用户在棋盘上进行黑白棋子的对局。通过TS的逻辑代码实现五子棋输赢判定、回退等逻辑计算&…

Linux制作C++静态库和动态库并使用示例

创建动态库&#xff1a; 编写源文件&#xff1a; // sub.h 显式调用 #include <iostream>extern "C" int sub(int a, int b);// sub.cpp #include "sub.h"int sub(int a, int b) {return a - b; }// quadrature.h 隐式调用 #include <iostream&…

dhcp中继代理

不同过路由器分配ip了&#xff0c;通过一台服务器来代替&#xff0c;路由器充当中继代理功能&#xff0c;如下图 服务器地址&#xff1a;172.10.1.1/24 配置流程&#xff1a; 1.使能dhcp功能 2.各个接口网关地址&#xff0c;配置dhcp中继功能 dhcp select relay &#xff0…

redis---位图Bitmap和位域 Bitfield

位图是字符串类型的拓展&#xff0c;可以使用一个string类型来模拟一个Bit数组。数组的下标就是偏移量&#xff0c;值只有0和1&#xff0c;也支持一些位运算&#xff0c;比如与或非&#xff0c;异或等等&#xff0c;它们的应用场景非常广泛比如可以用来记录用户的签到情况&…

MySQL之索引详细总结

索引简介 索引是帮助MySQL高效获取数据的数据结构(有序)。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用(指向)数据&#xff0c;这样就可以在这些数据结构上实现高级查法&#xff0c;这种数据结构就是索引 为什…

【星计划★C语言】c语言初相识:探索编程之路

&#x1f308;个人主页&#xff1a;聆风吟_ &#x1f525;系列专栏&#xff1a;星计划★C语言、Linux实践室 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️第一个c语言程序二. ⛳️数据类型2.1 &#x1f514;数据单位2.2 &…

【TB作品】STM32单片机读取大气压强传感器BMP280

文章目录 读取效果kei工程使用方法接线方法源码工程下载 读取效果 kei工程 标准库&#xff1a; 使用方法 将这2个文件加入 main示例&#xff1a; #include "sys.h" #include "delay.h" #include "usart.h" #include <stdio.h> #incl…

在线生成占位图片工具:简便快捷的设计利器

title: 在线生成占位图片工具&#xff1a;简便快捷的设计利器 date: 2024/4/4 17:36:41 updated: 2024/4/4 17:36:41 tags: 占位图片网页设计开发工具图片生成页面布局效率提升预览调整 在网页开发或设计过程中&#xff0c;经常会遇到需要临时使用占位图片的情况。占位图片是指…

flutter官方案例context_menus【搭建与效果查看】【省时】

案例地址 https://github.com/flutter/samples/tree/main/context_menus 1&#xff1a;运行查看有什么可以快捷使用的&#xff0c;更新了些什么&#xff0c;可不可以直接复制粘贴 主要内容&#xff1a;在web端中模拟手机类型的点击长按操作&#xff0c;不能直接运行在安卓与io…

专升本--python运算符总结

运算优先级 同一个等级是没有先后顺序的&#xff0c;此外&#xff0c;赋值语言的先后问题&#xff1a; 赋值的顺序从上往下&#xff0c;同一行一般都是代表同时进行赋值&#xff0c;如图所示&#xff1a; 一.and A and B&#xff0c;若A,B有任意一个为假&#xff08;0&#x…

论文笔记:Teaching Large Language Models to Self-Debug

ICLR 2024 REVIEWER打分 6666 1 论文介绍 论文提出了一种名为 Self-Debugging 的方法&#xff0c;通过执行生成的代码并基于代码和执行结果生成反馈信息&#xff0c;来引导模型进行调试不同于需要额外训练/微调模型的方法&#xff0c;Self-Debugging 通过代码解释来指导模型识…

复现k8s黄金票据学习

1.什么是黄金票据 在 Kubernetes 中&#xff0c;"黄金票据"并不是一个常见的术语。可能你想了解的是服务账户&#xff08;Service Account&#xff09;。服务账户是 Kubernetes 中用于身份验证和授权的一种机制。它们允许 Pods 或其他工作负载在 Kubernetes 集群中与…

什么是CSGO游戏搬砖及游戏搬砖注意事项?

CSGO市场是指《反恐精英&#xff1a;全球攻势》游戏内的物品交易市场。玩家可以在这个市场上买卖各类虚拟物品&#xff0c;包括武器皮肤、刀具、手套等。CSGO市场的价格是由供需关系、稀有度、流行度等多个因素影响的。 一般来说&#xff0c;稀有度较高或者比较受欢迎的物品价格…

C++类与对象的初识

面向对象关注的是一个操作需要哪些对象完成。 类是数据&#xff08;变量等&#xff09;与操作&#xff08;函数&#xff09;的集合。 &#xff08;类是对象的图纸&#xff09; 在C中&#xff0c;struct也可以内含成员函数。 调用时&#xff0c;变量名.成员-->与C中结构体…