ASP.NET Core 3.x concurrency limit

Original: ASP.NET Core 3.x concurrency limit

Foreword

Microsoft.AspNetCore.ConcurrencyLimiter after AspNetCore3.0 increase for incoming requests are queued treatment, avoid running thread pool.
Our daily development may often do to a web server configuration and number of connections, the size of the request queue, then today we look at how to implement a concurrent queue length limit by the amount and form of middleware.

Queue Policy

Add Nuget

Install-Package Microsoft.AspNetCore.ConcurrencyLimiter

 
 
Copy
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(); }); }

By the above simple configuration, we will be able to introduce him to our code, so do the value limits, and the queue length; so the question is, how he achieve it?

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

QueuePolicy uses SemaphoreSlim semaphore design, SemaphoreSlim , Semaphore (Semaphore) support for simultaneous multi-threading into the protected code, object specifies the maximum number of tasks at initialization time, when the thread requests access to a resource, the semaphore is decremented, and when they are released , and increments the count of the semaphore.

 
 
Copy
/// <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 Middleware

 
 
Copy
/// <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); } }

Every time we requested will first call _queuePolicy.TryEnterAsync(), after entering the first turn on a private lock method lock, then went on to determine the total amount requested if ≥ (request queue limit size + the maximum number of concurrent requests) , if the current exceeds the quantity, then I direct throw, give you a 503 status;

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

The question is, I'm not here to say that if you set the size of it, I did not give this request your server questioning pressure, then you give me something about it.
await _serverSemaphore.WaitAsync();Asynchronous waiting to enter the semaphore if no thread is granted signal the amount of access, then enter the code execution protection; otherwise, this thread will wait here until the semaphore is released

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

After a successful return to the side and then processed so middleware, _queuePolicy.OnExit();called by the call _serverSemaphore.Release();release signal lights, and then the total number of requests is decremented

Stack Strategy

Let's look at another way, the stack strategy, how he did it? Take a look. And then add the code on how to use it.

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

Through the above configuration, we can perform for our application out appropriate policy. Let's look at how he achieved it below

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

This can be seen by StackPolicythe class to do the strategy. To take a look at the main method

 
 
Copy
/// <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; }

When we request through the middleware call _queuePolicy.TryEnterAsync(), the first will determine whether we have a number of access requests, if _freeServerSpots> 0, then return true to us directly, let middleware directly to the next step, if we set the current queue = the queue size, then we need to cancel a previous request; behind every request to cancel reservations are canceled before;

 
 
Copy
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(); } }

When we requested to call _queuePolicy.OnExit();the stack, and then descending request length

 
 
Copy
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--; } }

to sum up

Based on the characteristics of the stack structure, in practical applications, usually only performs two operations on the stack:

  • Add elements to the stack, this process is called "push" (or push onto the stack);
  • Extracting the specified element from the stack, this process is called "popped" (or popping);

Queue storage structure of the following two ways:

  • The queues: queue structure implemented on the basis sequence table;
  • Chain queues: queue structure implemented on the basis of the list;

Guess you like

Origin www.cnblogs.com/lonelyxmas/p/11960758.html