【问题标题】:Rate-limiting Guzzle Requests in SymfonySymfony 中的限速 Guzzle 请求
【发布时间】:2018-01-15 00:27:13
【问题描述】:

这实际上是继上一个问题之后的一个问题,不幸的是,我没有收到任何答案,所以我并没有完全屏住呼吸等待回复,但我知道这可能是一个需要解决的棘手问题。

我目前正在尝试对外部 API 的传出请求实施速率限制,以匹配其端的限制。我试图在我们用来管理这个特定 API 的 Guzzle 请求的类中实现一个令牌桶库 (https://github.com/bandwidth-throttle/token-bucket)。

最初,这似乎按预期工作,但我们现在开始看到来自 API 的 429 响应,因为它似乎不再正确地限制请求的速率。

我感觉正在发生的事情是,由于 Symfony 处理服务的方式,每次调用 API 时,桶中的令牌数量都会被重置。

我正在设置当前在服务的构造函数中设置存储桶位置、速率和起始数量:

public function __construct()
{
  $storage = new FileStorage(__DIR__ . "/api.bucket");
  $rate = new Rate(50, Rate::MINUTE);
  $bucket = new TokenBucket(50, $rate, $storage);
  $this->consumer = new BlockingConsumer($bucket);
  $bucket->bootstrap(50);
}

然后我尝试在每个请求之前使用令牌:

public function fetch(): array
{
  try {
    $this->consumer->consume(1);
    $response = $this->client->request(
      'GET', $this->buildQuery(), [
        'query' => array_merge($this->params, ['api_key' => $this->apiKey]),
        'headers' => [ 'Content-type' => 'application/json' ]
      ]
    );
  } catch (ServerException $e) {
    // Process Server Exception
  } catch (ClientException $e) {
    // Process Client Exception
  }
  return $this->checkResponse($response);
}

我看不到任何明显的东西,这将允许它每分钟请求超过 50 次,除非每个请求都重置可用令牌的数量。

这被提供给一组存储库服务,这些存储库服务处理将来自每个端点的数据转换为系统内使用的对象。消费者使用适当的存储库来请求完成其流程所需的数据。

如果令牌的数量被服务构造函数中的引导函数重置,那么应该将其移动到 Symfony 框架中仍可与消费者一起使用的哪个位置?

【问题讨论】:

    标签: php symfony guzzle rate-limiting


    【解决方案1】:

    我认为它应该可以工作,但也许尝试从每个请求中移动 ->bootstrap(50) 调用?不确定,但这可能是原因。

    无论如何,最好只执行一次,作为部署的一部分(每次部署新版本时)。实际上,它与 Symfony 没有任何关系,因为该框架对部署过程没有任何限制。所以这取决于你如何进行部署。

    附:您是否考虑过只处理来自服务器的 429 错误?当您收到 429 错误时,IMO 您可以等待(这就是 BlockingConsumer 在里面所做的)。它更简单,并且不需要在您的系统中添加额外的层。

    【讨论】:

    • 我正在使用来自 429 响应的标头来延迟发生这种情况时的必要性。我只是尽量避免 429 响应,因为 API 的所有者相当坚持不超过速率限制。我已将引导程序移至部署时运行的单独命令,但这并没有解决问题。
    • 好的,知道原因了。然后我觉得一切都很好,你必须调试整个系统才能找到问题。
    【解决方案2】:

    顺便说一句,您是否考虑过将 nginx 的 ngx_http_limit_req_module 作为替代解决方案?它通常默认自带nginx,所以不需要额外的安装操作,只需要一个小配置。

    您可以在您的代码和目标 Web 服务后面放置一个 nginx 代理,并对其启用限制。然后在您的代码中,您将照常处理 429,但请求将由您的本地 nginx 代理限制,而不是由外部 Web 服务限制。所以最终目的地只会收到有限数量的请求。

    【讨论】:

    • 谢谢,我实际上发现原因是未记录的方法速率限制。尽管主要速率限制为 50/分钟,但一个特定端点被限制为 10/分钟。只是日志没有明确指出哪个消费者收到了 429 响应,所以我推测它达到了主要的速率限制。
    • 好的,很酷。无论如何,我认为使用 nginx 的速率限制更好:) 因为然后你从你的应用程序中删除一个状态(然后可以水平扩展它)。
    【解决方案3】:

    我发现了一个使用 Guzzle bundle 进行 symfony 的技巧。

    我必须改进向 Google API 发送 GET 请求的顺序程序。在代码示例中,它是一个 pagespeed URL。

    要设置速率限制,可以选择在异步发送请求之前延迟请求。

    Pagespeed 速率限制为每分钟 200 个请求。

    快速计算得出每个请求 200/60 = 0.3 秒。

    这是我在 300 个 url 上测试的代码,得到了一个没有错误的奇妙结果,除非在 GET 请求中作为参数传递的 url 给出了 400 HTTP 错误(错误请求)。

    我设置了 0.4 秒的延迟,平均结果时间不到 0.2 秒,而顺序程序则需要一分钟以上。

    use GuzzleHttp;
    use GuzzleHttp\Client;
    use GuzzleHttp\Promise\EachPromise;
    use GuzzleHttp\Exception\ClientException;
    
    // ... Now inside class code ... //  
    
    $client = new GuzzleHttp\Client();
    $promises = [];  
    
    foreach ($requetes as $i=>$google_request) {
        $promises[] = $client->requestAsync('GET', $google_request ,['delay'=>0.4*$i*1000]); // delay is the trick not to exceed rate limit (in ms)
    }  
    
    GuzzleHttp\Promise\each_limit($promises, function(){ // function returning the number of concurrent requests
        return 100; // 1 or 100 concurrent request(s) don't really change execution time
    }, // Fulfilled function
    function ($response,$index)use($urls,$fp) { // $urls is used to get the url passed as a parameter in GET request and $fp a csv file pointer
        $feed = json_decode($response->getBody(), true); // Get array of results
        $this->write_to_csv($feed,$fp,$urls[$index]); // Write to csv
    }, // Rejected function
    function ($reason,$index) {
        if ($reason instanceof GuzzleHttp\Exception\ClientException) {
            $message = $reason->getMessage();
            var_dump(array("error"=>"error","id"=>$index,"message"=>$message)); // You could write the errors to a file or database too
        }
    })->wait();
    

    【讨论】:

      猜你喜欢
      • 2017-10-20
      • 1970-01-01
      • 2018-03-24
      • 2014-12-17
      • 1970-01-01
      • 2018-12-22
      • 1970-01-01
      • 1970-01-01
      • 2013-12-13
      相关资源
      最近更新 更多