与节流/速率限制(429错误)使用异步时/等待处理速率、错误

2023-09-04 03:38:11 作者:我的爱已过期

我有以下的异步code,它会从这么多的地方在我的项目名为:

I have the following async code that gets called from so many places in my project:

public async Task<HttpResponseMessage> MakeRequestAsync(HttpRequestMessage request)
{            
    var client = new HttpClient();
    return await client.SendAsync(request).ConfigureAwait(false);                
}

如何上面的方法被调用的一个例子:

An example of how the above method gets called:

 var tasks = items.Select(async i =>
            {                    
                var response = await MakeRequestAsync(i.Url);           
                //do something with response    
            });

该ZenDesk的API,我打允许每分钟之后,我得到一个429错误200的请求。我需要做某种一个视频下载的,如果我遇到了429错误,但与异步/计谋,有可能在并行线程这么多要求等待的过程中,我不知道我怎么可以让所有的人的睡眠为5秒左右,然后恢复试。

The ZenDesk API that I'm hitting allows about 200 requests per minute after which I'm getting a 429 error. I need to do some sort of a Thread.sleep if I encounter the 429 error, but with with async/await, there may be so many requests in parallel threads waiting to process, I am not sure how I can make all of them sleep for 5 seconds or so and then resume again.

什么是解决这个问题的正确方法?我想听听快速的解决方案,以及良好的设计解决方案。

What's the correct way to approach this problem? I'd like to hear quick solutions as well as good-design solutions.

推荐答案

我不认为这是一个重复,因为最近标记。其他的SO海报不需要基于时间的滑动窗口(或基于时间的节流)和答案有不覆盖这种情况。当你想设置的传出请求硬限制才起作用。

I do not think that this is a duplicate, as marked recently. The other SO poster does not need a time-based sliding window (or time based throttling) and the answer there does not cover this situation. That works only when you want to set a hard limit on outgoing requests.

总之,准快速的解决办法是让在 MakeRequestAsync 法的限制。事情是这样的:

Anyway, a quasi-quick solution is to make the throttling in the MakeRequestAsync method. Something like this:

public async Task<HttpResponseMessage> MakeRequestAsync(HttpRequestMessage request)
{            
    //Wait while the limit has been reached. 
    while(!_throttlingHelper.RequestAllowed) 
    {
      await Task.Delay(1000);
    }

    var client = new HttpClient();

    _throttlingHelper.StartRequest();
    var result = await client.SendAsync(request).ConfigureAwait(false);
    _throttlingHelper.EndRequest();

    return result; 
}

ThrottlingHelper 只是我现在做,所以你可能需要将其调试位(读 - 可能不起作用开箱即用)。 它试图是一个时间戳滑动窗口。

The class ThrottlingHelper is just something I made now so you may need to debug it a bit (read - may not work out of the box). It tries to be a timestamp sliding window.

public class ThrottlingHelper : IDisposable
{
    //Holds time stamps for all started requests
    private readonly List<long> _requestsTx;
    private readonly ReaderWriterLockSlim _lock;

    private readonly int _maxLimit;
    private TimeSpan _interval;

    public ThrottlingHelper(int maxLimit, TimeSpan interval)
    {
        _requestsTx = new List<long>();
        _maxLimit = maxLimit;
        _interval = interval;
        _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
    }

    public bool RequestAllowed
    {
        get
        {
            _lock.EnterReadLock();
            try
            {
                var nowTx = DateTime.Now.Ticks;
                return _requestsTx.Count(tx => nowTx - tx < _interval.Ticks) < _maxLimit;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }
    }

    public void StartRequest()
    {
        _lock.EnterWriteLock();
        try
        {
            _requestsTx.Add(DateTime.Now.Ticks);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void EndRequest()
    {
        _lock.EnterWriteLock();
        try
        {
            var nowTx = DateTime.Now.Ticks;
            _requestsTx.RemoveAll(tx => nowTx - tx >= _interval.Ticks);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void Dispose()
    {
        _lock.Dispose();
    }
}

您会用它作为使请求的类的成员,并创建实例是这样的:

You would use it as a member in the class that makes the requests, and instantiate it like this:

_throttlingHelper = new ThrottlingHelper(200, TimeSpan.FromMinutes(1));

不要忘记处理它,当你用它做。

Don't forget to dispose it when you're done with it.

文件有关的位 ThrottlingHelper

构造PARAMS是要能够做到在一定的时间间隔的最大请求和区间本身作为一个时间跨度。因此,200还有1分意味着你想不超过200个的请求/分。 属性 RequestAllowed 让你知道,如果你能够做到与当前的限制设置的请求。 方法 StartRequest &放大器; EndRequest 注册/注销使用当前日期/时间的请求。 Constructor params are the maximum requests you want to be able to do in a certain interval and the interval itself as a time span. So, 200 and 1 minute means that that you want no more than 200 requests/minute. Property RequestAllowed lets you know if you are able to do a request with the current throttling settings. Methods StartRequest & EndRequest register/unregister a request by using the current date/time.

修改/陷阱

如由@PhilipABarnes, EndRequest 可能会删除仍在进行中的请求。据我所看到的,这可以在两种情况下发生:

As indicated by @PhilipABarnes, EndRequest can potentially remove requests that are still in progress. As far as I can see, this can happen in two situations:

的间隔很小,这样的请求没有得到完成的好时机。 的要求实际上需要超过的时间间隔来执行。

提出的解决方案涉及到实际的匹配 EndRequest 由GUID或类似的方式调用 StartRequest 通话。

The proposed solution involves actually matching EndRequest calls to StartRequest calls by means of a GUID or something similar.