简体   繁体   English

扩展 SemaphoreSlim 以区分资源使用强度

[英]Expanding SemaphoreSlim to differentiate on intensity of resource usage

I have about 100 different external endpoints spread out over about 6 or 7 API's I'm calling from my application.我有大约 100 个不同的外部端点分布在我从我的应用程序调用的大约 6 或 7 个 API 上。 All of these 100 (let's call them) resources share a fair-use pool and collectively keep track of when to give me a 429. I'm limiting my calls with a SemaphoreSlim (found a sweet spot on about 50 parallel calls).所有这 100 个(让我们称它们为)资源共享一个合理使用池,并共同跟踪何时给我 429。我使用 SemaphoreSlim 限制我的调用(在大约 50 个并行调用中找到了最佳点)。

BUT

These resources do not determine rate limiting based on the amount of calls, but on the amount of CPU usage.这些资源不根据调用量确定速率限制,而是根据 CPU 使用量确定速率限制。 This means that a call that takes 2ms serverside can be done 1000 times more than a call that takes 2 seconds serverside before triggering 429's.这意味着在触发 429 之前,服务器端花费 2 毫秒的调用比服务器端花费 2 秒的调用多执行 1000 倍。

What I'd like to build is an extension on Semaphore(Slim) that keeps track of avarage roundtrip-times per URL I'm using, store this somewhere and apply limiting based on how "heavy" the calls are that are being queued.我想要构建的是 Semaphore(Slim) 的扩展,它跟踪我正在使用的每个 URL 的平均往返时间,将其存储在某处并根据正在排队的呼叫的“繁重”程度应用限制。 So if I want a max of 10 seconds of calls parallel, I can do up to 5000 2ms calls or up to 5 2s calls.因此,如果我想要最多 10 秒的并行调用,我最多可以执行 5000 个 2ms 调用或最多 5 个 2s 调用。

Ideally I'd use a semaphore that allows for blocking more than just one "count".理想情况下,我会使用允许阻止不止一个“计数”的信号量。 SemaphoreSlim has AwaitAsync and Release. SemaphoreSlim 有 AwaitAsync 和 Release。 I've implemented it like this:我是这样实现的:

protected async Task<TResponse> AwaitSemaphoreAndExecuteRequest<TResponse>(Func<Task<TResponse>> ExecutionDelegate)
{
    try
    {
        await _semaphore.WaitAsync(); // Block 1 of 50 
        return await ExecutionDelegate();
    }
    finally
    {
        _semaphore.Release(); // Release 1 of 50
    }
}

But what I'd like to do is something like this:但我想做的是这样的:

protected async Task<TResponse> AwaitSemaphoreAndExecuteRequest<TResponse>(Func<Task<TResponse>> ExecutionDelegate, int requestWeight)
{
    try
    {
        await _semaphore.WaitAsync(requestWeight); // Block "weight" of 10000
        return await ExecutionDelegate();
    }
    finally
    {
        _semaphore.Release(requestWeight); // Release "weight" of 10000
    }
}

Does anyone know if there are any best-practices for this, maybe a third party library that already allows for this behaviour?有谁知道这是否有任何最佳实践,也许第三方库已经允许这种行为? Idea's for implementing something simple?想法是为了实现一些简单的东西?

I created a library that you may want to consider, it's called SemaphoreSlimThrottling that allows you to initialize a SemaphoreSlim with a negative initialCount .我创建了一个您可能想要考虑的库,它称为SemaphoreSlimThrottling ,它允许您使用负的initialCount初始化SemaphoreSlim

I had created this for a project with a similar problem to yours.我为一个与您有类似问题的项目创建了这个。 I rarely had API issues, but on rare occasions a bunch of calls thrown at the same time caused delays in my API, so what I did was measuring how long the API was taking to answer and if it goes above a certain threshold I would start throttling THEN, and if it went below a certain threshold (a lower value than the previous) then I would stop throttling.我很少遇到 API 问题,但在极少数情况下,同时抛出的一堆电话会导致我的 API 延迟,所以我所做的是测量 API 需要多长时间才能回答,如果它超过某个阈值,我将开始然后节流,如果它低于某个阈值(比之前的值低),那么我将停止节流。

The issue arises when you know you have 25 open calls to the API and you want to start throttling to 20 calls.当您知道对 API 有 25 个打开的呼叫并且您想要开始限制为 20 个呼叫时,就会出现问题。 You can't simply do:你不能简单地做:

var throttle = new SemaphoreSlim(-5, 20);

because SemaphoreSlims cannot take a negative initial value.因为 SemaphoreSlims 不能取负的初始值。

And if you start with an initialCount of 0, you have two problems:如果您以 0 的 initialCount 开始,则会遇到两个问题:

  1. if one of those 25 returns and it releases from the semaphore, another can enter, so you effectively are allowing a 25th one in when you intended to only have 20.如果这 25 个中的一个返回并从信号量中释放,另一个可以进入,所以当您打算只有 20 个时,您实际上允许第 25 个进入。
  2. if 21 return immediately, you can't release 21 if you have a maxCount of 20.如果 21 立即返回,如果 maxCount 为 20,则不能释放 21。

But with SemaphoreSlimThrottling you can simply do:但是使用SemaphoreSlimThrottling你可以简单地做:

var throttleMaxCount = 20;
var throttle = new SemaphoreSlimThrottle(currentCallCount - throttleMaxCount, throttleMaxCount);

which basically translates to:这基本上转化为:

var throttle = new SemaphoreSlimThrottle(-5, 20);

If you instead want to take the path of a fully-fledged throttling system, you may want to look at AspNetCoreRateLimit .如果您想要采用成熟的节流系统,您可能需要查看AspNetCoreRateLimit

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM