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

news2024/10/9 5:25:10

在这里插入图片描述

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

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

在计算机网络中,限流就是控制网络接口发送或接收请求的速率,它可防止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示例+压测
  • (微服务)服务治理:几种开源限流算法库/应用软件介绍和使用
  • 令牌桶算法限流
  • 常见限流算法

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

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

相关文章

MyBatis-Plus03

测试自定义功能 首先创建mapper文件夹。 在UserMapper下编写sql语句&#xff08;把namespace改为自己的&#xff09; <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""…

查询SQL server数据库在后台执行过的语句

查询SQL server数据库在后台执行过的语句 SELECT TOP 30000total_worker_time/1000 AS [总消耗CPU 时间(ms)],execution_count [运行次数],qs.total_worker_time/qs.execution_count/1000 AS [平均消耗CPU 时间(ms)],last_execution_time AS [最后一次执行时间],min_worker_ti…

Windows系统基于WSL子系统的torchquantum安装记录GPU版本

子系统需要的环境&#xff1a; anaconda/miniconda、pip换源(清华源) 1.准备 torchquantum最新版本可以从github上找到&#xff0c;直接clone/下载整个project&#xff0c;查看环境要求&#xff0c;需要安装pytorch和tensorflow 新建一个conda环境&#xff0c;注意python最…

算法沉淀——动态规划篇(子数组系列问题(下))

算法沉淀——动态规划篇&#xff08;子数组系列问题&#xff08;下&#xff09;&#xff09; 前言一、等差数列划分二、最长湍流子数组三、单词拆分四、环绕字符串中唯一的子字符串 前言 几乎所有的动态规划问题大致可分为以下5个步骤&#xff0c;后续所有问题分析都将基于此 …

【JavaScript 漫游】【052】Proxy

文章简介 本篇文章为【JavaScript 漫游】专栏的第 052 篇文章&#xff0c;记录了 ES6 规范中 Proxy 的知识点。 概述 Proxy 用于修改某些操作的默认行为&#xff0c;等同于在语言层面做出修改&#xff0c;所以属于一种“元编程”&#xff08;meta programming&#xff09;&a…

微信公众号如何开通留言功能?

首先&#xff0c;我们需要了解为什么现在注册的公众号没有留言功能。这是因为所有在2018年之后注册的微信公众号都无法再自带留言功能。这一变化是根据微信的通知而实施的。自2018年2月12日起&#xff0c;微信对新注册的公众号进行了调整&#xff0c;取消了留言功能。这一决策主…

多线程重点知识(个人整理笔记)

目录 1. java 多线程 1.1. 什么是进程?什么是线程? 1.1.1. 进程 1.1.2. 线程 1.1.3. 多线程 2. 并行和并发有什么区别&#xff1f; 3. 守护线程是什么&#xff1f; 4. 创建线程有哪几种方式&#xff1f; 4.1. 线程的常见成员方法 5. 线程安全问题 5.1. synchronize…

伪造靶机之iptables

伪造禁ping、网络不可达、主机不可达、协议、端口的命令 iptables -A INPUT -p icmp --icmp-type echo-request -j DROP iptables -A INPUT -s 172.18.6.89 -p icmp -j REJECT --reject-with icmp-net-unreachable iptables -A INPUT -s 172.18.6.89 -p icmp -j REJECT --re…

HCIA笔记

console 登录设备的特点&#xff1a; 1、带外&#xff0c;不依赖网络本身的连通性。 2、独占&#xff0c;console口不能被多人同时使用&#xff0c;具备唯一性。 3、本地&#xff0c;console口长度有限&#xff0c;一般只能在机房或者设备现场来使用。 4、只能实现命令行的管理…

Golang | Leetcode Golang题解之第7题整数反转

题目&#xff1a; 题解&#xff1a; func reverse(x int) (rev int) {for x ! 0 {if rev < math.MinInt32/10 || rev > math.MaxInt32/10 {return 0}digit : x % 10x / 10rev rev*10 digit}return }

