简体   繁体   English

wrapping rate limiting API call

[英]Wrapping rate limiting API call

I have access an API call that accepts a maximum rate of calls per second.我可以访问 API 呼叫,该呼叫每秒接受最大呼叫速率。 If the rate is exceeded, an exception is thrown.如果超过速率,则会抛出异常。

I would like to wrap this call into an abstraction that does the necessary to keep the call rate under the limit.我想将此调用包装到一个抽象中,该抽象执行必要的操作以将调用率保持在限制范围内。 It would act like a.network router: handling multiple calls and returning the results to the correct caller caring about the call rate.它会像网络路由器一样工作:处理多个呼叫并将结果返回给关心呼叫率的正确呼叫者。 The goal is to make the calling code as unaware as possible about that limitation.目标是使调用代码尽可能不知道该限制。 Otherwise, every part in the code having this call would have to be wrapped into a try-catch!否则,代码中具有此调用的每个部分都必须包装到 try-catch 中!

For example: Imagine that you can call a method from an external API that can add 2 numbers.例如:假设您可以从外部 API 调用一个可以将 2 个数字相加的方法。 This API can be called 5 times per second.这个API每秒可以调用5次。 Anything higher than this will result in an exception.任何高于此值的值都会导致异常。

To illustrate the problem, the external service that limits the call rate is like the one in the answer to this question: How to build a rate-limiting API with Observables?为了说明问题,限制调用速率的外部服务就像这个问题的答案中的那个: How to build a rate-limiting API with Observables?

Additional info附加信息

Since you don't want the worry about that limit every time you call this method from any part of your code, you think about designing a wrapper method that you could call without worrying about the rate limit.由于您不希望每次从代码的任何部分调用此方法时都担心该限制,因此您考虑设计一个包装器方法,您可以在调用时不必担心速率限制。 On the inside you care about the limit, but on the outside you expose a simple async method.在内部,您关心限制,但在外部,您公开了一个简单的异步方法。

It's similar to a web server.它类似于 web 服务器。 How does it return the correct pack of results to the correct customer?它如何将正确的结果包返回给正确的客户?

Multiple callers will call this method, and they will get the results as they come.多个调用者将调用此方法,并且他们将得到结果。 This abstraction should act like a proxy.这种抽象应该像代理一样。

How could I do it?我该怎么做?

I'm sure the firm of the wrapper method should be like我确定包装方法的公司应该像

public async Task<Results> MyMethod()

And inside the method it will perform the logic, maybe using Reactive Extensions (Buffer).在方法内部,它将执行逻辑,可能使用 Reactive Extensions (Buffer)。 I don't know.我不知道。

But how?但是怎么办? I mean, multiple calls to this method should return the results to the correct caller.我的意思是,多次调用此方法应该将结果返回给正确的调用者。 Is this even possible?这可能吗?

There are rate limiting libraries available (see Esendex's TokenBucket Github or Nuget ). 有可用的速率限制库(请参阅Esendex的TokenBucket GithubNuget )。

Usage is very simple, this example would limit polling to 1 a second 用法非常简单,此示例将轮询限制为每秒1次

// Create a token bucket with a capacity of 1 token that refills at a fixed interval of 1 token/sec.
ITokenBucket bucket = TokenBuckets.Construct()
  .WithCapacity(1)
  .WithFixedIntervalRefillStrategy(1, TimeSpan.FromSeconds(1))
  .Build();

// ...

while (true)
{
  // Consume a token from the token bucket.  If a token is not available this method will block until
  // the refill strategy adds one to the bucket.
  bucket.Consume(1);

  Poll();
}

I have also needed to make it async for a project of mine, I simply made an extension method: 我还需要使其与我的项目保持异步,我只做了一个扩展方法:

public static class TokenBucketExtensions
{
    public static Task ConsumeAsync(this ITokenBucket tokenBucket)
    {
        return Task.Factory.StartNew(tokenBucket.Consume);
    }
}

Using this you wouldn't need to throw/catch exceptions and writing a wrapper becomes fairly trivial 使用此方法,您无需抛出/捕获异常,并且编写包装器变得相当琐碎

What exactly you should depends on your goals and limitations. 您究竟应该做什么取决于您的目标和限制。 My assumptions: 我的假设:

  • you want to avoid making requests while the rate limiter is in effect 您想避免在限速器生效时发出请求
  • you can't predict whether a specific request would be denied or how exactly will it take for another request to be allowed again 您无法预测特定请求是否会被拒绝,或者再次允许另一个请求需要多少时间
  • you don't need to make multiple request concurrently, and when multiple requests are waiting, it does not matter in which order are they completed 您无需同时发出多个请求,并且当多个请求正在等待时,它们按什么顺序完成并不重要

If these assumptions are valid, you could use AsyncAutoResetEvent from AsyncEx : wait for it to be set before making the request, set it after successfully making a request and set it after a delay when it's rate limited. 如果这些假设是正确的,则可以使用AsyncAutoResetEvent的AsyncAutoResetEvent :等待设置,然后再提出请求,在成功提出请求后进行设置,并在限制速率后延迟设置。

The code can look like this: 代码如下所示:

class RateLimitedWrapper<TException> where TException : Exception
{
    private readonly AsyncAutoResetEvent autoResetEvent = new AsyncAutoResetEvent(set: true);

    public async Task<T> Execute<T>(Func<Task<T>> func) 
    {
        while (true)
        {
            try
            {
                await autoResetEvent.WaitAsync();

                var result = await func();

                autoResetEvent.Set();

                return result;
            }
            catch (TException)
            {
                var ignored = Task.Delay(500).ContinueWith(_ => autoResetEvent.Set());
            }
        }
    }
}

Usage: 用法:

public static Task<int> Add(int a, int b)
{
    return rateLimitedWrapper.Execute(() => rateLimitingCalculator.Add(a, b));
}

A variant to implement this is to ensure a minimum time between calls, something like the following: 实现此目的的一种变体是确保两次调用之间的最短时间,如下所示:

private readonly Object syncLock = new Object();
private readonly TimeSpan minTimeout = TimeSpan.FromSeconds(5);
private volatile DateTime nextCallDate = DateTime.MinValue;

public async Task<Result> RequestData(...) {
    DateTime possibleCallDate = DateTime.Now;
    lock(syncLock) {
        // When is it possible to make the next call?
        if (nextCallDate > possibleCallDate) {
            possibleCallDate = nextCallDate;
        }
        nextCallDate = possibleCallDate + minTimeout;
    }

    TimeSpan waitingTime = possibleCallDate - DateTime.Now;
    if (waitingTime > TimeSpan.Zero) {
        await Task.Delay(waitingTime);
    }

    return await ... /* the actual call to API */ ...;
}

Older thread but in this context I would also like to mention the Polly library https://github.com/App-vNext/Polly which can be very helpful in this scenario.较旧的线程,但在这种情况下,我还想提及 Polly 库https://github.com/App-vNext/Polly ,它在这种情况下非常有用。

Can do the same (and more) as TokenBucket https://github.com/esendex/TokenBucket mentioned in another answer, but this library is a bit older and doesn't support for example .net standard by default.可以像另一个答案中提到的 TokenBucket https://github.com/esendex/TokenBucket一样(甚至更多),但是这个库有点旧,默认情况下不支持例如 .net 标准。

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

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