You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用弹性策略管理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);
            }
        }
    }
}

关键注意事项

  1. 端点匹配灵活性:使用路由模板匹配支持带参数的端点,避免硬编码具体路径
  2. 队列处理:设置QueueLimit可让超出限额的请求进入等待队列,而非直接被拒绝,适合持续采集场景
  3. 监控与统计:可通过_rateLimiter.GetMetrics()获取限流统计数据(已使用许可数、排队数等),便于监控信用分使用情况
  4. 自定义限流算法:如果固定窗口不满足需求,可继承RateLimiter抽象类实现支持权重的滑动窗口或令牌桶算法

内容的提问来源于stack exchange,提问作者E.Benedos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 17:24:56