前言

Microsoft.AspNetCore.ConcurrencyLimiter AspNetCore3.0后增加的,用于传入的请求进行排队处理,避免线程池的不足.

我们日常开发中可能常做的给某web服务器配置连接数以及,请求队列大小,那么今天我们看看如何在通过中间件形式实现一个并发量以及队列长度限制.

Queue策略

添加Nuget

Install-Package Microsoft.AspNetCore.ConcurrencyLimiter

        public void ConfigureServices(IServiceCollection services)
{
services.AddQueuePolicy(options =>
{
//最大并发请求数
options.MaxConcurrentRequests = 2;
//请求队列长度限制
options.RequestQueueLimit = 1;
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//添加并发限制中间件
app.UseConcurrencyLimiter();
app.Run(async context =>
{
Task.Delay(100).Wait(); // 100ms sync-over-async await context.Response.WriteAsync("Hello World!");
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

通过上面简单的配置,我们就可以将他引入到我们的代码中,从而做并发量限制,以及队列的长度;那么问题来了,他是怎么实现的呢?

 public static IServiceCollection AddQueuePolicy(this IServiceCollection services, Action<QueuePolicyOptions> configure)
{
services.Configure(configure);
services.AddSingleton<IQueuePolicy, QueuePolicy>();
return services;
}

QueuePolicy采用的是SemaphoreSlim信号量设计,SemaphoreSlim、Semaphore(信号量)支持并发多线程进入被保护代码,对象在初始化时会指定 最大任务数量,当线程请求访问资源,信号量递减,而当他们释放时,信号量计数又递增。

      /// <summary>
/// 构造方法(初始化Queue策略)
/// </summary>
/// <param name="options"></param>
public QueuePolicy(IOptions<QueuePolicyOptions> options)
{
_maxConcurrentRequests = options.Value.MaxConcurrentRequests;
if (_maxConcurrentRequests <= 0)
{
throw new ArgumentException(nameof(_maxConcurrentRequests), "MaxConcurrentRequests must be a positive integer.");
} _requestQueueLimit = options.Value.RequestQueueLimit;
if (_requestQueueLimit < 0)
{
throw new ArgumentException(nameof(_requestQueueLimit), "The RequestQueueLimit cannot be a negative number.");
}
//使用SemaphoreSlim来限制任务最大个数
_serverSemaphore = new SemaphoreSlim(_maxConcurrentRequests);
}

ConcurrencyLimiterMiddleware中间件

        /// <summary>
/// Invokes the logic of the middleware.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <returns>A <see cref="Task"/> that completes when the request leaves.</returns>
public async Task Invoke(HttpContext context)
{
var waitInQueueTask = _queuePolicy.TryEnterAsync(); // Make sure we only ever call GetResult once on the TryEnterAsync ValueTask b/c it resets.
bool result; if (waitInQueueTask.IsCompleted)
{
ConcurrencyLimiterEventSource.Log.QueueSkipped();
result = waitInQueueTask.Result;
}
else
{
using (ConcurrencyLimiterEventSource.Log.QueueTimer())
{
result = await waitInQueueTask;
}
} if (result)
{
try
{
await _next(context);
}
finally
{
_queuePolicy.OnExit();
}
}
else
{
ConcurrencyLimiterEventSource.Log.RequestRejected();
ConcurrencyLimiterLog.RequestRejectedQueueFull(_logger);
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
await _onRejected(context);
}
}

每次当我们请求的时候首先会调用_queuePolicy.TryEnterAsync(),进入该方法后先开启一个私有lock锁,再接着判断总请求量是否≥(请求队列限制的大小+最大并发请求数),如果当前数量超出了,那么我直接抛出,送你个503状态;

  if (result)
{
try
{
await _next(context);
}
finally
{
_queuePolicy.OnExit();
}
}
else
{
ConcurrencyLimiterEventSource.Log.RequestRejected();
ConcurrencyLimiterLog.RequestRejectedQueueFull(_logger);
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
await _onRejected(context);
}

问题来了,我这边如果说还没到你设置的大小呢,我这个请求没有给你服务器造不成压力,那么你给我处理一下吧.

await _serverSemaphore.WaitAsync();异步等待进入信号量,如果没有线程被授予对信号量的访问权限,则进入执行保护代码;否则此线程将在此处等待,直到信号量被释放为止

 lock (_totalRequestsLock)
{
if (TotalRequests >= _requestQueueLimit + _maxConcurrentRequests)
{
return false;
}
TotalRequests++;
}
//异步等待进入信号量,如果没有线程被授予对信号量的访问权限,则进入执行保护代码;否则此线程将在此处等待,直到信号量被释放为止
await _serverSemaphore.WaitAsync();
return true;
}

返回成功后那么中间件这边再进行处理, _queuePolicy.OnExit();通过该调用进行调用 _serverSemaphore.Release();释放信号灯,再对总请求数递减

Stack策略

再来看看另一种方法,栈策略,他是怎么做的呢?一起来看看.再附加上如何使用的代码.

     public void ConfigureServices(IServiceCollection services)
{
services.AddStackPolicy(options =>
{
//最大并发请求数
options.MaxConcurrentRequests = 2;
//请求队列长度限制
options.RequestQueueLimit = 1;
});
services.AddControllers();
}

通过上面的配置,我们便可以对我们的应用程序执行出相应的策略.下面再来看看他是怎么实现的呢

  public static IServiceCollection AddStackPolicy(this IServiceCollection services, Action<QueuePolicyOptions> configure)
{
services.Configure(configure);
services.AddSingleton<IQueuePolicy, StackPolicy>();
return services;
}

可以看到这次是通过StackPolicy类做的策略.来一起来看看主要的方法

        /// <summary>
/// 构造方法(初始化参数)
/// </summary>
/// <param name="options"></param>
public StackPolicy(IOptions<QueuePolicyOptions> options)
{
//栈分配
_buffer = new List<ResettableBooleanCompletionSource>();
//队列大小
_maxQueueCapacity = options.Value.RequestQueueLimit;
//最大并发请求数
_maxConcurrentRequests = options.Value.MaxConcurrentRequests;
//剩余可用空间
_freeServerSpots = options.Value.MaxConcurrentRequests;
}

当我们通过中间件请求调用, _queuePolicy.TryEnterAsync()时,首先会判断我们是否还有访问请求次数,如果_freeServerSpots>0,那么则直接给我们返回true,让中间件直接去执行下一步,如果当前队列=我们设置的队列大小的话,那我们需要取消先前请求;每次取消都是先取消之前的保留后面的请求;

    public ValueTask<bool> TryEnterAsync()
{
lock (_bufferLock)
{
if (_freeServerSpots > 0)
{
_freeServerSpots--;
return _trueTask;
}
// 如果队列满了,取消先前的请求
if (_queueLength == _maxQueueCapacity)
{
_hasReachedCapacity = true;
_buffer[_head].Complete(false);
_queueLength--;
}
var tcs = _cachedResettableTCS ??= new ResettableBooleanCompletionSource(this);
_cachedResettableTCS = null;
if (_hasReachedCapacity || _queueLength < _buffer.Count)
{
_buffer[_head] = tcs;
}
else
{
_buffer.Add(tcs);
}
_queueLength++;
// increment _head for next time
_head++;
if (_head == _maxQueueCapacity)
{
_head = 0;
}
return tcs.GetValueTask();
}
}

当我们请求后调用 _queuePolicy.OnExit();出栈,再将请求长度递减

    public void OnExit()
{
lock (_bufferLock)
{
if (_queueLength == 0)
{
_freeServerSpots++; if (_freeServerSpots > _maxConcurrentRequests)
{
_freeServerSpots--;
throw new InvalidOperationException("OnExit must only be called once per successful call to TryEnterAsync");
} return;
} // step backwards and launch a new task
if (_head == 0)
{
_head = _maxQueueCapacity - 1;
}
else
{
_head--;
}
//退出,出栈
_buffer[_head].Complete(true);
_queueLength--;
}
}

总结

基于栈结构的特点,在实际应用中,通常只会对栈执行以下两种操作:

  • 向栈中添加元素,此过程被称为"进栈"(入栈或压栈);
  • 从栈中提取出指定元素,此过程被称为"出栈"(或弹栈);

队列存储结构的实现有以下两种方式:

  • 顺序队列:在顺序表的基础上实现的队列结构;
  • 链队列:在链表的基础上实现的队列结构;

ASP.NET Core 3.x 并发限制的更多相关文章

  1. asp.net core 系列之并发冲突

    本文介绍如何处理多个用户并发更新同一实体(同时)时出现的冲突 . 主要是两种:一种,检查属性并发冲突,使用 [ConcurrencyCheck] ;另一种,检测行的并发冲突,使用 rowversion ...

  2. 用ASP.NET Core 2.1 建立规范的 REST API -- 缓存和并发

    本文所需的一些预备知识可以看这里: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314 ...

  3. 学习ASP.NET Core Razor 编程系列十八——并发解决方案

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  4. 【转】用ASP.NET Core 2.1 建立规范的 REST API -- 缓存和并发

    原文链接:https://www.cnblogs.com/cgzl/p/9165388.html 本文所需的一些预备知识可以看这里: http://www.cnblogs.com/cgzl/p/901 ...

  5. C# .net 提升 asp.net mvc, asp.net core mvc 并发量

    1.提升System.Net.ServicePointManager.DefaultConnectionLimit 2.提升最小工作线程数 ------ DefaultConnectionLimit在 ...

  6. ASP.NET Core教程【三】实体字段属性、链接标签、并发数据异常、文件上传及读取

    前文索引:ASP.NET Core教程[二]从保存数据看Razor Page的特有属性与服务端验证ASP.NET Core教程[一]关于Razor Page的知识 实体字段属性 再来看看我们的实体类 ...

  7. asp.net core系列 24 EF模型配置(主键,生成值,最大长度,并发标记)

    一.主键 键用作每个实体实例的主要唯一标识符. 使用关系数据库时,这会映射到主键的概念. 还可以配置不是主键的唯一标识符.按照约定,名为 Id 或 <type name>Id 的属性会配置 ...

  8. Ubuntu-18.04 下使用Nginx搭建高可用,高并发的asp.net core集群

    一.实现前的准备 以下是实现简单负载均衡的思路,图中的服务器均为虚拟机 三台Linux服务器,一台用作Nginx负载均衡(192.168.254.139),另外两台用作Asp.Net Core应用程序 ...

  9. Jexus 5.8.2 正式发布为Asp.Net Core进入生产环境提供平台支持

    Jexus 是一款运行于 Linux 平台,以支持  ASP.NET.PHP 为特色的集高安全性和高性能为一体的 WEB 服务器和反向代理服务器.最新版 5.8.2 已经发布,有如下更新: 1,现在大 ...

随机推荐

  1. 设计模式----创建型模式之工厂模式(FactoryPattern)

    工厂模式主要分为三种简单工厂模式.工厂方法模式.抽象工厂模式三种.顾名思义,工厂主要是生产产品,作为顾客或者商家,我们不考虑工厂内部是怎么一个流程,我们要的是最终产品.将该种思路放在我们面向对象开发实 ...

  2. 「面试高频」二叉搜索树&双指针&贪心 算法题指北

    本文将覆盖 「字符串处理」 + 「动态规划」 方面的面试算法题,文中我将给出: 面试中的题目 解题的思路 特定问题的技巧和注意事项 考察的知识点及其概念 详细的代码和解析 开始之前,我们先看下会有哪些 ...

  3. lnmp环境搭设

    安装nginx============================ 1添加nginx的rpm信息 rpm -Uvh http://nginx.org/packages/centos/7/noarc ...

  4. Spring5源码解析6-ConfigurationClassParser 解析配置类

    ConfigurationClassParser 在ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中创建了Configur ...

  5. Python3数据分析与挖掘建模实战 ☝☝☝

    Python3数据分析与挖掘建模实战 Python数据分析简介 Python入门 运行:cmd下"python hello.py" 基本命令: 第三方库 安装 Windows中 p ...

  6. 如何让excel文件读取变得更简单

    今天给大家安利一款excel文件导入神器,easyexcel,官方地址:(https://github.com/alibaba/easyexcel). 在官网文档中有介绍了其性能. 从上面的性能测试可 ...

  7. Cocos2d-x 学习笔记(11.5) SkewTo SkewBy

    1. SkewTo SkewBy node朝X和Y方向的歪斜.SkewTo是SkewBy的父类. 1.1 成员变量 create方法 // 两者成员变量一致 float _skewX; float _ ...

  8. java集合之HashSet哈希SET基础

    hashSet实现set接口.serializable.cloneable接口.不允许有重复值,但可以null,不保证与插入顺序一致. HashSet迭代的时间与HashSet实例的大小(元素的数量) ...

  9. POJ 1276 Cash Machine(多重背包的二进制优化)

    题目网址:http://poj.org/problem?id=1276 思路: 很明显是多重背包,把总金额看作是背包的容量. 刚开始是想把单个金额当做一个物品,用三层循环来 转换成01背包来做.T了… ...

  10. MS12-042 用户态调度机制特权提升漏洞

    漏洞编号:MS12-042 披露日期: 2012/6/12 受影响的操作系统:Windows 2000;XP;Server 2003;windows 7;Server 2008; 测试系统:windo ...