Go语言如何实现限制用户 1 分钟内最多请求 1000 次?

文章精选推荐

1 JetBrains Ai assistant 编程工具让你的工作效率翻倍

2 Extra Icons:JetBrains IDE的图标增强神器

3 IDEA插件推荐-SequenceDiagram,自动生成时序图

4 BashSupport Pro 这个ides插件主要是用来干嘛的 ?

5 IDEA必装的插件:Spring Boot Helper的使用与功能特点

6 Ai assistant ,又是一个写代码神器

7 Cursor 设备ID修改器,你的Cursor又可以继续试用了

文章正文

在 Go 语言中,限制用户每分钟最多请求 1000 次的常见做法是使用 限流算法 (Rate Limiting)。有多种算法可以实现这一目标,其中最常见的包括 令牌桶算法 (Token Bucket)漏桶算法 (Leaky Bucket)计数器算法 (Counter)。每种算法有其特点和适用场景,下面将逐个介绍,并附上相应的 Go 语言实现。

1. 令牌桶算法 (Token Bucket)

令牌桶算法是常见的限流算法,适用于需要平滑流量控制的场景。令牌桶维护一个存储令牌的桶,每个请求需要消耗一个令牌。如果桶内有足够的令牌,请求可以继续;如果没有令牌,则请求被拒绝。令牌按固定速率生成,当桶满时,额外的令牌会丢弃。

令牌桶算法的实现
go 复制代码
package main

import (
	"fmt"
	"sync"
	"time"
)

type TokenBucket struct {
	rate      int           // 生成令牌的速率,单位是令牌/秒
	capacity  int           // 桶的容量
	tokens    int           // 当前令牌数量
	lastToken time.Time     // 上次生成令牌的时间
	mutex     sync.Mutex    // 用于并发控制
}

func NewTokenBucket(rate, capacity int) *TokenBucket {
	return &TokenBucket{
		rate:     rate,
		capacity: capacity,
		tokens:   capacity, // 初始时,桶里有满的令牌
	}
}

func (tb *TokenBucket) refill() {
	// 计算过去时间段内生成的令牌数
	now := time.Now()
	elapsed := now.Sub(tb.lastToken)
	tb.lastToken = now
	// 按速率生成令牌
	newTokens := int(elapsed.Seconds()) * tb.rate
	if newTokens > 0 {
		// 桶中令牌数增加
		tb.tokens += newTokens
		if tb.tokens > tb.capacity {
			// 超过桶容量,令牌数只能是桶的最大容量
			tb.tokens = tb.capacity
		}
	}
}

func (tb *TokenBucket) Allow() bool {
	tb.mutex.Lock()
	defer tb.mutex.Unlock()
	// 补充令牌
	tb.refill()
	if tb.tokens > 0 {
		// 有令牌可以消耗
		tb.tokens--
		return true
	}
	// 没有令牌可用,限制请求
	return false
}

func main() {
	// 创建令牌桶,令牌生成速率为每秒 1000 个,容量为 1000 个令牌
	tb := NewTokenBucket(1000, 1000)

	// 模拟用户发起请求
	for i := 0; i < 10; i++ {
		if tb.Allow() {
			fmt.Println("Request", i+1, "allowed")
		} else {
			fmt.Println("Request", i+1, "rejected")
		}
		time.Sleep(100 * time.Millisecond) // 模拟请求间隔
	}
}
说明:
  • rate:每秒生成的令牌数。
  • capacity:桶的最大容量。
  • tokens:当前桶中可用的令牌数。
  • 每次请求时,Allow() 方法会检查桶中是否有令牌,如果有,则消耗一个令牌并允许请求;如果没有令牌,则拒绝请求。

2. 漏桶算法 (Leaky Bucket)

漏桶算法是另一种常用的限流算法,适用于流量平滑控制。在漏桶算法中,桶里有水(请求),水按固定速率流出。当请求到来时,如果桶满了,新的请求会被丢弃;如果桶未满,新的请求会被加入桶中,并在固定速率下流出。

漏桶算法的实现
go 复制代码
package main

import (
	"fmt"
	"sync"
	"time"
)

type LeakyBucket struct {
	rate      int           // 水流出速率,单位是请求/秒
	capacity  int           // 桶的容量
	water     int           // 当前桶中水的数量
	lastDrain time.Time     // 上次排水时间
	mutex     sync.Mutex    // 用于并发控制
}

func NewLeakyBucket(rate, capacity int) *LeakyBucket {
	return &LeakyBucket{
		rate:     rate,
		capacity: capacity,
		water:    0, // 初始时,桶里没有水
	}
}

