【问题标题】:Queuing Guzzle Requests With Limits使用限制排队 Guzzle 请求
【发布时间】:2017-10-20 21:27:12
【问题描述】:

我正在使用 Guzzle 6 开发一个 Laravel 应用程序。很多功能都依赖于一个 API,我已经为其创建了一个包装器。

我的包装器是一个单独的类,它在 __construct() 中创建 Guzzle 客户端,并具有各种从 Guzzle 请求返回响应的公共函数。

我使用的 API 每 10 秒有 40 个请求的限制。我正在缓存东西,所以很少会达到这个限制,但我想知道我的应用程序不会死!

关于我的应用的一些说明:

  • 仅在过去 6 小时内未进行相同调用时才进行 API 调用。如果有,则永远不会进行调用,并且直接从我的 redis 缓存中提供响应。
  • 在大多数情况下,API 调用是通过用户操作进行的。应用程序本身永远不会接近这些限制。
  • 在大多数情况下,我已经拥有向用户显示请求页面所需的数据。可能会在后台进行 API 调用以查看我是否需要更新任何内容,但如果我已经拥有数据,并且 API 请求失败,这不会使页面变得无用。
  • 该应用程序已上线,https://likethis.tv 如果您想查看。我正在使用TMDb API。

所以,我的问题是,我应该如何确保我没有达到这个限制?我的一些想法如下:

  • 使用 Laravel 队列系统将 Guzzle 请求放入队列中,并且仅在仍有请求时才处理它们。如果没有,请等到 10 秒的冷却时间过去...
  • 直接使用HandlerStack 表示 Guzzle。不确定这是否可行,但我之前使用过HandlerStack 来缓存响应。

我尽量不引起过于自以为是的回应,但我确信可能有比上述更好和/或更简单的方法,或者如果它们是好主意,任何指针或建议都会很棒。

提前致谢。

【问题讨论】:

  • 为此添加了赏金。我也想知道如何有效地限制我自己对 API 的调用,我也在考虑使用队列,但无法找到正确的方法。
  • 应该将api的响应传递到数据库中还是应该显示给客户端?第二种情况看起来很困难。
  • API 调用的顺序在某种程度上是否相关?
  • 响应用于更新/创建数据库中的记录。顺序并不重要。目前我的包装器只处理 404,所以我想我会重做那一点,并将错误响应交给刚刚进行调用的任何内容,以便它可以决定做什么。例如,当只有 API 请求出错时,如果我的数据库中有记录,则不需要抛出错误。

标签: php laravel laravel-5 guzzle guzzle6


【解决方案1】:
  1. 使用 Jobs 包装您的 API 调用并将它们推送到单独的队列:

    ApiJob::dispatch()->onQueue('api');
    
  2. 使用mxl/laravel-queue-rate-limit 包(我是作者)对api 队列进行限速。将此添加到config/queue.php

    'rateLimit' => [
        'api' => [
            'allows' => 40,
            'every' => 10
        ]
    ]
    
  3. 运行队列工作者:

    $ php artisan queue:work --queue api
    

另见this answer

【讨论】:

    【解决方案2】:

    没有足够的信息来真正深入研究这一点,但为了帮助您入门,好的 API 通常会在您超过其限制限制时返回 429 响应代码。

    您可以使用 guzzle 中的 $res->getStatusCode() 来检查这一点,如果用户过快地发出过多请求,则将消息闪回给用户。

    您能否提供更多有关您的应用在做什么的信息?您是否在 foreach 循环中发出请求?视图是否依赖于来自此 API 的数据?

    【讨论】:

    • API 确实返回有用的错误代码,所以这是可能的。我可能会按照我在上面对我的问题的评论中所说的去做,而不是处理我的包装器中的错误响应,而是将它们传递给我的控制器,因为在某些情况下,API 没有返回我需要的内容并不是结束世界。
    【解决方案3】:

    我也在处理同样的问题,我更喜欢基于回调的架构,其中我的Client 类控制请求流。目前我正在做睡眠和检查算法。我为我工作,因为我有 3 秒的冷却时间。

    我使用Cache 来保存触发请求的计数。

    while(($count = Cache::get($this->getCacheKey(),0)) >= 40){ // Max request
        sleep(1);
    }
    Cache::set($this->getCacheKey(), ++$count);
    // fire request
    
    function getCacheKey(){
        return floor(time()/10); // Cool down time
    }
    

    排队似乎是一个更好的选择,我最终会转向那个。在将队列放在中间之前,您需要记住几件事。

    1. 基于回调的架构,因为您可能需要在队列中保存代码的序列化状态。基于回调的设计将把控制权交给Client 类。您不必担心代码中的节流问题。
    2. 序列化可能很棘手,试试__sleep__wakeup
    3. 您可能还希望优先处理少数呼叫,您可以从客户端为此类呼叫分配配额。

    【讨论】:

      【解决方案4】:

      我个人认为 Guzzle 不应该处理这种情况,但如果你想让 Guzzle 处理它,我会编写一个中间件来检查响应以及它是否返回速率限制错误(例如状态码 429)。然后您可以发出自定义错误或等到速率限制结束再试一次。但是,这可能会导致响应时间较长(因为您要等待速率限制)。

      我不认为 Laravel 队列会更好,因为这会使响应异步可用,并且您必须轮询数据库或缓存,无论您存储结果的位置。 (当然,如果您不需要立即获得结果,它也可以工作)

      如果此第三方服务直接连接到面向用户的界面,我可能会应用相同的速率限制(在您的应用程序代码中)并向用户返回错误消息,而不是等待并自动解决问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-12-17
        • 2013-12-13
        • 2018-01-15
        • 1970-01-01
        • 1970-01-01
        • 2011-06-16
        • 2014-10-18
        • 1970-01-01
        相关资源
        最近更新 更多