一文搞懂cookie,session,token,JWT到底是怎么进行验证的???

文章目录 cookiesessiontokenJWT 比较 HTTP 协议是一种无状态协议&#xff0c;每次服务端接收到客户端的请求时&#xff0c;都是一个全新且独立请求&#xff0c;这样就无法获取历史请求的记录&#xff0c;为了解决这种机制&#xff0c;让某个域名下的所有网页能够共享某些数据&…

openlayers 入门教程(九):overlay 篇

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

云原生技术精选:探索腾讯云容器与函数计算的最佳实践

文章目录 写在前面《2023腾讯云容器和函数计算技术实践精选集》深度解读案例集特色&#xff1a;腾讯云的创新实践与技术突破精选案例分析——Stable Diffusion云原生部署的最佳实践精选集实用建议分享总结 写在前面 在数字化转型的浪潮下&#xff0c;云计算技术已成为企业运营…

电脑上怎么压缩图片?三个处理方法介绍

随着我们现在使用图片的地方越来越多&#xff0c;我们处理图片的情况也比较多了&#xff0c;通过压缩图片大小可以使图片文件更小&#xff0c;从而减少存储空间和带宽的使用&#xff0c;同时也可以提高加载速度和性能。良好的图片压缩可以有效地减少文件大小&#xff0c;同时保…

【Spring】使用@Bean和@Import注解配置Bean,与Bean的实例化

目录 1、bean是什么 2、配置bean 2.1、使用Bean注解配置Bean 2.2、使用Import注解配置Bean 3、实例化Bean 1、bean是什么 在 Spring 中&#xff0c;Bean 是指由 Spring 容器管理的对象。Spring IOC 容器负责创建、配置和管理这些 Bean 对象的生命周期。Spring IOC 容器会管…

Linux简单介绍

Linux简单介绍 编译器VMware虚拟机Ubuntu——LinuxOS为什么使用LinuxOS&#xff1f; 目录结构Windows目录结构Linux操作系统home是不是家目录&#xff1f; Linux常用命令终端命令行提示符与权限切换命令tab 作用&#xff1a;自动补全上下箭头pwd命令ls命令mkdir命令touch命令rm…

C++实现vector

目录 前言 1.成员变量 2.成员函数 2.1构造函数 2.2析构函数 2.3begin,end 2.4获取size和capacity 2.5函数重载【】 2.6扩容reserve 2.7resize 2.8insert 2.9删除 2.10尾插、尾删 3.0拷贝构造函数 3.1赋值运算符重载 前言 自主实现C中vector大部分的功能可以使我们更好的理解并使…

flink源码编译-job提交

1、启动standalone集群的taskmanager standalone集群中的taskmanager启动类为 TaskManagerRunner 2 打开master启动类 通过 ctrln快捷键&#xff0c;找到、并打开类&#xff1a; org.apache.flink.runtime.taskexecutor.TaskManagerRunner 3 修改运⾏配置 基本完全按照mas…

高等数学基础篇之导数与微分的运算法则

导数与微分&#xff1a; 一、导数基本公式 二、微分基本公式 三、导数运算法则 四、微分运算法则 一、导数基本公式 二、微分基本公式 三、导数运算法则 四、微分运算法则 有理运算法则 设f(x), g(x)在x处可导&#xff0c;则&#xff1a; 复合函数运算法则 设 yf(u), ug…

【JavaScript】函数 ① ( 函数引入 | 函数声明 | 函数调用 )

文章目录 一、JavaScript 函数1、函数引入2、函数声明3、函数调用4、代码示例 - 函数声明调用 一、JavaScript 函数 1、函数引入 JavaScript 代码编写时 , 会遇到 定义 大量相同或相似代码的 场景 , 这些代码可能需要重复使用 , 这种情况下就需要 将 这些代码 定义在 函数 中 ;…