Domanda

I wrote an API rate limiter to use with Last.fm's API.

Last.fm's Tos states that I cannot make more than 5 requests per originating IP address per second, averaged over a 5 minute period.

Here is the class I wrote:

public class RateLimiter
{
    private static readonly List<DateTime> _requests = new List<DateTime>();

    private const double _perMillisecond = 1000.1;
    private const int _rateLimit = 5;
    private const int _rateLimitCooldownMs = 500;

    public static void CheckLimiter()
    {
        _requests.Add(DateTime.Now);

        var requestsDuringRateLimit = _requests.Where(w => (DateTime.Now - w).TotalMilliseconds < _perMillisecond).ToArray();

        if (requestsDuringRateLimit.Count() >= _rateLimit)
        {
            Thread.Sleep(_rateLimitCooldownMs);
            _requests.Clear();
            Console.Clear();
        }
    }
}

The CheckLimiter method is called before the HttpWebRequest is initiated, is this a good way to limit API requests?

È stato utile?

Soluzione

This is quite fine in my opinion. Except that, there is a bug in this code. This is because what if each request is done more than a second after one another? It will never go inside that if block. Thus, some kind of memory leak because the _requests will grow larger over time and possibly never be cleared if my scenario above always happens.

Example:

for (int i = 0; i < 100; i++)
{
   RateLimiter.CheckLimiter();
   Thread.Sleep(2000);
}

What you can do is to remove entries in your _requests that are exceeding the 1 second rule like adding this line at the end of your method.

if (_requests.Count != 0)
{
    //remove irrelevant/expired entries
    _requests.RemoveAll(date => (DateTime.Now - date).TotalMilliseconds >= _perMillisecond);
}

Altri suggerimenti

I wrote a library RateLimiter to handle this kind of constraints. Main advantage from our proposed solution is that it is asynchroneous and cancellable. Another feature is that you can compose constraints to build complex constraint.

Sample:

var timeconstraint = TimeLimiter.GetFromMaxCountByInterval(5, TimeSpan.FromSeconds(1));

for(int i=0; i<1000; i++)
{
    await timeconstraint.Perform(ConsoleIt);
}       

....
private Task ConsoleIt()
{
    Trace.WriteLine(string.Format("{0:MM/dd/yyy HH:mm:ss.fff}", DateTime.Now));
    return Task.FromResult(0);
}

Composed:

var constraint = new CountByIntervalAwaitableConstraint(5, TimeSpan.FromSeconds(1));

//Create second constraint: one time each 100 ms
var constraint2 = new CountByIntervalAwaitableConstraint(1, TimeSpan.FromMilliseconds(100));

//Compose the two constraints
var timeconstraint = TimeLimiter.Compose(constraint, constraint2);

//Use it
for(int i=0; i<1000; i++)
{
    await timeconstraint.Perform(ConsoleIt);
} 

It is also available as a nuget package.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top