C#/.NET多API请求优化咨询:重试与间隔实现方案
.NET调用不稳定API的重试优化方案与示例
核心优化方向
- 指数退避策略:替代固定递增的间隔,采用指数级增长的等待时间(如1s、2s、4s、8s...),避免短时间内重复请求给API造成压力,同时提升重试成功率
- 精准重试判断:只对可恢复的错误(如5xx服务器错误、网络超时、连接失败)进行重试,跳过客户端错误(如400参数错误)
- 请求与客户端复用:RestClient实例全局复用(而非每次创建),减少资源开销;刷新token后需更新请求头实例
- 异步并发控制:针对500+条请求的场景,改用异步并行处理并限制并发数,提升整体处理效率,同时避免触发API限流机制
- 精细化日志:记录每次重试的状态、间隔时间和错误信息,便于排查问题
改进后的代码实现
1. 全局复用RestClient
在类级别初始化RestClient,避免每次请求重复创建:
private readonly RestClient _restClient; private readonly int _maxRetries = 5; private readonly int _baseRetryDelayMs = 1000; // 基础重试间隔 public YourServiceClass(string basePath) { ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; _restClient = new RestClient(basePath); }
2. 带指数退避的异步重试方法
public async Task<Table_Price[]> GetPriceTableAsync(string sku, string token) { Table_Price[] response = Array.Empty<Table_Price>(); Exception lastException = null; // 每次请求创建独立的RestRequest实例,避免并发下请求头被篡改 var request = new RestRequest("api/url", Method.Get); request.AddQueryParameter("sku", sku); request.AddHeader("Authorization", token); request.Timeout = -1; for (int attempt = 0; attempt < _maxRetries; attempt++) { try { response = await _restClient.GetAsync<Table_Price[]>(request); return response; } catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) { // 单独处理401:刷新token并更新请求头 var newToken = await GetTokenAsync(_authRequest); request.RemoveHeader("Authorization"); request.AddHeader("Authorization", $"Bearer {newToken}"); token = newToken; lastException = ex; } catch (Exception ex) when (IsRetryableException(ex)) { lastException = ex; // 指数退避+随机抖动:避免重试风暴 int delayMs = _baseRetryDelayMs * (int)Math.Pow(2, attempt); delayMs = new Random().Next((int)(delayMs * 0.8), (int)(delayMs * 1.2)); // 记录重试日志 _CFMContext.Insert_LOG_Error(BrasiliaTime(), $"GetPriceTable({sku})", $"Retry {attempt + 1}/{_maxRetries} - Error: {ex.Message}, Waiting {delayMs}ms"); await Task.Delay(delayMs); } catch (Exception ex) { // 不可重试的错误直接终止 lastException = ex; break; } } // 所有重试失败,记录最终错误 _CFMContext.Insert_LOG_Error(BrasiliaTime(), $"GetPriceTable({sku})", $"002 - Failed after {_maxRetries} attempts: {lastException?.Message ?? "Unknown error"}"); return response; } // 判断是否为可重试的异常 private bool IsRetryableException(Exception ex) { if (ex is HttpRequestException httpEx) { // 5xx服务器错误或无状态码的网络错误 return httpEx.StatusCode is >= HttpStatusCode.InternalServerError or null; } // 超时、连接失败等网络异常 return ex is TaskCanceledException or SocketException or WebException; }
3. 500+条请求的异步并发处理
用Parallel.ForEachAsync控制并发数,替代同步foreach提升效率:
public async Task ProcessAllRequestsAsync(List<string> skuList, string initialToken) { // 根据API限流规则设置并发数,示例为10 var options = new ParallelOptions { MaxDegreeOfParallelism = 10 }; await Parallel.ForEachAsync(skuList, options, async (sku, cancellationToken) => { try { var result = await GetPriceTableAsync(sku, initialToken); // 处理返回结果逻辑 } catch (Exception ex) { _CFMContext.Insert_LOG_Error(BrasiliaTime(), $"ProcessSku({sku})", $"Failed: {ex.Message}"); } }); }
关键优化点说明
- 指数退避+随机抖动:既避免短时间重复请求压垮API,又防止多客户端同时重试引发的"重试风暴"
- 异步并发处理:相比同步遍历,大幅提升500+条请求的整体处理速度
- 精准重试判断:跳过客户端错误(如400),避免无效重试浪费资源
- 请求实例隔离:每次请求创建独立RestRequest,避免并发场景下请求头被篡改
内容的提问来源于stack exchange,提问作者Willian Lopes
相关产品推荐
相关产品推荐