func (lb *LeakyBucket) drain() {
	// 计算过去时间段内排出的请求数
	now := time.Now()
	elapsed := now.Sub(lb.lastDrain)
	lb.lastDrain = now
	// 按排出速率流出请求
	drained := int(elapsed.Seconds()) * lb.rate
	if drained > 0 {
		lb.water -= drained
		if lb.water < 0 {
			lb.water = 0
		}
	}
}

func (lb *LeakyBucket) Allow() bool {
	lb.mutex.Lock()
	defer lb.mutex.Unlock()
	// 排水
	lb.drain()
	if lb.water < lb.capacity {
		// 桶未满,允许请求
		lb.water++
		return true
	}
	// 桶已满,拒绝请求
	return false
}

func main() {
	// 创建漏桶,排水速率为每秒 1000 个,桶的容量为 1000 个
	lb := NewLeakyBucket(1000, 1000)

	// 模拟用户发起请求
	for i := 0; i < 10; i++ {
		if lb.Allow() {
			fmt.Println("Request", i+1, "allowed")
		} else {
			fmt.Println("Request", i+1, "rejected")
		}
		time.Sleep(100 * time.Millisecond) // 模拟请求间隔
	}
}
说明:
  • rate:请求的排出速率。
  • capacity:桶的最大容量。
  • water:当前桶中水(请求)的数量。
  • drain():排水操作,控制请求的流出速率。

3. 计数器算法 (Fixed Window Counter)

计数器算法是最简单的一种限流算法。在每个时间窗口内,记录请求的数量。当请求数达到限制时,就会拒绝进一步的请求。它适用于简单的限流场景,但对于高并发时可能会出现窗口突发的情况。

计数器算法的实现
go 复制代码
package main

import (
	"fmt"
	"sync"
	"time"
)

type Counter struct {
	limit      int           // 请求限制次数
	windowSize time.Duration // 时间窗口大小
	mu         sync.Mutex    // 用于并发控制
	requests   int           // 当前请求计数
	windowStart time.Time    // 当前时间窗口开始时间
}

func NewCounter(limit int, windowSize time.Duration) *Counter {
	return &Counter{
		limit:      limit,
		windowSize: windowSize,
		requests:   0,
		windowStart: time.Now(),
	}
}

func (c *Counter) Allow() bool {
	c.mu.Lock()
	defer c.mu.Unlock()
	// 判断是否在当前时间窗口内
	now := time.Now()
	if now.Sub(c.windowStart) > c.windowSize {
		// 如果超过了窗口时间,则重置请求计数器和窗口开始时间
		c.windowStart = now
		c.requests = 0
	}
	if c.requests < c.limit {
		// 如果请求数未达到限制,允许请求
		c.requests++
		return true
	}
	// 否则,拒绝请求
	return false
}

func main() {
	// 创建计数器,限制每分钟 1000 次请求
	counter := NewCounter(1000, time.Minute)

	// 模拟用户发起请求
	for i := 0; i < 10; i++ {
		if counter.Allow() {
			fmt.Println("Request", i+1, "allowed")
		} else {
			fmt.Println("Request", i+1, "rejected")
		}
		time.Sleep(100 * time.Millisecond) // 模拟请求间隔
	}
}
说明:
  • limit:时间窗口内允许的最大请求次数。
  • windowSize:时间窗口的大小(比如 1 分钟)。
  • requests:当前时间窗口内已处理的请求数量。
  • Allow():每次请求时,检查当前窗口内请求数是否达到限制。

4. 总结

  • 令牌桶算法(Token Bucket)适用于平滑流量控制,允许突发请求。
  • 漏桶算法(Leaky Bucket)适用于平滑流量,适合流量控制比较严格的场景。
  • 计数器算法(Counter)是最简单的一种限流方式,适合简单的限流需求,但对突发流量处理较差。根据不同的需求场景,选择合适的算法进行实现。
相关推荐
字节跳动数据库7 分钟前
文章分享——相似函数处理方法
人工智能·后端·程序员
云技纵横7 分钟前
@Transactional 失效的 7 种场景:第 5 种最难排查
后端
用户67570498850224 分钟前
你知道 Go 结构体和结构体指针调用的区别吗?一文带你彻底搞懂!
后端·go
程序员cxuan40 分钟前
读懂 Claude Code 架构分析系列,第一篇,开始!
人工智能·后端·架构
用户67570498850244 分钟前
面试官问“装饰器模式”,这样回答薪资多要 3000!
后端
tntxia1 小时前
Geo Scene域名修改引起的一些问题
后端
用户298698530141 小时前
Java 实现 Word 文档加密与权限解除
java·后端
vanuan1 小时前
给你的A2A-Agent加把锁-认证鉴权实战指南
后端
Yeats_Liao1 小时前
14:Servlet中的页面跳转-Java Web
java·后端·架构