高端网站制作公,广告制作,wordpress文章标题过长,重庆建设工程交易中心官网前言限流是应对流量暴增或某些用户恶意攻击等场景的重要手段之一#xff0c;然而微软官方从未支持这一重要特性#xff0c;AspNetCoreRateLimit这一第三方库限流库一般作为首选使用#xff0c;然而其配置参数过于繁多#xff0c;对使用者造成较大的学习成本。令人高兴的是然而微软官方从未支持这一重要特性AspNetCoreRateLimit这一第三方库限流库一般作为首选使用然而其配置参数过于繁多对使用者造成较大的学习成本。令人高兴的是在刚刚发布的.NET 7 Preview 4中开始支持限流中间件。UseRateLimiter尝鲜安装.NET 7.0 SDKv7.0.100-preview.4通过nuget包安装Microsoft.AspNetCore.RateLimiting创建.Net7网站应用注册中间件全局限流并发1个app.UseRateLimiter(new RateLimiterOptions
{Limiter PartitionedRateLimiter.CreateHttpContext, string(resource {return RateLimitPartition.CreateConcurrencyLimiter(MyLimiter,_ new ConcurrencyLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1));})
});根据不同资源不同限制并发数/api前缀的资源租约数2等待队列长度为2其他默认租约数1队列长度1。app.UseRateLimiter(new RateLimiterOptions()
{// 触发限流的响应码DefaultRejectionStatusCode 500,OnRejected async (ctx, rateLimitLease) {// 触发限流回调处理},Limiter PartitionedRateLimiter.CreateHttpContext, string(resource {if (resource.Request.Path.StartsWithSegments(/api)){return RateLimitPartition.CreateConcurrencyLimiter(WebApiLimiter,_ new ConcurrencyLimiterOptions(2, QueueProcessingOrder.NewestFirst, 2));}else{return RateLimitPartition.CreateConcurrencyLimiter(DefaultLimiter,_ new ConcurrencyLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1));}})
});本地测试新建一个webapi项目并注册限流中间件如下using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;var builder WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();var app builder.Build();// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{app.UseSwagger();app.UseSwaggerUI();
}app.UseRateLimiter(new RateLimiterOptions
{DefaultRejectionStatusCode 500,OnRejected async (ctx, lease) {await Task.FromResult(ctx.Response.WriteAsync(ConcurrencyLimiter));},Limiter PartitionedRateLimiter.CreateHttpContext, string(resource {return RateLimitPartition.CreateConcurrencyLimiter(MyLimiter,_ new ConcurrencyLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1));})
});app.UseHttpsRedirection();app.UseAuthorization();app.MapControllers();app.Run();启动项目使用jmeter测试100并发,请求接口/WeatherForecast所有请求处理成功失败0这个结果是不是有点失望其实RateLimitPartition.CreateConcurrencyLimiter创建的限流器是ConcurrencyLimiter,后续可以实现各种策略的限流器进行替换之。看了ConcurrencyLimiter的实现其实就是令牌桶的限流思想上面配置的new ConcurrencyLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1)),第一个1代表令牌的个数第二个1代表可以当桶里的令牌为空时进入等待队列而不是直接失败当前面的请求结束后会归还令牌此时等待的请求就可以拿到令牌了QueueProcessingOrder.NewestFirst代表最新的请求优先获取令牌也就是获取令牌时非公平的还有另一个枚举值QueueProcessingOrder.OldestFirst老的优先获取令牌是公平的。只要我们获取到令牌的人干活速度快虽然我们令牌只有1并发就很高。3. 测试触发失败场景 只需要让我们拿到令牌的人持有时间长点就能轻易的触发。调整jmater并发数为10相应内容也是我们设置的内容。ConcurrencyLimiter源码令牌桶限流思想获取令牌protected override RateLimitLease AcquireCore(int permitCount){// These amounts of resources can never be acquiredif (permitCount _options.PermitLimit){throw new ArgumentOutOfRangeException(nameof(permitCount), permitCount, SR.Format(SR.PermitLimitExceeded, permitCount, _options.PermitLimit));}ThrowIfDisposed();// Return SuccessfulLease or FailedLease to indicate limiter stateif (permitCount 0){return _permitCount 0 ? SuccessfulLease : FailedLease;}// Perf: Check SemaphoreSlim implementation instead of lockingif (_permitCount permitCount){lock (Lock){if (TryLeaseUnsynchronized(permitCount, out RateLimitLease? lease)){return lease;}}}return FailedLease;}尝试获取令牌核心逻辑private bool TryLeaseUnsynchronized(int permitCount, [NotNullWhen(true)] out RateLimitLease? lease){ThrowIfDisposed();// if permitCount is 0 we want to queue it if there are no available permitsif (_permitCount permitCount _permitCount ! 0){if (permitCount 0){// Edge case where the check before the lock showed 0 available permits but when we got the lock some permits were now availablelease SuccessfulLease;return true;}// a. if there are no items queued we can lease// b. if there are items queued but the processing order is newest first, then we can lease the incoming request since it is the newestif (_queueCount 0 || (_queueCount 0 _options.QueueProcessingOrder QueueProcessingOrder.NewestFirst)){_idleSince null;_permitCount - permitCount;Debug.Assert(_permitCount 0);lease new ConcurrencyLease(true, this, permitCount);return true;}}lease null;return false;}令牌获取失败后进入等待队列protected override ValueTaskRateLimitLease WaitAsyncCore(int permitCount, CancellationToken cancellationToken default){// These amounts of resources can never be acquiredif (permitCount _options.PermitLimit){throw new ArgumentOutOfRangeException(nameof(permitCount), permitCount, SR.Format(SR.PermitLimitExceeded, permitCount, _options.PermitLimit));}// Return SuccessfulLease if requestedCount is 0 and resources are availableif (permitCount 0 _permitCount 0 !_disposed){return new ValueTaskRateLimitLease(SuccessfulLease);}// Perf: Check SemaphoreSlim implementation instead of lockinglock (Lock){if (TryLeaseUnsynchronized(permitCount, out RateLimitLease? lease)){return new ValueTaskRateLimitLease(lease);}// Avoid integer overflow by using subtraction instead of additionDebug.Assert(_options.QueueLimit _queueCount);if (_options.QueueLimit - _queueCount permitCount){if (_options.QueueProcessingOrder QueueProcessingOrder.NewestFirst permitCount _options.QueueLimit){// remove oldest items from queue until there is space for the newest requestdo{RequestRegistration oldestRequest _queue.DequeueHead();_queueCount - oldestRequest.Count;Debug.Assert(_queueCount 0);if (!oldestRequest.Tcs.TrySetResult(FailedLease)){// Updating queue count is handled by the cancellation code_queueCount oldestRequest.Count;}}while (_options.QueueLimit - _queueCount permitCount);}else{// Dont queue if queue limit reached and QueueProcessingOrder is OldestFirstreturn new ValueTaskRateLimitLease(QueueLimitLease);}}CancelQueueState tcs new CancelQueueState(permitCount, this, cancellationToken);CancellationTokenRegistration ctr default;if (cancellationToken.CanBeCanceled){ctr cancellationToken.Register(static obj {((CancelQueueState)obj!).TrySetCanceled();}, tcs);}RequestRegistration request new RequestRegistration(permitCount, tcs, ctr);_queue.EnqueueTail(request);_queueCount permitCount;Debug.Assert(_queueCount _options.QueueLimit);return new ValueTaskRateLimitLease(request.Tcs.Task);}}归还令牌private void Release(int releaseCount){lock (Lock){if (_disposed){return;}_permitCount releaseCount;Debug.Assert(_permitCount _options.PermitLimit);while (_queue.Count 0){RequestRegistration nextPendingRequest _options.QueueProcessingOrder QueueProcessingOrder.OldestFirst? _queue.PeekHead(): _queue.PeekTail();if (_permitCount nextPendingRequest.Count){nextPendingRequest _options.QueueProcessingOrder QueueProcessingOrder.OldestFirst? _queue.DequeueHead(): _queue.DequeueTail();_permitCount - nextPendingRequest.Count;_queueCount - nextPendingRequest.Count;Debug.Assert(_permitCount 0);ConcurrencyLease lease nextPendingRequest.Count 0 ? SuccessfulLease : new ConcurrencyLease(true, this, nextPendingRequest.Count);// Check if request was canceledif (!nextPendingRequest.Tcs.TrySetResult(lease)){// Queued item was canceled so add count back_permitCount nextPendingRequest.Count;// Updating queue count is handled by the cancellation code_queueCount nextPendingRequest.Count;}nextPendingRequest.CancellationTokenRegistration.Dispose();Debug.Assert(_queueCount 0);}else{break;}}if (_permitCount _options.PermitLimit){Debug.Assert(_idleSince is null);Debug.Assert(_queueCount 0);_idleSince Stopwatch.GetTimestamp();}}}总结虽然这次官方对限流进行了支持但貌似还不能支持对ip或client级别的限制支持对于更高级的限流策略仍需要借助第三方库或自己实现期待后续越来越完善。