Asp.Net Core 7 preview 4 重磅新特性--限流中间件
前言
限流是应对流量暴增或某些用户恶意攻击等场景的重要手段之一,然而微软官方从未支持这一重要特性,AspNetCoreRateLimit这一第三方库限流库一般作为首选使用,然而其配置参数过于繁多,对使用者造成较大的学习成本。令人高兴的是,在刚刚发布的.NET 7 Preview 4中开始支持限流中间件。
UseRateLimiter尝鲜
- 安装
.NET 7.0 SDK(v7.0.100-preview.4) - 通过nuget包安装
Microsoft.AspNetCore.RateLimiting - 创建.Net7网站应用,注册中间件
全局限流并发1个
app.UseRateLimiter(new RateLimiterOptions
{
Limiter = PartitionedRateLimiter.Create<HttpContext, 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.Create<HttpContext, 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.Create<HttpContext, 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 acquired
if (permitCount > _options.PermitLimit)
{
throw new ArgumentOutOfRangeException(nameof(permitCount), permitCount, SR.Format(SR.PermitLimitExceeded, permitCount, _options.PermitLimit));
}
ThrowIfDisposed();
// Return SuccessfulLease or FailedLease to indicate limiter state
if (permitCount == 0)
{
return _permitCount > 0 ? SuccessfulLease : FailedLease;
}
// Perf: Check SemaphoreSlim implementation instead of locking
if (_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 permits
if (_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 available
lease = 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 newest
if (_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 ValueTask<RateLimitLease> WaitAsyncCore(int permitCount, CancellationToken cancellationToken = default)
{
// These amounts of resources can never be acquired
if (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 available
if (permitCount == 0 && _permitCount > 0 && !_disposed)
{
return new ValueTask<RateLimitLease>(SuccessfulLease);
}
// Perf: Check SemaphoreSlim implementation instead of locking
lock (Lock)
{
if (TryLeaseUnsynchronized(permitCount, out RateLimitLease? lease))
{
return new ValueTask<RateLimitLease>(lease);
}
// Avoid integer overflow by using subtraction instead of addition
Debug.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 request
do
{
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
{
// Don't queue if queue limit reached and QueueProcessingOrder is OldestFirst
return new ValueTask<RateLimitLease>(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 ValueTask<RateLimitLease>(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 canceled
if (!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级别的限制支持,对于更高级的限流策略仍需要借助第三方库或自己实现,期待后续越来越完善。
Asp.Net Core 7 preview 4 重磅新特性--限流中间件的更多相关文章
- ASP.NET Core 2 preview 1中Program.cs,Startup.cs和CreateDefaultBuilder的探索
Exploring Program.cs, Startup.cs and CreateDefaultBuilder in ASP.NET Core 2 preview 1 ASP.NET Core 2 ...
- ASP.NET Core 1.1 静态文件、路由、自定义中间件、身份验证简介
概述 之前写过一篇关于<ASP.NET Core 1.0 静态文件.路由.自定义中间件.身份验证简介>的文章,主要介绍了ASP.NET Core中StaticFile.Middleware ...
- ASP.NET Core 中的SEO优化(2):中间件中渲染Razor视图
前言 上一篇文章<ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存>中介绍了中间件的使用方法.以及使用中间件实现服务端静态化缓存的功能.本系列文章的这些技巧都是我 ...
- JDK新特性关于流操作部分
// array 工具类 可以用来快捷的将数组转化为list List<String> strings = Arrays.asList("zhongguo", &quo ...
- 功能:Java8新特性steam流
Java8新特性steam流 一.包装数据类型 @Test public void main22() { List<Integer> list = new ArrayList<Int ...
- ASP.NET Core 系列视频完结,新项目实战课程发布。
今天把MVC的章节完成了,给大家从头到尾做了一个登录注册的示例,带前后端Model验证,算是完整的示例.同时借助于eShopOnContainers的示例也做了一个DBContextSeed的包装器来 ...
- ASP.NET Core 1.0 静态文件、路由、自定义中间件、身份验证简介
概述 ASP.NET Core 1.0是ASP.NET的一个重要的重新设计. 例如,在ASP.NET Core中,使用Middleware编写请求管道. ASP.NET Core中间件对HttpCon ...
- ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存
分享 最近在公司成功落地了一个用ASP.NET Core 开发前台的CMS项目,虽然对于表层的开发是兼容MVC5的,但是作为爱好者当然要用尽量多的ASP.NET Core新功能了. 背景 在项目开发的 ...
- 使用Enablebuffering多次读取Asp Net Core 3.0 请求体 读取Request.Body流
原文:使用Enablebuffering多次读取Asp Net Core 请求体 使用Enablebuffering多次读取Asp Net Core 请求体 1 .Net Core 2.X时代 使用E ...
随机推荐
- sudo rosdep init 出现 ERROR: cannot download default sources list from:错误解决方法
关于安装ROS时出现的rosdep init错误 sudo rosdep init ERROR: cannot download default sources list from: https:// ...
- PCB常用低速、高速板材参数性能(2)
- 前端每日实战:89# 视频演示如何用 CSS 和 D3 创作旋臂粒子动画
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/xJrOqd 可交互视频 此视频是可 ...
- Codepen 每日精选(2018-4-16)
按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以打开原始页面. 内容切换的交互效果https://codepen.io/jcoulterde... 报价卡片的交互效果ht ...
- java中Object类的finalize的用法
Object类的finalize的用法: 马克-to-win:java当中有个垃圾回收机制,具体说,就是当一些对象被创建使用之后若不再使用的话{比如(i)对象被置成null.(ii)局部对象(无需置成 ...
- Exchange 2013 清空邮箱
在某些应用场景中,需要清空用户邮箱的所有数据.如果使用Outlook web app或者Outlook 的邮件删除方式,对数以千计的邮件来说,实在不是一个好办法.exchange管理员可以使用&quo ...
- 【c++】容器的基本操作
操作\容器 vector list string set stack queue map 插入 push_bcak().insert() push_back() .push_front().inser ...
- iOS开发 将html 富文本文字 转换成oc 的富文本
- (NSMutableAttributedString *)mf_htmlAttribute:(NSString *)htmlString{ htmlString = [NSString strin ...
- 让我们写一个 Win32 文本编辑器吧 - 2. 计划和显示
让我们写一个 Win32 文本编辑器吧 - 2. 计划和显示 如果你已经阅读了简介,相信你已经对我们接下来要做的事情有所了解. 本文,将会把简介中基础程序修改为一个窗体应用程序.并对编辑器接下来的编辑 ...
- Nestjs模块机制的概念和实现原理
1 前言 Nest 提供了模块机制,通过在模块装饰器中定义提供者.导入.导出和提供者构造函数便完成了依赖注入,通过模块树组织整个应用程序的开发.按照框架本身的约定直接撸一个应用程序,是完全没有问题的. ...