如何设计Go的Goroutines程序处理API限流错误并控制请求速率
Go 并发API调用限流实现方案
针对每秒最多20次API调用的需求,推荐两种低改造成本的实现方式:
方案1:使用官方扩展库golang.org/x/time/rate令牌桶限流器(推荐)
令牌桶限流器会固定每秒生成20个调用令牌,所有Goroutine必须拿到令牌才能发起API请求,天然满足速率限制要求,改造成本极低:
import ( "sync" "log" "context" "golang.org/x/time/rate" ) // 新增limiter入参 func readInstance(acc string, region string, wg *sync.WaitGroup, limiter *rate.Limiter) { defer wg.Done() // 等待获取令牌,拿不到就阻塞直到有可用令牌 if err := limiter.Wait(context.Background()); err != nil { log.Println("获取令牌失败:", err) return } response, err := client.DescribeInstances(acc, region) if err != nil { log.Println(err) return } // 后续业务逻辑不变 // ... } func main() { accounts := []string{"g", "h", "i", ...} regions := []string{"g", "h", "i", ...} var wg sync.WaitGroup // 初始化限流器:每秒生成20个令牌,桶最大容量20(允许短时间内最多并发20次) limiter := rate.NewLimiter(rate.Limit(20), 20) for _, region := range regions { for i := 0; i < len(accounts); i++ { wg.Add(1) go readInstance(accounts[i], region, &wg, limiter) } } wg.Wait() }
方案2:无第三方依赖的Ticker实现
如果不想引入额外扩展库,可以用定时器配合channel实现速率控制:
import ( "sync" "log" "time" ) func readInstance(acc string, region string, wg *sync.WaitGroup, limiterChan <-chan struct{}) { defer wg.Done() // 等待获取槽位 <-limiterChan response, err := client.DescribeInstances(acc, region) if err != nil { log.Println(err) return } // 后续业务逻辑不变 // ... } func main() { accounts := []string{"g", "h", "i", ...} regions := []string{"g", "h", "i", ...} var wg sync.WaitGroup // 初始化限流槽位channel,容量20 limiterChan := make(chan struct{}, 20) // 启动定时器,每50ms填充一个槽位(1秒/20次=50ms间隔) go func() { ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() for range ticker.C { limiterChan <- struct{}{} } }() for _, region := range regions { for i := 0; i < len(accounts); i++ { wg.Add(1) go readInstance(accounts[i], region, &wg, limiterChan) } } wg.Wait() }
注意事项
- 两种方案都是全局速率控制,刚好匹配API对单个客户端的限流规则
- 如果后续需要调整调用速率,只需要修改限流器的初始化参数即可,无需改动业务逻辑
- 生产环境使用时可以给
limiter.Wait传入带超时的context,避免长时间等待导致程序阻塞
内容的提问来源于stack exchange,提问作者user14171711
相关产品推荐
相关产品推荐

