返回

.NET如何实现限流?ASP.NET Core RateLimiter中间件使用教程

2026-10-01 .NET ASP.NET Core 1 0

在 Web API 开发中,接口可能遇到短时间大量请求,例如登录接口被频繁调用、公开 API 被高频访问、某个查询接口突然出现流量峰值。ASP.NET Core 已经提供 Microsoft.AspNetCore.RateLimiting,可以直接在应用层配置请求速率限制。

RateLimiter 支持固定窗口、滑动窗口、令牌桶和并发限制四种常见算法,可以针对整个应用,也可以单独限制某些 API。

注册RateLimiter服务

在 Program.cs 中调用 AddRateLimiter:

using System.Threading.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("api", limiterOptions =>
    {
        limiterOptions.PermitLimit = 10;
        limiterOptions.Window = TimeSpan.FromMinutes(1);
        limiterOptions.QueueLimit = 0;
    });
});

var app = builder.Build();

app.UseRateLimiter();

app.MapGet("/api/test", () =>
{
    return Results.Ok("请求成功");
}).RequireRateLimiting("api");

app.Run();

上面的配置表示 /api/test 每分钟最多允许 10 个请求,超过限制的请求会被拒绝。

ASP.NET Core 8 开始,使用 RateLimiter 中间件时需要调用 AddRateLimiter 注册相关服务,否则应用启动时会出现服务未注册错误。

固定窗口限流

AddFixedWindowLimiter 是比较容易理解的一种方式。

例如:

options.AddFixedWindowLimiter("fixed", opt =>
{
    opt.PermitLimit = 100;
    opt.Window = TimeSpan.FromMinutes(1);
    opt.QueueLimit = 0;
});

表示一个时间窗口允许 100 次请求。窗口结束后重新计算。

它适合普通查询接口、公开 API 等场景。如果接口流量比较稳定,固定窗口配置起来比较简单。

滑动窗口限流

滑动窗口可以减少固定窗口切换时出现的流量突增问题:

builder.Services.AddRateLimiter(options =>
{
    options.AddSlidingWindowLimiter("sliding", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(1);
        opt.SegmentsPerWindow = 6;
        opt.QueueLimit = 0;
    });
});

这里把一分钟划分为 6 个时间段,限制器会根据滑动窗口中的请求数量进行判断。微软官方提供了 AddSlidingWindowLimiter 对应的配置方式。

令牌桶限流

令牌桶适合需要允许短时间突发流量的 API:

builder.Services.AddRateLimiter(options =>
{
    options.AddTokenBucketLimiter("token", opt =>
    {
        opt.TokenLimit = 20;
        opt.TokensPerPeriod = 5;
        opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
        opt.QueueLimit = 0;
    });
});

可以把令牌理解成接口调用额度。请求消耗令牌,系统按照设定周期补充令牌。短时间内积累的令牌可以处理一定程度的突发请求。

按照IP地址限流

实际项目中,经常需要给不同客户端分别计算请求次数。这时可以使用分区限流。

builder.Services.AddRateLimiter(options =>
{
    options.GlobalLimiter =
        PartitionedRateLimiter.Create<HttpContext, string>(context =>
        {
            var ip = context.Connection.RemoteIpAddress?.ToString()
                     ?? "unknown";

            return RateLimitPartition.GetFixedWindowLimiter(
                ip,
                _ => new FixedWindowRateLimiterOptions
                {
                    PermitLimit = 60,
                    Window = TimeSpan.FromMinutes(1),
                    QueueLimit = 0
                });
        });
});

这样每个 IP 都拥有独立的请求计数器,一个客户端达到 60 次限制后,不会直接消耗其他客户端的额度。

RateLimiter 的分区键还可以使用用户 ID、API Key 等数据。

限制并发请求数量

有些接口执行时间比较长,例如导出 Excel、生成报表、复杂数据库查询。这种情况下,单纯限制一分钟请求数量可能不够,可以使用并发限制:

builder.Services.AddRateLimiter(options =>
{
    options.AddConcurrencyLimiter("concurrent", opt =>
    {
        opt.PermitLimit = 10;
        opt.QueueLimit = 5;
        opt.QueueProcessingOrder =
            QueueProcessingOrder.OldestFirst;
    });
});

这里最多允许 10 个请求同时执行,额外请求最多排队 5 个。

并发限制关注的是“同时运行多少个请求”,固定窗口、滑动窗口和令牌桶关注的是“一段时间允许多少个请求”。

超过限制返回429

生产环境通常建议把被限流的请求返回 HTTP 429:

builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    options.AddFixedWindowLimiter("api", opt =>
    {
        opt.PermitLimit = 10;
        opt.Window = TimeSpan.FromMinutes(1);
        opt.QueueLimit = 0;
    });
});

也可以通过 OnRejected 自定义响应内容,并根据限制器提供的 Retry-After 信息告诉客户端什么时候重试。

项目中怎么选择限流方式

普通 API 可以从固定窗口开始配置,例如每个用户每分钟 60 次。接口存在明显突发流量时,可以考虑令牌桶。需要控制数据库、CPU 密集型接口的同时执行数量时,可以使用并发限制。对流量连续性要求较高的场景,可以选择滑动窗口。还可以给不同接口配置不同策略,例如登录接口限制得更严格,普通查询接口设置较高额度,文件导出接口单独限制并发数。

RateLimiter 本身属于应用层限流。如果应用部署了多个实例,还需要考虑各实例之间的限流计数是否需要统一,单机内存计数并不能自动变成整个集群的统一计数器。

总结

ASP.NET Core RateLimiter 已经覆盖了 Web API 中常见的限流需求。基本使用流程就是通过 AddRateLimiter 注册策略,调用 UseRateLimiter 启用中间件,再使用 RequireRateLimiting 将策略应用到指定接口。

实际项目不要直接照搬某个固定数字。应结合接口访问量、数据库压力、CPU 使用率和正常用户行为进行压测,再确定 PermitLimit、窗口时间、队列数量等参数。微软官方也建议上线前对限流配置进行充分测试。

顶部