如何用弹性策略管理HTTP请求权重?.NET 8信用额度限流
基于信用分的HTTP请求限流方案(.NET 8 Worker Service)
针对你的需求——按API端点的信用分消耗限制每分钟请求总额度,且支持动态请求类型,以下是具体实现方案:
核心思路
利用.NET 8自带的System.Threading.RateLimiting组件,将信用分作为限流的许可单位:
- 把每分钟1000信用分的上限设为限流器的总许可数
- 每个请求根据对应端点的信用消耗值,申请对应数量的许可
- 通过HTTP客户端的委托处理器(DelegatingHandler)自动处理请求前的许可申请,无需在Worker逻辑中重复编写限流代码
步骤1:配置端点信用分映射
首先定义每个API端点对应的信用消耗值,可通过配置文件存储:
appsettings.json
"ApiCreditConfig": { "WindowLimit": 1000, "WindowMinutes": 1, "EndpointCredits": { "/api/request1": 20, "/api/request2": 1, "/api/data/{id}": 5 // 支持带参数的端点模板 } }
配置类
public class ApiCreditConfig { public int WindowLimit { get; set; } public int WindowMinutes { get; set; } public Dictionary<string, int> EndpointCredits { get; set; } = new(); }
步骤2:注册带权重支持的固定窗口限流器
使用FixedWindowRateLimiter,将总许可数设为每分钟的信用分上限,同时注册到DI容器:
builder.Services.Configure<ApiCreditConfig>(builder.Configuration.GetSection("ApiCreditConfig")); // 注册固定窗口限流器,以信用分为许可单位 builder.Services.AddSingleton<RateLimiter>(sp => { var config = sp.GetRequiredService<IOptions<ApiCreditConfig>>().Value; return new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions { PermitLimit = config.WindowLimit, Window = TimeSpan.FromMinutes(config.WindowMinutes), QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 200 // 设置等待队列长度,避免请求直接被拒绝 }); });
步骤3:实现限流委托处理器
创建自定义DelegatingHandler,在发送请求前自动匹配端点并申请对应数量的许可,支持带参数的端点模板匹配:
public class CreditLimitingHandler : DelegatingHandler { private readonly RateLimiter _rateLimiter; private readonly ApiCreditConfig _creditConfig; private readonly List<(RouteTemplate Template, int CreditCost)> _endpointTemplates; public CreditLimitingHandler(RateLimiter rateLimiter, IOptions<ApiCreditConfig> creditConfig) { _rateLimiter = rateLimiter; _creditConfig = creditConfig.Value; // 预编译端点路由模板,用于匹配带参数的请求路径 _endpointTemplates = _creditConfig.EndpointCredits .Select(kv => (RouteTemplate.Parse(kv.Key), kv.Value)) .ToList(); } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var requestPath = request.RequestUri?.AbsolutePath ?? string.Empty; int creditCost = 1; // 默认消耗1信用分 // 匹配请求路径对应的信用分(优先匹配模板) foreach (var (template, cost) in _endpointTemplates) { if (TemplateMatcher.Match(template, requestPath, out _)) { creditCost = cost; break; } } // 申请对应数量的信用分许可 using var lease = await _rateLimiter.AcquireAsync(creditCost, cancellationToken); if (!lease.IsAcquired) { // 触发限流时返回429,或根据需求重试/等待 return new HttpResponseMessage(HttpStatusCode.TooManyRequests) { Content = new StringContent($"超出每分钟{_creditConfig.WindowLimit}信用分上限") }; } // 许可获取成功,继续发送请求 return await base.SendAsync(request, cancellationToken); } }
注意:TemplateMatcher来自Microsoft.AspNetCore.Routing包,需要单独安装:
Install-Package Microsoft.AspNetCore.Routing
步骤4:注册HTTP客户端并集成限流
将自定义处理器添加到HTTP客户端,确保所有请求都经过限流检查:
builder.Services.AddHttpClient("DataCollectionClient") .AddHttpMessageHandler<CreditLimitingHandler>(); builder.Services.AddTransient<CreditLimitingHandler>();
步骤5:Worker Service中使用限流客户端
在Worker逻辑中直接使用配置好的HTTP客户端,无需关心限流细节:
public class DataCollectionWorker : BackgroundService { private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger<DataCollectionWorker> _logger; private readonly Random _random = new(); public DataCollectionWorker(IHttpClientFactory httpClientFactory, ILogger<DataCollectionWorker> logger) { _httpClientFactory = httpClientFactory; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var client = _httpClientFactory.CreateClient("DataCollectionClient"); while (!stoppingToken.IsCancellationRequested) { try { // 模拟随机请求不同端点 var endpoints = new[] { "/api/request1", "/api/request2", "/api/data/123" }; var targetEndpoint = endpoints[_random.Next(endpoints.Length)]; var response = await client.GetAsync(targetEndpoint, stoppingToken); if (response.IsSuccessStatusCode) { var data = await response.Content.ReadAsStringAsync(stoppingToken); _logger.LogInformation("从{Endpoint}采集数据成功: {Data}", targetEndpoint, data); } else if (response.StatusCode == HttpStatusCode.TooManyRequests) { _logger.LogWarning("触发信用分限流,等待5秒后重试"); await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); } else { _logger.LogError("请求{Endpoint}失败,状态码: {StatusCode}", targetEndpoint, response.StatusCode); } // 模拟采集间隔 await Task.Delay(TimeSpan.FromMilliseconds(150), stoppingToken); } catch (Exception ex) { _logger.LogError(ex, "采集数据时发生异常"); await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); } } } }
关键注意事项
- 端点匹配灵活性:使用路由模板匹配支持带参数的端点,避免硬编码具体路径
- 队列处理:设置
QueueLimit可让超出限额的请求进入等待队列,而非直接被拒绝,适合持续采集场景 - 监控与统计:可通过
_rateLimiter.GetMetrics()获取限流统计数据(已使用许可数、排队数等),便于监控信用分使用情况 - 自定义限流算法:如果固定窗口不满足需求,可继承
RateLimiter抽象类实现支持权重的滑动窗口或令牌桶算法
内容的提问来源于stack exchange,提问作者E.Benedos
相关产品推荐
相关产品推荐

