Polly费率限制过早

发布于 2025-02-13 18:26:02 字数 2625 浏览 1 评论 0 原文

我正在努力围绕Polly Ration-Limit政策。

public class RateLimiter
{
    private readonly AsyncRateLimitPolicy _throttlingPolicy;
    private readonly Action<string> _rateLimitedAction;

    public RateLimiter(int numberOfExecutions, TimeSpan perTimeSpan, Action<string> rateLimitedAction)
    {       
        _throttlingPolicy = Policy.RateLimitAsync(numberOfExecutions, perTimeSpan);
        _rateLimitedAction = rateLimitedAction;
    }

    public async Task<T> Throttle<T>(Func<Task<T>> func)
    {
        var result = await _throttlingPolicy.ExecuteAndCaptureAsync(func);

        if (result.Outcome == OutcomeType.Failure)
        {
            var retryAfter = (result.FinalException as RateLimitRejectedException)?.RetryAfter ?? TimeSpan.FromSeconds(1);
            _rateLimitedAction($"Rate limited. Should retry in {retryAfter}.");
            return default;
        }

        return result.Result;
    }
}

在我的控制台应用程序中,我正在实例化比率,每10秒最多5个呼叫。

var rateLimiter = new RateLimiter(5, TimeSpan.FromSeconds(10), err => Console.WriteLine(err));
var rdm = new Random();

while (true)
{
    var result = await rateLimiter.Throttle(() => Task.FromResult(rdm.Next(1, 10)));

    if (result != default) Console.WriteLine($"Result: {result}");

    await Task.Delay(200);
}

我希望看到5个结果,并且在第六个结果中受到限制。但这就是我所得到的,

Result: 9
Rate limited. Should retry in 00:00:01.7744615.
Rate limited. Should retry in 00:00:01.5119933.
Rate limited. Should retry in 00:00:01.2313921.
Rate limited. Should retry in 00:00:00.9797322.
Rate limited. Should retry in 00:00:00.7309150.
Rate limited. Should retry in 00:00:00.4812646.
Rate limited. Should retry in 00:00:00.2313643.
Result: 7
Rate limited. Should retry in 00:00:01.7982864.
Rate limited. Should retry in 00:00:01.5327321.
Rate limited. Should retry in 00:00:01.2517093.
Rate limited. Should retry in 00:00:00.9843077.
Rate limited. Should retry in 00:00:00.7203371.
Rate limited. Should retry in 00:00:00.4700262.
Rate limited. Should retry in 00:00:00.2205184.

我也尝试使用 executeasync 而不是 executeandcaptureasync ,并且没有更改结果。

public async Task<T> Throttle<T>(Func<Task<T>> func)
{
    try
    {
        var result = await _throttlingPolicy.ExecuteAsync(func);

        return result;
    }
    catch (RateLimitRejectedException ex)
    {
        _rateLimitedAction($"Rate limited. Should retry in {ex.RetryAfter}.");
        return default;
    }
}

这对我没有任何意义。我缺少什么吗?

I'm trying to get my head around Polly rate-limit policy.

public class RateLimiter
{
    private readonly AsyncRateLimitPolicy _throttlingPolicy;
    private readonly Action<string> _rateLimitedAction;

    public RateLimiter(int numberOfExecutions, TimeSpan perTimeSpan, Action<string> rateLimitedAction)
    {       
        _throttlingPolicy = Policy.RateLimitAsync(numberOfExecutions, perTimeSpan);
        _rateLimitedAction = rateLimitedAction;
    }

