Go语言中的速率限制与节流:完整指南及示例

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Go语言中的速率限制与节流:如何保护API并保持头脑清醒



想象一下:您用Go编写了一个完美的API。它运行飞快,毫秒级处理请求,然后——砰!——大量请求涌向服务器。可能是机器人,可能是客户端错误,也可能只是您的产品火了。如果没有防护,服务器会崩溃,用户会流失,而您将彻夜排查日志。



熟悉吗?那么速率限制和节流就是您新的最佳伙伴。在本文中,我们将探讨如何在Go中实现它们——Go是一种专为高负载系统设计的语言。没有废话,只有实用的代码和真实案例。



速率限制 vs 节流:有什么区别?



首先,让我们理清术语。很多人混淆这两个概念,但它们是有区别的。



  • 速率限制——是一种严格的限制:“每分钟不超过100个请求”。如果超过限制,服务器会返回429 Too Many Requests。公平且严格。
  • 节流——是一种柔和的调节:“我们会减慢你的速度,但不会阻止你”。例如,不是立即响应所有请求,而是将它们放入队列并以固定速度处理。


在Go中,由于goroutine和通道的存在,这两种方法都可以优雅地实现。让我们看一些具体的例子。



基于通道的最简单速率限制器



限制速度最基本的方法是使用time包中的ticker。它就像一个节拍器:每个“滴答”允许一个请求。



package main

import ( "fmt" "time")

func main() { // 创建一个每200毫秒发送一次令牌的通道 limiter := time.Tick(200 * time.Millisecond)

// 模拟10个请求 for i := 1; i <= 10; i++ { <-limiter // 等待许可 fmt.Printf("请求 %d 在 %s 处理", i, time.Now().Format("15:04:05.000")) }}


这里发生了什么?time.Tick创建一个通道,每200毫秒发送一个值。我们的goroutine在<-limiter处阻塞,直到收到令牌。结果——每秒正好5个请求,没有例外。



缺点:如果请求少于滴答数,我们只能等待。效率低?是的。但对于简单场景——完美。



带缓冲区的突发限制



如果我们需要允许流量“突发”呢?例如,允许立即处理10个请求,然后限制为每秒5个。为此,我们使用带缓冲的通道——经典的令牌桶。



package main

import ( "fmt" "time")

func main() { // 带5个令牌的缓冲通道 tokens := make(chan struct{}, 5)

// 填充goroutine:每200毫秒添加一个令牌 go func() { ticker := time.NewTicker(200 * time.Millisecond) defer ticker.Stop() for range ticker.C { select { case tokens <- struct{}{}: default: // 通道已满——不执行任何操作 } } }()

// 模拟15个请求 for i := 1; i <= 15; i++ { <-tokens // 获取令牌(如果为空则等待) fmt.Printf("请求 %d 在 %s 处理", i, time.Now().Format("15:04:05.000")) time.Sleep(50 * time.Millisecond) // 请求之间的小延迟 }}


前5个请求将立即通过(缓冲区已满),之后——每200毫秒处理一个。这种模式非常适合API,其中客户端可能突然开始发送大量请求(例如,重启后)。



通过WaitGroup和工作池实现节流



现在让我们转向节流。假设我们有一个任务队列(例如,发送邮件群发)。我们不想阻塞客户端,但也不想过载

博客

书籍推荐