我正在为外部API设计一个.NET客户端应用程序。它将有两个主要责任:
Service的文档指定了在给定时间内可发出的最大请求数量的下列规则:
在一天内:
晚上:
超过这些限制不会导致立即锁定--也不会抛出任何例外。但供应商可能会生气,联系我们,然后禁止我们使用他的服务。因此,我需要建立一些请求延迟机制来防止这种情况发生。以下是我的看法:
public async Task MyMethod(Request request)
{
await _rateLimter.WaitForNextRequest(); // awaitable Task with calculated Delay
await _api.DoAsync(request);
_rateLimiter.AppendRequestCounters();
}最安全和最简单的选择将是尊重最低的利率限制,即最大3次请求每2秒。但是,由于“同步”的责任,有必要尽可能多地使用这些限制。
因此,下一个选项是根据当前的请求计数添加一个延迟。我尝试过自己做一些事情,我也使用过大卫·德斯马豪斯( David Desmaisons )著的RateLimiter,这很好,但是这里有一个问题:
假设我的客户端每天向API发送3次请求,我们将看到:
如果我的应用程序只是关于“同步”,但是“客户端”请求不能等待那么长时间,这是可以接受的。
我在网络上搜索过,我读过令牌/漏桶和滑动窗口算法,但是我无法将它们转换到我的案例和.NET,因为它们主要涉及拒绝超过极限的请求。我已经找到了这个回购和那个回购,但它们都只是服务端解决方案。
类似于QoS的速率分割,这样“同步”就会更慢,而“客户端”的速率更快,这不是一个选项。
假设将测量当前的请求速率,如何计算下一个请求的延迟,使其能够适应当前的情况,尊重所有的最大速率,并且不会超过5秒?在接近极限时逐渐减速的东西。
发布于 2019-08-16 10:30:51
这可以通过使用您在GitHub上链接的库来实现。我们需要使用由3个TimeLimiter组成的组合CountByIntervalAwaitableConstraint,如下所示:
var hourConstraint = new CountByIntervalAwaitableConstraint(6000, TimeSpan.FromHours(1));
var minuteConstraint = new CountByIntervalAwaitableConstraint(120, TimeSpan.FromMinutes(1))
var secondConstraint = new CountByIntervalAwaitableConstraint(3, TimeSpan.FromSeconds(1));
var timeLimiter = TimeLimiter.Compose(hourConstraint, minuteConstraint, secondConstraint);我们可以通过这样做来测试这是否有效:
for (int i = 0; i < 1000; i++)
{
await timeLimiter;
Console.WriteLine($"Iteration {i} at {DateTime.Now:T}");
}这将每秒钟运行3次,直到达到120次迭代(迭代119次),然后等待分钟结束,然后每秒钟继续运行3次。我们还可以(再次使用库)通过使用如下所提供的TimeLimiter扩展方法来轻松地使用HTTP中的AsDelegatingHandler():
var handler = TimeLimiter.Compose(hourConstraint, minuteConstraint, secondConstraint);
var client = new HttpClient(handler);我们也可以使用CancellationToken,但据我所知,并不是同时使用它作为HttpClient的处理程序。下面是如何在HttpClient中使用它的方法:
var timeLimiter = TimeLimiter.Compose(hourConstraint, minuteConstraint, secondConstraint);
var client = new HttpClient();
for (int i = 0; i < 100; i++)
{
await composed.Enqueue(async () =>
{
var client = new HttpClient();
var response = await client.GetAsync("https://hacker-news.firebaseio.com/v0/item/8863.json?print=pretty");
if (response.IsSuccessStatusCode)
Console.WriteLine(await response.Content.ReadAsStringAsync());
else
Console.WriteLine($"Error code {response.StatusCode} reason: {response.ReasonPhrase}");
}, new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token);
}编辑关于OPs问题的更多信息:
如果您想确保用户可以发送请求,而不必等待限制结束,我们需要每秒钟/分钟/小时将一定数量的请求用于我们的用户。为此,我们需要一个新的TimeLimiter,并调整API TimeLimiter。这是两个新的:
var apiHourConstraint = new CountByIntervalAwaitableConstraint(5500, TimeSpan.FromHours(1));
var apiMinuteConstraint = new CountByIntervalAwaitableConstraint(100, TimeSpan.FromMinutes(1));
var apiSecondConstraint = new CountByIntervalAwaitableConstraint(2, TimeSpan.FromSeconds(1));
// TimeLimiter for calls automatically to the API
var apiTimeLimiter = TimeLimiter.Compose(apiHourConstraint, apiMinuteConstraint, apiSecondConstraint);
var userHourConstraint = new CountByIntervalAwaitableConstraint(500, TimeSpan.FromHours(1));
var userMinuteConstraint = new CountByIntervalAwaitableConstraint(20, TimeSpan.FromMinutes(1));
var userSecondConstraint = new CountByIntervalAwaitableConstraint(1, TimeSpan.FromSeconds(1));
// TimeLimiter for calls made manually by a user to the API
var userTimeLimiter = TimeLimiter.Compose(userHourConstraint, userMinuteConstraint, userSecondConstraint);你可以利用这些数字来满足你的需要。
现在要使用它:
我看到您正在使用一个中心方法来执行您的请求,这使得执行变得更容易。我只需要添加一个可选的布尔参数来确定它是一个自动执行的请求还是一个用户发出的请求。(如果您想要的不仅仅是自动和手动请求,可以用Enum替换这个参数)
public static async Task DoRequest(Request request, bool manual = false)
{
TimeLimiter limiter;
if (manual)
limiter = TimeLimiterManager.UserLimiter;
else
limiter = TimeLimiterManager.ApiLimiter;
await limiter;
_api.DoAsync(request);
}
static class TimeLimiterManager
{
public static TimeLimiter ApiLimiter { get; }
public static TimeLimiter UserLimiter { get; }
static TimeLimiterManager()
{
var apiHourConstraint = new CountByIntervalAwaitableConstraint(5500, TimeSpan.FromHours(1));
var apiMinuteConstraint = new CountByIntervalAwaitableConstraint(100, TimeSpan.FromMinutes(1));
var apiSecondConstraint = new CountByIntervalAwaitableConstraint(2, TimeSpan.FromSeconds(1));
// TimeLimiter to control access to the API for automatically executed requests
ApiLimiter = TimeLimiter.Compose(apiHourConstraint, apiMinuteConstraint, apiSecondConstraint);
var userHourConstraint = new CountByIntervalAwaitableConstraint(500, TimeSpan.FromHours(1));
var userMinuteConstraint = new CountByIntervalAwaitableConstraint(20, TimeSpan.FromMinutes(1));
var userSecondConstraint = new CountByIntervalAwaitableConstraint(1, TimeSpan.FromSeconds(1));
// TimeLimiter to control access to the API for manually executed requests
UserLimiter = TimeLimiter.Compose(userHourConstraint, userMinuteConstraint, userSecondConstraint);
}
}这并不完美,因为当用户没有每分钟执行20个API调用时,但是您的自动化系统需要每分钟执行超过100个API调用,它将不得不等待。
关于昼夜差异:您可以为Api/UserLimiter使用2个备份字段,并在属性的{ get {...} }中返回适当的字段。
https://stackoverflow.com/questions/57522524
复制相似问题