包装速率限制API调用

我有一个API调用,它接受每秒最大调用率 。 如果超过速率, 则抛出exception

我想把这个调用包装成一个抽象,它可以使调用率保持在极限之下。 它将像网络路由器一样:处理多个呼叫并将结果返回给正确的呼叫者,关注呼叫率。 目标是使调用代码尽可能不知道该限制。 否则,具有此调用的代码中的每个部分都必须包装到try-catch中!

例如:想象一下,您可以从可以添加2个数字的extern API调用方法。 此API可以每秒调用5次 。 高于此值的任何内容都将导致exception。

为了说明问题,限制通话费率的外部服务就像这个问题的答案中的那个

如何使用Observables构建速率限制API?

附加信息:

由于每次从代码的任何部分调用此方法时都不需要担心该限制,因此您可以考虑设计一个可以调用的包装器方法,而不必担心速率限制。 在内部你关心限制,但在外面你暴露了一个简单的异步方法。

它类似于Web服务器。 它如何将正确的结果包返回给正确的客户?

多个呼叫者将调用此方法,他们将获得结果。 这种抽象应该像代理一样。

我怎么能这样做?

我确定包装方法的公司应该是这样的

public async Task MyMethod() 

在方法内部,它将执行逻辑,可能使用Reactive Extensions(Buffer)。 我不知道。

但是怎么样? 我的意思是,多次调用此方法应该将结果返回给正确的调用者。 这有可能吗?

非常感谢!

有速率限制库可用(参见Esendex的TokenBucket Github或Nuget )。

用法很简单,这个例子将轮询限制为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(); } 

我还需要让它为我的项目异步,我只是做了一个扩展方法:

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

使用它你不需要抛出/捕获exception,编写包装器变得相当简单

实现此目的的一种变体是确保调用之间的最短时间,如下所示:

 private readonly Object syncLock = new Object(); private readonly TimeSpan minTimeout = TimeSpan.FromSeconds(5); private volatile DateTime nextCallDate = DateTime.MinValue; public async Task 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 */ ...; } 

你究竟应该取决于你的目标和限制。 我的假设:

  • 您希望在速率限制器生效时避免发出请求
  • 您无法预测特定请求是否会被拒绝,或者如何再次允许另一个请求
  • 您不需要同时发出多个请求,并且当多个请求等待时,它们完成的顺序无关紧要

如果这些假设有效,您可以使用AsyncAutoResetEvent的AsyncAutoResetEvent :等待在发出请求之前设置它,在成功发出请求后设置它,并在速率受限后延迟设置它。

代码可能如下所示:

 class RateLimitedWrapper where TException : Exception { private readonly AsyncAutoResetEvent autoResetEvent = new AsyncAutoResetEvent(set: true); public async Task Execute(Func> 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()); } } } } 

用法:

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