2016-07-30 120 views
4

我可以訪問一個API調用,接受每秒最大調用速率。如果超過速率,則會拋出異常環繞速率限制API調用

我想把這個調用包裝成一個抽象,這個抽象把通話費率保持在限制之下。它將像網絡路由器一樣工作:處理多個呼叫並將結果返回給關注呼叫率的正確呼叫者。目標是使呼叫代碼儘可能不知道該限制。否則,具有此調用的代碼中的每個部分都必須包裝到try-catch中!

例如:想象一下,您可以從可以添加2個數字的extern API調用方法。這個API可以稱爲每秒5次。任何高於此的值都會導致異常。

爲了說明問題,限制呼叫速率的外部服務就像是一個在回答這個問題

How to build a rate-limiting API with Observables?

附加信息:

既然你不每次從代碼的任何部分調用此方法時都不需要擔心該限制,因此您考慮設計一種可以調用的包裝器方法,而不必擔心速率限制。在內部你關心的限制,但在外面你公開一個簡單的異步方法。

它類似於網絡服務器。它如何將正確的結果包返回給正確的客戶?

多個來電者會調用這個方法,他們會在他們來的時候得到結果。這種抽象應該像一個代理。

我該怎麼辦?

我敢肯定,包裝方法的公司應該像

public async Task<Results> MyMethod() 

而且裏面的方法也將執行邏輯,也許使用無擴展(緩衝)。我不知道。

但是如何?我的意思是,對這個方法的多次調用應該將結果返回給正確的調用者。這甚至有可能嗎?

非常感謝!

+1

想到最簡單的方法是使用FiFo('Queue')和某種異步我們實施處理呼叫。 – lokusking

+1

當你有更多的來電比可以處理的時候,你有沒有策略?即如果您在兩天內獲得1000次通話/分鐘,您是否應該放棄一些消息?如果你只是填充一個無界緩衝區(可能會拋出OOM)?或者有一個固定大小的緩衝區,當它滿的時候,會阻止對API的進一步調用? –

+0

我認爲在我的情況下,要長時間保持高通話率才能生成OOM是相當困難的,所以目前我會採取第一種策略。 – SuperJMN

回答

4

有可用的速率限制庫(見Esendex的TokenBucket GithubNuget)。

用法很簡單,這個例子將限制輪詢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); 
    } 
} 

使用這個你不需要拋出/捕獲異常並且編寫一個包裝器變得相當簡單

0

實現這個變體是保證通話之間的最小時間,類似如下:

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 */ ...; 
} 
+2

你不能在'lock'中使用'await'。 – svick

+0

哎呀,謝謝你,@svick,我編輯了答案,以避免'lock'裏面的'await'。然而,思想保持不變 - 計算下一個可能呼叫的日期,等待它,然後進行呼叫。 –

0

究竟你應該取決於你的目標和限制。我的假設:

  • 你希望避免的請求,而速率限制實際上是
  • 你無法預測一個特定的請求是否會被拒絕,否則將究竟如何承擔再次被允許另一個請求
  • 你不需要讓多個請求同時,當有多個請求正在等待,不要緊的次序是他們完成

如果這些假設是正確的,你可以使用AsyncAutoResetEvent from AsyncEx:等待它將在maki之前設置在請求成功之後設置它,並且在速率受限時延遲設置它。

的代碼可以是這樣的:

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()); 
      } 
     } 
    } 
} 

用法:

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