    public async Task<T> Throttle<T>(Func<Task<T>> func)
    {
        var result = await _throttlingPolicy.ExecuteAndCaptureAsync(func);

        if (result.Outcome == OutcomeType.Failure)
        {
            var retryAfter = (result.FinalException as RateLimitRejectedException)?.RetryAfter ?? TimeSpan.FromSeconds(1);
            _rateLimitedAction(
quot;Rate limited. Should retry in {retryAfter}.");
            return default;
        }

        return result.Result;
    }
}

In my console application, I'm instantiating a RateLimiter with up to 5 calls per 10 seconds.

var rateLimiter = new RateLimiter(5, TimeSpan.FromSeconds(10), err => Console.WriteLine(err));
var rdm = new Random();

while (true)
{
    var result = await rateLimiter.Throttle(() => Task.FromResult(rdm.Next(1, 10)));

    if (result != default) Console.WriteLine(
quot;Result: {result}");

    await Task.Delay(200);
}

I would expect to see 5 results, and be rate limited on the 6th one. But this is what I get

Result: 9
Rate limited. Should retry in 00:00:01.7744615.
Rate limited. Should retry in 00:00:01.5119933.
Rate limited. Should retry in 00:00:01.2313921.
Rate limited. Should retry in 00:00:00.9797322.
Rate limited. Should retry in 00:00:00.7309150.
Rate limited. Should retry in 00:00:00.4812646.
Rate limited. Should retry in 00:00:00.2313643.
Result: 7
Rate limited. Should retry in 00:00:01.7982864.
Rate limited. Should retry in 00:00:01.5327321.
Rate limited. Should retry in 00:00:01.2517093.
Rate limited. Should retry in 00:00:00.9843077.
Rate limited. Should retry in 00:00:00.7203371.
Rate limited. Should retry in 00:00:00.4700262.
Rate limited. Should retry in 00:00:00.2205184.

I've also tried to use ExecuteAsync instead of ExecuteAndCaptureAsync and it didn't change the results.

public async Task<T> Throttle<T>(Func<Task<T>> func)
{
    try
    {
        var result = await _throttlingPolicy.ExecuteAsync(func);

        return result;
    }
    catch (RateLimitRejectedException ex)
    {
        _rateLimitedAction(
quot;Rate limited. Should retry in {ex.RetryAfter}.");
        return default;
    }
}

This doesn't make any sense to me. Is there something I'm missing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

孤蝉 2025-02-20 18:26:02

速率限制器的工作方式与您预期的方式不同。

假设我有500个请求,我想将其限制为每分钟50。

期望:在前50个执行后,如果限制限制者的执行时间不到一分钟,则将启动。

这种直观的方法没有考虑到传入负载的平等分布。这可能会引起以下可观察的行为:

  • 让我们假设前50个执行时间为30秒
  • ,然后您必须再等待30秒才能执行第51个请求

polly的费率限制器使用漏水库算法

这可以通过以下方式工作:

  • 水桶具有
  • 固定
  • 容量滴水将水桶留在给定频率上,
  • 接收新的水滴
  • 如果输入频率大于外向,则水桶

  • 顶部
  • 可以 已满,然后候选者抛出异常

上面描述中最重要的信息是:漏水的桶算法使用恒定速率空为空水桶。


更新14/11/22

让我自己纠正。 Polly的费率限制器使用令牌 bucket而不是泄漏桶。还有其他算法,例如固定窗口计数器滑动窗口日志滑动窗口计数器。您可以阅读有关替代方案在这里内部系统设计访谈第1卷第4章的第4章

因此,让我们谈论令牌桶algorithm:

  • 存储桶具有固定容量
  • 令牌,以固定的周期性速率放入固定周期性的速度
  • 如果存储桶已满,则 添加到它(溢出)
  • 每个请求试图消耗单个令牌
    • 如果至少有一个,则该请求会消耗案件,并且允许请求​​
    • 如果存储桶内至少没有一个令牌,则该请求被删除

“令牌桶”
来源


如果我们仔细审查实现,那么我们可以看到以下内容:

public static IRateLimiter Create(TimeSpan onePer, int bucketCapacity)
    => new LockFreeTokenBucketRateLimiter(onePer, bucketCapacity);

方法>和 bucketcapacity )!

如果您对实际实施感兴趣,则可以找到在这里。 (几乎每行评论)


我想强调更多一件事。速率限制器确实 执行任何重试。如果您想在罚款时间结束后继续执行,那么您必须自己执行。通过编写一些自定义代码或将重试策略与速率限制策略相结合。

The rate limiter works in a bit different way than you might expect.

Let's suppose I have 500 requests and I want to throttle it to 50 per minute.

Expectation: After the first 50 executions the rate limiter kicks in if they were executed less than a minute.

This intuitive approach does not put into account the equal distribution of the incoming load. This might induce the following observable behaviour:

  • Let's suppose the first 50 executions took 30 seconds
  • Then you have to wait another 30 seconds to execute the 51st request

Polly's rate limiter uses the Leaky bucket algorithm

leaky bucket

This works in the following way:

  • The bucket has a fix capacity
  • The bucket has a leak at the bottom
  • Water drops are leaving the bucket on a given frequency
  • The bucket can receive new water drops from top
  • The bucket can overflow if the incoming frequency is greater than the outgoing

So, technically speaking:

  • it is a fixed sized queue
  • the dequeue is called periodically
  • if the queue is full then the enqueue throws an exception

The most important information from the above description is the following: the leaky bucket algorithm uses a constant rate to empty the bucket.


UPDATE 14/11/22

Let me correct myself. Polly's rate limiter is using token bucket not leaky bucket. There are also other algorithms like fixed window counter, sliding window log or sliding window counter. You can read about the alternatives here or inside the System Design Interview Volume 1 book's chapter 4

So, let's talk about the token bucket algorithm:

  • The bucket has a fix capacity
  • Tokens are put into the bucket in a fixed periodic rate
  • If the bucket is full no more token is added to it (overflow)
  • Each request tries to consume a single token
    • If there is at least one then the request consumes it and the request is allowed
    • If there isn't at least one token inside the bucket then the request is dropped

Token bucket
(Source)


If we scrutinise the implementation then we can see the following things:

public static IRateLimiter Create(TimeSpan onePer, int bucketCapacity)
    => new LockFreeTokenBucketRateLimiter(onePer, bucketCapacity);

Please be aware of how the parameters are named (onePer and bucketCapacity)!

If you are interested about the actual implementation then you can find here. (Almost each line is commented)


I want to emphasize one more thing. The rate limiter does not perform any retry. If you want to continue the execution after the penalty time is over then you have to do it yourself. Either by writing some custom code or by combining a retry policy with the rate limiter policy.

听你说爱我 2025-02-20 18:26:02

有一个过载接受第三参数 - maxburst

单个爆发中允许的最大执行次数(例如,如果没有执行一段时间)。

默认值是 1 ,如果将其设置为 numberfexecutions 您会看到第一个执行的所需效果,尽管此后它将在您的情况下变速为相似的模式。观察(我想这是基于限制器如何“释放”资源和 var Oneper = timespan.fromticks(pertimespan.ticks / numberOfexecutions); 计算,但是我没有太深的挖掘,但是基于文档和代码,速率限制似乎正在发生“ 1执行per pertimespan /code>/ numberfexecutions /code>“速率而不是“ numberofexecutions ”中的任何选定的 pertimespan >”):

_throttlingPolicy = Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, numberOfExecutions);

添加定期等待几秒钟将带回不过,“爆发”。

另请参见:

There is an overload accepting third parameter - maxBurst:

The maximum number of executions that will be permitted in a single burst (for example if none have been executed for a while).

The default value is 1, if you will set it to numberOfExecutions you will see the desired effect for the first execution, though after that it will deteriorate to the similar pattern as you observe (I would guess it is based on how the limiter "frees" the resources and var onePer = TimeSpan.FromTicks(perTimeSpan.Ticks / numberOfExecutions); calculation, but I have not dug too deep, but based on the docs and code it seems that rate limiting is happening with "1 execution per perTimeSpan/numberOfExecutions" rate rather than "numberOfExecutions in any selected perTimeSpan"):

_throttlingPolicy = Policy.RateLimitAsync(numberOfExecutions, perTimeSpan, numberOfExecutions);

Adding periodic wait for several seconds brings back the "bursts" though.

Also see:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文