1、速率限制器算法
在ASP.NET Core 中实现速率限制时,通常会使用特定的算法来决定何时以及如何限制请求。这些算法可以基于不同的标准,如IP地址、客户端ID或用户账号。
Microsoft.AspNetCore.RateLimiting 中间件用于限制速率,支持四种扩展方法。
1)固定窗口
AddFixedWindowLimiter 方法使用固定的时间窗口来限制请求。 当时间窗口过期时,会启动一个新的时间窗口,并重置请求限制
2)滑动窗口
与固定窗口限制器类似,但为每个窗口添加了段。 窗口在每个段间隔滑动一段。 段间隔的计算方式是:(窗口时间)/(每个窗口的段数)。
将窗口的请求数限制为 permitLimit 个请求。每个时间窗口划分为一个窗口 n 个段。从倒退一个窗口的过期时间段(当前段之前的 n 个段)获取的请求会添加到当前的段。 我们将倒退一个窗口最近过期时间段称为“过期的段”。
3)令牌桶
令牌桶限制器与滑动窗口限制器类似,但它不会结存从过期段获取的请求数,而是在每个补充期间添加固定数量的令牌。 每个段添加的令牌数不能使可用令牌数超过令牌桶限制。
4)并发
并发限制器会限制并发请求数。 每添加一个请求,在并发限制中减去 1。 一个请求完成时,在限制中增加 1。 其他请求限制器限制的是指定时间段的请求总数,而与它们不同,并发限制器仅限制并发请求数,不对一段时间内的请求数设置上限。
2、Microsoft.AspNetCore.RateLimiting
Microsoft.AspNetCore.RateLimiting 是微软官方提供的速率限制中间件,Microsoft.AspNetCore.RateLimiting 是 ASP.NET Core 中提供的内置速率限制包,它可以帮助控制应用程序中资源的访问频率,防止出现DoS攻击或过度消耗资源。它提供了基本的速率限制功能,适用于简单的场景。
1)AddFixedWindowLimiter
using Microsoft.AspNetCore.RateLimiting; using System.Threading.RateLimiting; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRateLimiter(_ => _ .AddFixedWindowLimiter(policyName: "fixed", options => { options.PermitLimit = 4; options.Window = TimeSpan.FromSeconds(12); options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = 2; })); var app = builder.Build(); app.UseRateLimiter(); static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000"); app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}")) .RequireRateLimiting("fixed"); app.Run();
2)AddSlidingWindowLimiter
using Microsoft.AspNetCore.RateLimiting; using System.Threading.RateLimiting; using WebRateLimitAuth.Models; var builder = WebApplication.CreateBuilder(args); var myOptions = new MyRateLimitOptions(); builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions); var slidingPolicy = "sliding"; builder.Services.AddRateLimiter(_ => _ .AddSlidingWindowLimiter(policyName: slidingPolicy, options => { options.PermitLimit = myOptions.PermitLimit; options.Window = TimeSpan.FromSeconds(myOptions.Window); options.SegmentsPerWindow = myOptions.SegmentsPerWindow; options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = myOptions.QueueLimit; })); var app = builder.Build(); app.UseRateLimiter(); static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000"); app.MapGet("/", () => Results.Ok($"Sliding Window Limiter {GetTicks()}")) .RequireRateLimiting(slidingPolicy); app.Run();
3)AddTokenBucketLimiter
using Microsoft.AspNetCore.RateLimiting; using System.Threading.RateLimiting; using WebRateLimitAuth.Models; var builder = WebApplication.CreateBuilder(args); var tokenPolicy = "token"; var myOptions = new MyRateLimitOptions(); builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions); builder.Services.AddRateLimiter(_ => _ .AddTokenBucketLimiter(policyName: tokenPolicy, options => { options.TokenLimit = myOptions.TokenLimit; options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = myOptions.QueueLimit; options.ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod); options.TokensPerPeriod = myOptions.TokensPerPeriod; options.AutoReplenishment = myOptions.AutoReplenishment; })); var app = builder.Build(); app.UseRateLimiter(); static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000"); app.MapGet("/", () => Results.Ok($"Token Limiter {GetTicks()}")) .RequireRateLimiting(tokenPolicy); app.Run();
4)AddConcurrencyLimiter
using Microsoft.AspNetCore.RateLimiting; using System.Threading.RateLimiting; using WebRateLimitAuth.Models; var builder = WebApplication.CreateBuilder(args); var concurrencyPolicy = "Concurrency"; var myOptions = new MyRateLimitOptions(); builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions); builder.Services.AddRateLimiter(_ => _ .AddConcurrencyLimiter(policyName: concurrencyPolicy, options => { options.PermitLimit = myOptions.PermitLimit; options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = myOptions.QueueLimit; })); var app = builder.Build(); app.UseRateLimiter(); static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000"); app.MapGet("/", async () => { await Task.Delay(500); return Results.Ok($"Concurrency Limiter {GetTicks()}"); }).RequireRateLimiting(concurrencyPolicy); app.Run();
5)创建链式限制器
通过 CreateChained API 可传入多个 PartitionedRateLimiter
,这些限制器组合成一个 PartitionedRateLimiter
。 组合的限制器按顺序运行所有输入限制器。
using System.Globalization; using System.Threading.RateLimiting; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRateLimiter(_ => { _.OnRejected = (context, _) => { if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)) { context.HttpContext.Response.Headers.RetryAfter = ((int) retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo); } context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; context.HttpContext.Response.WriteAsync("Too many requests. Please try again later."); return new ValueTask(); }; _.GlobalLimiter = PartitionedRateLimiter.CreateChained( PartitionedRateLimiter.Create<HttpContext, string>(httpContext => { var userAgent = httpContext.Request.Headers.UserAgent.ToString(); return RateLimitPartition.GetFixedWindowLimiter (userAgent, _ => new FixedWindowRateLimiterOptions { AutoReplenishment = true, PermitLimit = 4, Window = TimeSpan.FromSeconds(2) }); }), PartitionedRateLimiter.Create<HttpContext, string>(httpContext => { var userAgent = httpContext.Request.Headers.UserAgent.ToString(); return RateLimitPartition.GetFixedWindowLimiter (userAgent, _ => new FixedWindowRateLimiterOptions { AutoReplenishment = true, PermitLimit = 20, Window = TimeSpan.FromSeconds(30) }); })); }); var app = builder.Build(); app.UseRateLimiter(); static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000"); app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}")); app.Run();
6)EnableRateLimiting 和 DisableRateLimiting 属性
[EnableRateLimiting]
和 [DisableRateLimiting]
特性可应用于控制器、操作方法或 Razor Page。 对于 Razor Pages,这些特性必须应用于 Razor Page,而不是页面处理程序。例如,[EnableRateLimiting] 不能应用于 OnGet、OnPost 或任何其他页面处理程序。
using Microsoft.AspNetCore.RateLimiting; using System.Threading.RateLimiting; using WebRateLimitAuth.Models; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); builder.Services.AddControllersWithViews(); builder.Services.Configure<MyRateLimitOptions>( builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit)); var myOptions = new MyRateLimitOptions(); builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions); var fixedPolicy = "fixed"; builder.Services.AddRateLimiter(_ => _ .AddFixedWindowLimiter(policyName: fixedPolicy, options => { options.PermitLimit = myOptions.PermitLimit; options.Window = TimeSpan.FromSeconds(myOptions.Window); options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = myOptions.QueueLimit; })); var slidingPolicy = "sliding"; builder.Services.AddRateLimiter(_ => _ .AddSlidingWindowLimiter(policyName: slidingPolicy, options => { options.PermitLimit = myOptions.SlidingPermitLimit; options.Window = TimeSpan.FromSeconds(myOptions.Window); options.SegmentsPerWindow = myOptions.SegmentsPerWindow; options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = myOptions.QueueLimit; })); var app = builder.Build(); app.UseRateLimiter(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.MapRazorPages(); app.MapDefaultControllerRoute(); app.Run();
标签配置:
[EnableRateLimiting("fixed")] public class HomeController : Controller { private readonly ILogger<Home2Controller> _logger; public Home2Controller(ILogger<Home2Controller> logger) { _logger = logger; } public ActionResult Index() { return View(); } [EnableRateLimiting("sliding")] public ActionResult Privacy() { return View(); } [DisableRateLimiting] public ActionResult NoLimit() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } }
7)调用 RequireRateLimiting 应用所有控制器了的限制
using Microsoft.AspNetCore.RateLimiting; using System.Threading.RateLimiting; using WebRateLimitAuth.Models; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); builder.Services.AddControllersWithViews(); builder.Services.Configure<MyRateLimitOptions>( builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit)); var myOptions = new MyRateLimitOptions(); builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions); var fixedPolicy = "fixed"; builder.Services.AddRateLimiter(_ => _ .AddFixedWindowLimiter(policyName: fixedPolicy, options => { options.PermitLimit = myOptions.PermitLimit; options.Window = TimeSpan.FromSeconds(myOptions.Window); options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = myOptions.QueueLimit; })); var slidingPolicy = "sliding"; builder.Services.AddRateLimiter(_ => _ .AddSlidingWindowLimiter(policyName: slidingPolicy, options => { options.PermitLimit = myOptions.SlidingPermitLimit; options.Window = TimeSpan.FromSeconds(myOptions.Window); options.SegmentsPerWindow = myOptions.SegmentsPerWindow; options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = myOptions.QueueLimit; })); var app = builder.Build(); app.UseRateLimiter(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.MapRazorPages().RequireRateLimiting(slidingPolicy); app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy); app.Run();
参考文档:https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-8.0
3、AspNetCoreRateLimit
AspNetCoreRateLimit 是一个第三方库,提供了一套丰富的速率限制功能。它支持IP限制和客户端ID限制策略。保护API免受过度使用和潜在的滥用。通过合理配置,可以有效地管理客户端的请求频率,确保服务的稳定性和可用性。可以存储在内存中,也可以配置为使用分布式存储(如Redis)来实现跨多个应用实例的速率限制。
1)安装引用 AspNetCoreRateLimit
在Nuget管理程序中,搜索 "AspNetCoreRateLimit",然后点击安装。
或者使用Package Manager命令安装 PM> Install-Package Serilog
注意:使用Redis,还需要安装AspNetCoreRateLimit.Redis包。
相关文档:VS(Visual Studio)中Nuget的使用
2)基于客户端IP速率限制
Program.cs:
using AspNetCoreRateLimit; using Microsoft.Extensions.Configuration; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddOptions(); // needed to store rate limit counters and ip rules builder.Services.AddMemoryCache(); //load general configuration from appsettings.json builder.Services.Configure(builder.Configuration.GetSection("IpRateLimiting")); //load ip rules from appsettings.json builder.Services.Configure(builder.Configuration.GetSection("IpRateLimitPolicies")); // inject counter and rules stores builder.Services.AddInMemoryRateLimiting(); //若使用redis时使用 //services.AddDistributedRateLimiting(); //services.AddDistributedRateLimiting(); //services.AddRedisRateLimiting(); builder.Services.AddRazorPages(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); //启用客户端IP限制速率 app.UseIpRateLimiting(); app.MapRazorPages(); app.Run();
appsettings.json配置文件:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "IpRateLimiting": { //false,则全局将应用限制,并且仅应用具有作为端点的规则*。例如,如果您设置每秒5次调用的限制,则对任何端点的任何HTTP调用都将计入该限制 //true, 则限制将应用于每个端点,如{HTTP_Verb}{PATH}。例如,如果您为*:/api/values客户端设置每秒5个呼叫的限制, "EnableEndpointRateLimiting": false, //false,拒绝的API调用不会添加到调用次数计数器上;如 客户端每秒发出3个请求并且您设置了每秒一个调用的限制,则每分钟或每天计数器等其他限制将仅记录第一个调用,即成功的API调用。如果您希望被拒绝的API调用计入其他时间的显示(分钟,小时等),则必须设置StackBlockedRequests为true。 "StackBlockedRequests": false, //Kestrel 服务器背后是一个反向代理,如果你的代理服务器使用不同的页眉然后提取客户端IP X-Real-IP使用此选项来设置 "RealIpHeader": "X-Real-IP", //取白名单的客户端ID。如果此标头中存在客户端ID并且与ClientWhitelist中指定的值匹配,则不应用速率限制。 "ClientIdHeader": "X-ClientId", //限制状态码 "HttpStatusCode": 429, ////IP白名单:支持Ip v4和v6 //"IpWhitelist": [ "127.0.0.1", "::1/10", "192.168.0.0/24" ], ////端点白名单:支持各种请求; //"EndpointWhitelist": [ "get:/api/license", "*:/api/status" ], ////客户端白名单 //"ClientWhitelist": [ "dev-id-1", "dev-id-2" ], //通用规则 "GeneralRules": [ { //端点路径 "Endpoint": "*", //时间段,格式:{数字}{单位};可使用单位:s, m, h, d "Period": "1s", //限制 "Limit": 1 }, //15分钟只能调用100次 { "Endpoint": "*", "Period": "15m", "Limit": 100 }, //12H只能调用1000 { "Endpoint": "*", "Period": "12h", "Limit": 1000 }, //7天只能调用10000次 { "Endpoint": "*", "Period": "7d", "Limit": 10000 } ] }, "IpRateLimitPolicies": { //ip规则 "IpRules": [ { //IP "Ip": "127.0.0.1", //规则内容 "Rules": [ //1s请求10次 { "Endpoint": "*", "Period": "1s", "Limit": 10 }, //15分钟请求200次 { "Endpoint": "*", "Period": "15m", "Limit": 200 } ] }, { //ip支持设置多个 "Ip": "192.168.3.22/25", "Rules": [ //1秒请求5次 { "Endpoint": "*", "Period": "1s", "Limit": 5 }, //15分钟请求150次 { "Endpoint": "*", "Period": "15m", "Limit": 150 }, //12小时请求500次 { "Endpoint": "*", "Period": "12h", "Limit": 500 } ] } ] } }
3)基于客户端ID速率限制
Program.cs:
using AspNetCoreRateLimit; using Microsoft.Extensions.Configuration; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddOptions(); // needed to store rate limit counters and ip rules builder.Services.AddMemoryCache(); //load general configuration from appsettings.json services.Configure(Configuration.GetSection("ClientRateLimiting")); //load client rules from appsettings.json services.Configure(Configuration.GetSection("ClientRateLimitPolicies")); // inject counter and rules stores services.AddInMemoryRateLimiting(); //若使用redis时使用 //services.AddDistributedRateLimiting(); //services.AddDistributedRateLimiting(); //services.AddRedisRateLimiting(); // Add framework services. builder.Services.AddRazorPages(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); //启用客户端ID限制速率 app.UseClientRateLimiting(); app.MapRazorPages(); app.Run();
appsettings.json配置文件:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "ClientRateLimitPolicies": { "ClientRules": [ { "ClientId": "client-id-1", "Rules": [ { "Endpoint": "*", "Period": "1s", "Limit": 10 }, { "Endpoint": "*", "Period": "15m", "Limit": 200 } ] }, { "ClientId": "client-id-2", "Rules": [ { "Endpoint": "*", "Period": "1s", "Limit": 5 }, { "Endpoint": "*", "Period": "15m", "Limit": 150 }, { "Endpoint": "*", "Period": "12h", "Limit": 500 } ] } ] } }
参考文档:
https://github.com/stefanprodan/AspNetCoreRateLimit/wiki/IpRateLimitMiddleware#setup
https://github.com/stefanprodan/AspNetCoreRateLimit/wiki/ClientRateLimitMiddleware#setup