序文
Microsoft.AspNetCore.ConcurrencyLimiter着信要求のためのAspNetCore3.0の増加後は避けるが、スレッドプールを実行し、治療をキューに入れられている。
私たちの日々の開発は頻繁にして、Webサーバの設定と接続の数、要求キューのサイズに行うことができます今日はミドルウェアの量や形状により同時キューの長さの制限を実装する方法を見て。
キューポリシー
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、同時保護されたコードにマルチスレッドのためのセマフォ(セマフォ)のサポートは、オブジェクトがスレッドがリソースへのアクセスを要求した場合、初期化時にタスクの最大数を指定し、セマフォがデクリメントされ、そしてそれらがリリースされたとき、およびセマフォのカウントをインクリメント。
/// <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()
、プライベートロック法ロックの最初のターンを入力した後、その後、決定するために行ってきました≥(要求キュー制限のサイズ+同時要求の最大数)場合は、要求の合計額を、現在の数量を超える場合は、その後、 I直接投げる、あなたに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();
解除信号が点灯し、その後、要求の合計数が減算されます
スタック戦略
彼はそれをやったか、別の方法で、スタック戦略で見てみましょうか?見てみましょう。そして、それを使用する方法のコードを追加します。
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
戦略を実行するためのクラスです。mainメソッドを見てみましょうするには
/// <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--;
}
}
概要
積層構造の特性に基づいて、実際の応用において、通常は、スタック上の2つの操作を実行します。
- スタックに要素を追加し、このプロセスは、「プッシュ」(またはスタック上にプッシュ)と呼ばれます。
- スタックから指定された要素を抽出し、このプロセスは、「ポップ」(またはポップ)と呼ばれます。
次の2つの方法のキュー記憶構造:
- キュー:キュー構造は、基本シーケンス表に実装しました。
- チェーンキュー:キュー構造がリストに基づいて実施し、