在构建Web API服务时,限流(Rate Limiting)是一个至关重要的防护机制。想象一下,你开了一家网红餐厅,突然有1000个顾客同时涌入,厨房根本来不及准备这么多菜品,最终所有顾客都得不到服务——这就是没有限流保护的API面临的真实场景。
API限流主要解决三个核心问题:
在.NET生态中,AspNetCoreRateLimit是目前最成熟的限流解决方案之一。它支持IP、客户端ID等多种限流策略,并能灵活配置限流规则。下面我将结合一个电商API项目的实战经验,详细讲解如何从零实现这套机制。
首先用CLI创建一个基础的Web API项目:
bash复制dotnet new webapi -n RateLimitDemo
cd RateLimitDemo
AspNetCoreRateLimit的核心包有两个版本:
对于大多数场景,IP限流已经足够:
bash复制dotnet add package AspNetCoreRateLimit
注意:当前最新稳定版是4.0.1,但.NET 7项目建议使用3.0.0版本以避免某些兼容性问题。可以通过指定版本号安装:
bash复制dotnet add package AspNetCoreRateLimit --version 3.0.0
在appsettings.json中添加IpRateLimiting节点:
json复制{
"IpRateLimiting": {
"EnableEndpointRateLimiting": false,
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
"IpWhitelist": [],
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1m",
"Limit": 10
},
{
"Endpoint": "POST:/orders",
"Period": "1h",
"Limit": 100
}
]
}
}
关键参数解析:
EnableEndpointRateLimiting:是否启用端点级限流(细粒度控制)Period:支持秒(s)、分(m)、时(h)、天(d)四种单位Limit:在Period时间段内允许的最大请求数实际项目中,我们通常需要设置多级限流规则。例如:
json复制"GeneralRules": [
{
"Endpoint": "*",
"Period": "1m",
"Limit": 60
},
{
"Endpoint": "POST:/account/login",
"Period": "5m",
"Limit": 20
},
{
"Endpoint": "POST:/payment/process",
"Period": "1h",
"Limit": 100
}
]
在Program.cs中添加以下服务注册代码:
csharp复制var builder = WebApplication.CreateBuilder(args);
// 添加内存缓存支持
builder.Services.AddMemoryCache();
// 加载限流配置
builder.Services.Configure<IpRateLimitOptions>(
builder.Configuration.GetSection("IpRateLimiting"));
// 注册限流服务
builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
builder.Services.AddInMemoryRateLimiting();
var app = builder.Build();
// 启用限流中间件(必须在UseRouting之后)
app.UseIpRateLimiting();
app.Run();
限流中间件的注册位置非常关键:
csharp复制// 正确顺序示例
app.UseRouting();
app.UseIpRateLimiting(); // ← 必须在UseRouting之后
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
如果顺序错误(比如放在UseRouting之前),会导致限流不生效。这是因为中间件需要路由信息来判断哪些规则适用。
除了在配置文件中静态设置白名单,我们还可以动态管理:
csharp复制// 在Startup中获取IPolicyStore实例
var ipPolicyStore = app.Services.GetRequiredService<IIpPolicyStore>();
// 动态添加白名单IP
await ipPolicyStore.AddAsync(new IpPolicy
{
Ip = "192.168.1.100",
Rules = new List<RateLimitRule>()
});
默认的内存存储只适合单机部署。对于集群环境,需要改用Redis存储:
bash复制dotnet add package AspNetCoreRateLimit.Redis
csharp复制builder.Services.AddSingleton<IIpPolicyStore, DistributedCacheIpPolicyStore>();
builder.Services.AddSingleton<IRateLimitCounterStore, DistributedCacheRateLimitCounterStore>();
构造连续请求观察响应:
集成测试中可以模拟限流场景:
csharp复制[Fact]
public async Task Should_Return429_When_ExceedRateLimit()
{
// 初始化客户端
var client = _factory.CreateClient();
// 快速发起11次请求(配置限制是10次/分钟)
for (int i = 0; i < 11; i++)
{
var response = await client.GetAsync("/api/products");
// 第11次应该被限制
if (i == 10)
{
Assert.Equal(HttpStatusCode.TooManyRequests, response.StatusCode);
}
}
}
建议配合Application Insights或Prometheus监控限流事件:
csharp复制// 示例:记录限流事件
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 429)
{
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogWarning($"Rate limit triggered for {context.Connection.RemoteIpAddress}");
}
});
更友好的限流方案可以采用"渐进式限制":
这可以通过自定义IRateLimitConfiguration实现。
csharp复制app.UseWhen(context => !context.Request.Path.StartsWithSegments("/static"),
appBuilder => appBuilder.UseIpRateLimiting());
除了AspNetCoreRateLimit,.NET 7+原生提供了更轻量的限流中间件:
csharp复制// .NET 7原生方案
app.UseRateLimiter(new RateLimiterOptions()
.AddFixedWindowLimiter("fixed", options =>
{
options.PermitLimit = 10;
options.Window = TimeSpan.FromMinutes(1);
}));
两种方案对比:
| 特性 | AspNetCoreRateLimit | .NET 7原生方案 |
|---|---|---|
| 配置灵活性 | 高(JSON配置) | 中(代码配置) |
| 分布式支持 | 需要额外包 | 内置 |
| 细粒度控制 | 支持端点级限流 | 支持 |
| 学习曲线 | 中等 | 低 |
| 成熟度 | 高 | 中等 |
对于新项目,如果不需要复杂配置,可以优先考虑.NET 7原生方案。但对于需要精细控制的企业级应用,AspNetCoreRateLimit仍是更成熟的选择。
在实际电商项目中,我们曾遇到过几个典型问题:
误杀合法流量:某促销活动期间,由于CDN节点共享IP,导致大量用户被误判为单个客户端。解决方案是配置ClientIdHeader,让前端在请求头中传递唯一用户标识。
配置热更新需求:通过创建自定义IIpPolicyStore实现,我们开发了通过管理API动态调整限流规则的能力:
csharp复制public class DynamicIpPolicyStore : IIpPolicyStore
{
public Task<bool> UpdateAsync(IpPolicy policy)
{
// 实现动态更新逻辑
}
}
csharp复制app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 429)
{
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(new
{
error = "请求过于频繁",
retryAfter = context.Response.Headers["Retry-After"]
}));
}
});
这些经验表明,限流策略需要根据实际业务场景不断调整优化,而不是简单配置后就一劳永逸。