调用每秒限3次的外部API仍触发429错误有哪些优化方案
现有方案的问题
你当前的实现是单请求完成后固定延迟1秒,没有全局的调用频率管控:
- 若存在并发调用
GetAll的场景,同一秒内可能有超过3个请求同时打到外部API,直接触发限流 - 延迟放在API请求完成之后,无法控制请求发出的时间点,无法从根源避免流量突刺
可行解决方案
方案1:本地全局限流(单实例部署适用)
通过SemaphoreSlim+时间窗口计数,全局管控每秒最多发出3次请求,代码示例如下:
// 全局静态限流配置,所有GetAll调用共享该配置 private static readonly SemaphoreSlim _limitSemaphore = new SemaphoreSlim(3, 3); private static readonly Queue<DateTime> _requestTimeQueue = new Queue<DateTime>(); private static readonly object _queueLock = new object(); public async Task<YourSessionResponder> GetAll(Guid factoryId) { UserSession.AuthData sessionManager = new UserSession.AuthData { UserName = "xxxx", Password = "xxxx" }; ISessionHandler sessionMgr = new APIclient(); // 先获取限流许可 await _limitSemaphore.WaitAsync(); try { lock (_queueLock) { // 移除1秒之前的请求记录 while (_requestTimeQueue.Count > 0 && DateTime.Now - _requestTimeQueue.Peek() > TimeSpan.FromSeconds(1)) { _requestTimeQueue.Dequeue(); } // 如果当前1秒内请求数已经到3,等待直到窗口过期 if (_requestTimeQueue.Count >= 3) { var waitTime = TimeSpan.FromSeconds(1) - (DateTime.Now - _requestTimeQueue.Peek()); Thread.Sleep(waitTime); // 等待后清空过期记录 while (_requestTimeQueue.Count > 0 && DateTime.Now - _requestTimeQueue.Peek() > TimeSpan.FromSeconds(1)) { _requestTimeQueue.Dequeue(); } } _requestTimeQueue.Enqueue(DateTime.Now); } // 执行API请求 return await sessionMgr.GetDataAsync(sessionManager, new ListerRequest { FactoryId = factoryId }); } finally { _limitSemaphore.Release(); } }
方案2:增加429重试兜底
即使做了限流,也可能因为网络波动、外部API计数偏差等原因触发429,可配合指数退避重试做兜底:
// 最大重试3次,每次重试间隔翻倍 var retryCount = 0; while(retryCount < 3) { try { return await sessionMgr.GetDataAsync(sessionManager, new ListerRequest { FactoryId = factoryId }); } catch (HttpRequestException ex) when (ex.StatusCode == (HttpStatusCode)429) { retryCount++; await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount))); } }
多实例部署补充
如果你的应用是多实例部署,本地静态限流无法跨实例管控总请求量,需要改用分布式限流方案,比如基于Redis实现滑动窗口计数,管控所有实例的总请求量不超过每秒3次。
内容的提问来源于stack exchange,提问作者SkyeBoniwell
相关产品推荐
相关产品推荐

