当前位置: 首页 > 知识库问答 >
问题:

客户端请求限速

巫马浩言
2023-03-14
  • 同步-向API发出一批请求,并定期将响应保存到我的数据库。
  • 客户端-从我的客户端的用户向API请求的传递。

服务的文档指定了在给定时间段内可以发出的最大请求数的以下规则:

在一天中:

    null
    null

超过这些限制不会导致立即锁定-不会抛出任何异常。但供应商可能会感到恼火,联系我们,然后禁止我们使用他的服务。因此,我需要有一些请求延迟机制,在适当的地方,以防止这一点。我是这样看的:

public async Task MyMethod(Request request)
{
  await _rateLimter.WaitForNextRequest(); // awaitable Task with calculated Delay
  await _api.DoAsync(request);
  _rateLimiter.AppendRequestCounters();
}

最安全和最简单的选择是只遵守最低速率限制,即每2秒最多3个请求。但由于“同步”责任,需要尽可能多地使用这些限制。

因此下一个选项是根据当前请求计数添加延迟。我试着自己做一些事情,我也用过David Desmaisons的RateLimiter,它会很好的,但是这里有一个问题:

    null

如果我的应用程序只涉及“同步”,这是可以接受的,但“客户端”请求不能等那么久。

我在网上搜索过,也读过关于令牌/漏桶和滑动窗口算法的文章,但我无法将它们转换到我的case和.NET中,因为它们主要涉及拒绝超过限制的请求。我找到了这个回购和那个回购,但它们都只是服务端的解决方案

类似于QoS的速率分割,使得“同步”的速率较慢,而“客户端”的速率较快,这不是一种选择。

共有1个答案

施兴言
2023-03-14

这可以通过使用您在GitHub上链接的库来实现。我们需要使用由3CountByIntervalAwaitableConstraint组成的组合时间限制器,如下所示:

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次。我们还可以(同样使用库)使用asDelegatingHandler()扩展方法(如下所示)轻松地将TimeLimiter与HTTP客户端一起使用:

var handler = TimeLimiter.Compose(hourConstraint, minuteConstraint, secondConstraint);
var client = new HttpClient(handler);

我们还可以使用cancellationtokens,但据我所知,不是同时使用它作为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问题更多:

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

你可以玩弄这些数字以满足你的需要。

现在使用它:
我看到您正在使用一个中央方法来执行请求,这使它更容易。我将添加一个可选的布尔参数来确定它是自动执行的请求还是用户发出的请求。(如果您想要的不仅仅是自动和手动请求,可以将此参数替换为枚举)

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个以上时,它将不得不等待。

 类似资料:
  • 客户端的HTTP/HTTPS请求。 进程:主进程​ ClientRequest是由EventEmitter来实现Writable Stream​ new ClientRequest(options) 作用:发起新的HTTP/HTTPS请求 options(Object | String) - options是String时即请求URL。 options 是Object时则按以下属性请求: meth

  • httplib 库主要用来模拟客户端发送 HTTP 请求,类似于 Curl 工具,支持 JQuery 类似的链式操作。使用起来相当的方便;通过如下方式进行安装: go get github.com/astaxie/beego/httplib 如何使用 首先导入包 import ( "github.com/astaxie/beego/httplib" ) 然后初始化请求方法,返回对象 r

  • 当浏览器请求一个网页时,它会向网络服务器发送一系列不能被直接读取的信息,因为这些信息是作为HTTP信息头的一部分来传送的。您可以查阅HTTP协议来获得更多的信息。 下表列出了浏览器端信息头的一些重要内容,在以后的网络编程中将会经常见到这些信息: 信息 描述 Accept 指定浏览器或其他客户端可以处理的MIME类型。它的值通常为 image/png 或 image/jpeg Accept-Char

  • 当浏览器请求网页时,它会向 Web 服务器发送特定信息,这些信息不能被直接读取,因为这些信息是作为 HTTP 请求的头的一部分进行传输的。您可以查看 HTTP 协议 了解更多相关信息。 以下是来自于浏览器端的重要头信息,您可以在 Web 编程中频繁使用: 头信息 描述 Accept 这个头信息指定浏览器或其他客户端可以处理的 MIME 类型。值 image/png 或 image/jpeg 是最常

  • Java import java.io.IOException; import java.net.URLEncoder; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import jav

  • 当我运行我的gRPC客户端,它试图将请求流式传输到服务器时,我收到了这个错误:"TypeError: has typelist_iterator,但期望其中之一:bytes, unicode" 我需要以某种方式对我发送的文本进行编码吗?错误消息有一定的意义,因为我肯定是在传入一个迭代器。我从gRPC留档中假设这是需要的。(https://grpc.io/docs/tutorials/basic/p