如何重构这个 cURL 脚本以利用 PHP 的 curl_multi 函数?

发布于 2024-10-03 07:59:01 字数 3031 浏览 2 评论 0原文

我在 PHP 应用程序中使用 cURL 连接到 RESTful API。然而,我最近发现我没有并行化我的 cURL 连接,因此执行多个连续连接会导致最终用户出现极大的延迟。

我以前没有使用过curl_multi,读完文档后我有点不知所措。如何最好地重构以下代码以利用 curl_multi 的并行化?

编辑:我忘了提及我开源了此处使用的 API。这些是我自己的 Directed Edge PHP 绑定。因此,如果您愿意,您还可以将此处的帮助合并到 GitHub 上的代码中,并且您将被列为贡献者。

以下是我在客户端代码中所做的示例:

 // Get 100 goal recommendations from Directed Edge
  $de = new DirectedEdgeRest();
  $item = "user".$uid;
  $limit = 100;
  $tags = "goal";
  $recommendedGoals = $de->getRecommended($item, $tags, $limit);

  // Get 100 interest recommendations from Directed Edge
  $de = new DirectedEdgeRest();
  $item = "user".$uid;
  $limit = 100;
  $tags = "interest";
  $recommendedInterests = $de->getRecommended($item, $tags, $limit);

以下是 DirectedEdgeRest() 中的相关函数

  /**
   * Returns array of recommended result IDs for an item
   * @param string $item Item, e.g. "Miles%20Davis"
   * @param string $tags Tags as comma delimited string, e.g. "product,page"
   * @param int $limit Limit for max results
   *
   * @return array Recommended result IDs
   */
  public function getRecommended($item, $tags, $limit)
  {
    // Connect to Directed Edge and parse the returned XML
    $targeturl = self::buildURL($item, 'recommended', $tags, $limit, 'true');
    $response = self::getCurlResponse($targeturl);
    $xml = self::parseXML($response);

    // Iterate through the XML and place IDs into an array
    foreach($xml->item->recommended as $recommended) {
      $recommendedResults[] = filter_var($recommended, FILTER_SANITIZE_NUMBER_INT);
    }

    return $recommendedResults;
  }

  /**
   * Builds URL for cURL
   * @param string $item Item, e.g. "Miles%20Davis"
   * @param string $type Type of API request: either "related" or "recommended"
   * @param string $tags Tags as comma delimited string, e.g. "product,page"
   * @param int $limit Limit for max results
   * @param string $exclude "true" if you want to exclude linked, "false" otherwise
   *
   * @return string The target URL
   */
  private function buildURL($item, $type, $tags, $limit, $exclude)
  {
    $targeturl = DE_BASE_URL;
    $targeturl .= $item; // Item
    $targeturl .= "/" . $type; // Type
    $targeturl .= "?tags=" . $tags; // Tags
    $targeturl .= "&maxresults=" . $limit; // Limit
    $targeturl .= "&excludeLinked=" . $exclude; // Exclude
    return $targeturl;
  }

  /**
   * Returns the cURL response given a target URL
   * @param string $targeturl The target URL for cURL
   *
   * @return string cURL Response
   */
  private function getCurlResponse($targeturl)
  {
    $ch = curl_init($targeturl);
    curl_setopt($ch, CURLOPT_POST, FALSE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    $response = curl_exec($ch);
    curl_close($ch);
    return $response;
  }

I'm using cURL in my PHP app to connect to a RESTful API. However, I just recently discovered that I'm not parallelizing my cURL connections and so performing several successive connection results in extreme latency for the end-user.

I've not used curl_multi before and I'm kind of at a loss after reading through the documentation. How can I best refactor the following code to take advantage of curl_multi's parallelization?

EDIT: I forgot to mention that I open-sourced the API that's being used here. These are my own Directed Edge PHP bindings. So if you'd like, you can also have your help here merged into the code on GitHub and you'll be listed as a contributor.

Here's an example of what I'm doing in the client code:

 // Get 100 goal recommendations from Directed Edge
  $de = new DirectedEdgeRest();
  $item = "user".$uid;
  $limit = 100;
  $tags = "goal";
  $recommendedGoals = $de->getRecommended($item, $tags, $limit);

  // Get 100 interest recommendations from Directed Edge
  $de = new DirectedEdgeRest();
  $item = "user".$uid;
  $limit = 100;
  $tags = "interest";
  $recommendedInterests = $de->getRecommended($item, $tags, $limit);

And here are the relevant functions from DirectedEdgeRest()

  /**
   * Returns array of recommended result IDs for an item
   * @param string $item Item, e.g. "Miles%20Davis"
   * @param string $tags Tags as comma delimited string, e.g. "product,page"
   * @param int $limit Limit for max results
   *
   * @return array Recommended result IDs
   */
  public function getRecommended($item, $tags, $limit)
  {
    // Connect to Directed Edge and parse the returned XML
    $targeturl = self::buildURL($item, 'recommended', $tags, $limit, 'true');
    $response = self::getCurlResponse($targeturl);
    $xml = self::parseXML($response);

    // Iterate through the XML and place IDs into an array
    foreach($xml->item->recommended as $recommended) {
      $recommendedResults[] = filter_var($recommended, FILTER_SANITIZE_NUMBER_INT);
    }

    return $recommendedResults;
  }

  /**
   * Builds URL for cURL
   * @param string $item Item, e.g. "Miles%20Davis"
   * @param string $type Type of API request: either "related" or "recommended"
   * @param string $tags Tags as comma delimited string, e.g. "product,page"
   * @param int $limit Limit for max results
   * @param string $exclude "true" if you want to exclude linked, "false" otherwise
   *
   * @return string The target URL
   */
  private function buildURL($item, $type, $tags, $limit, $exclude)
  {
    $targeturl = DE_BASE_URL;
    $targeturl .= $item; // Item
    $targeturl .= "/" . $type; // Type
    $targeturl .= "?tags=" . $tags; // Tags
    $targeturl .= "&maxresults=" . $limit; // Limit
    $targeturl .= "&excludeLinked=" . $exclude; // Exclude
    return $targeturl;
  }

  /**
   * Returns the cURL response given a target URL
   * @param string $targeturl The target URL for cURL
   *
   * @return string cURL Response
   */
  private function getCurlResponse($targeturl)
  {
    $ch = curl_init($targeturl);
    curl_setopt($ch, CURLOPT_POST, FALSE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    $response = curl_exec($ch);
    curl_close($ch);
    return $response;
  }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

远昼 2024-10-10 07:59:01

在你提出问题之前我并不知道curl_multi,这是一个非常奇怪的(对于PHP)接口。

看起来有一个 curl_multi_init 文档中的 Hello World 示例

// create both cURL resources
$ch1 = curl_init();
$ch2 = curl_init();

// set URL and other appropriate options
curl_setopt($ch1, CURLOPT_URL, "http://www.example.com/");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/");
curl_setopt($ch2, CURLOPT_HEADER, 0);

//create the multiple cURL handle
$mh = curl_multi_init();

//add the two handles
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);

$running=null;
//execute the handles
do {
    usleep(10000);
    curl_multi_exec($mh,$running);
} while ($running > 0);

//close the handles
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);

我就从那开始。

I wasn't aware of curl_multi before your question, that's a pretty bizarre (for PHP) interface.

It looks like there's a Hello World example in the curl_multi_init documentation

// create both cURL resources
$ch1 = curl_init();
$ch2 = curl_init();

// set URL and other appropriate options
curl_setopt($ch1, CURLOPT_URL, "http://www.example.com/");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/");
curl_setopt($ch2, CURLOPT_HEADER, 0);

//create the multiple cURL handle
$mh = curl_multi_init();

//add the two handles
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);

$running=null;
//execute the handles
do {
    usleep(10000);
    curl_multi_exec($mh,$running);
} while ($running > 0);

//close the handles
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);

I'd start with that.

梅窗月明清似水 2024-10-10 07:59:01

如果有人感兴趣,以下是我如何重构代码以使用 curl_multi。如果这一切看起来很酷,或者如果你可以,请为绑定做出贡献做得更好!

class DirectedEdgeRest
{
  /**
   * Gets multiple simultaneous recommendations from Directed Edge
   * @param array $queryArray Array of the form array(0 => (array('item' => (string) $item, 'tags' => (string) $tags, 'limit' => (int) $limit))
   *
   * @return array Multi-dimensional array containing responses to
   *  queries in the order they were passed in the array
   */
  public function getMultiRecommended($queryArray)
  {
    $targetUrls = array();

    foreach($queryArray as $query) {
      $targeturl = self::buildURL($query['item'], 'recommended', $query['tags'], $query['limit'], 'true');
      $targetUrls[] = $targeturl;
    }

    $responses = self::getMultiCurlResponses($targetUrls);

    $xmlArray = array();

    foreach($responses as $response) {
      $xmlArray[] = self::parseXML($response);      
    }

    $count =  count($xmlArray);

    // Iterate through the XML and place IDs into an array
    for($i = 0; $i < $count; $i++) {            
      foreach($xmlArray[$i]->item->recommended as $recommended) {
        $recommendedResults[$i][] = filter_var($recommended, FILTER_SANITIZE_NUMBER_INT);
      }
    }

    return $recommendedResults;
  }

  /**
   * Returns the cURL responses given multiple target URLs
   * @param array $targetUrls Array of target URLs for cURL
   *
   * @return array cURL Responses
   */
  private function getMultiCurlResponses($targetUrls)
  {
    // Cache the count
    $count = count($targetUrls);

    // Create the multiple cURL handles
    for($i = 0; $i < $count; $i++) {
      $ch[$i] = curl_init($targetUrls[$i]);
      curl_setopt($ch[$i], CURLOPT_POST, FALSE);
      curl_setopt($ch[$i], CURLOPT_SSL_VERIFYPEER, FALSE);
      curl_setopt($ch[$i], CURLOPT_RETURNTRANSFER, TRUE);
    }

    // Initialize the multiple cURL handle
    $mh = curl_multi_init();

    // Add the handles to the curl_multi handle
    for($i = 0; $i < $count; $i++) {
      curl_multi_add_handle($mh, $ch[$i]);
    }

    $running=null;
    // Execute the handles
    do {
      curl_multi_exec($mh,$running);
    } while ($running > 0);

    $responses = array();

    // Remove the handles and return the response
    for($i = 0; $i < $count; $i++) {
      curl_multi_remove_handle($mh, $ch[$i]);

      $responses[$i] = curl_multi_getcontent($ch[$i]);
    }

    // Close the multiple cURL handle
    curl_multi_close($mh);

    return $responses;
  }
}

$uid = 3;
$de = new DirectedEdgeRest();
$query['item'] = "user".$uid;
$query['limit'] = 10;
$query['tags'] = "goal";
$queryArray[0] = $query;

$query['tags'] = "question";
$queryArray[1] = $query;


$recommended = $de->getMultiRecommended($queryArray);
echo '<pre>';
var_dump($recommended);

// Outputs...
array(2) {
  [0]=>
  array(10) {
    [0]=>
    string(3) "141"
    [1]=>
    string(2) "64"
    [2]=>
    string(2) "37"
    [3]=>
    string(2) "65"
    [4]=>
    string(2) "63"
    [5]=>
    string(1) "7"
    [6]=>
    string(2) "78"
    [7]=>
    string(1) "9"
    [8]=>
    string(2) "30"
    [9]=>
    string(2) "10"
  }
  [1]=>
  array(10) {
    [0]=>
    string(2) "97"
    [1]=>
    string(3) "125"
    [2]=>
    string(3) "133"
    [3]=>
    string(3) "127"
    [4]=>
    string(3) "101"
    [5]=>
    string(3) "134"
    [6]=>
    string(2) "69"
    [7]=>
    string(2) "80"
    [8]=>
    string(2) "19"
    [9]=>
    string(3) "129"
  }
}

In case anyone's interested, here's how I refactored the code to make use of curl_multi. And please, contribute to the bindings if this all seems cool, or if you can do a better job!

class DirectedEdgeRest
{
  /**
   * Gets multiple simultaneous recommendations from Directed Edge
   * @param array $queryArray Array of the form array(0 => (array('item' => (string) $item, 'tags' => (string) $tags, 'limit' => (int) $limit))
   *
   * @return array Multi-dimensional array containing responses to
   *  queries in the order they were passed in the array
   */
  public function getMultiRecommended($queryArray)
  {
    $targetUrls = array();

    foreach($queryArray as $query) {
      $targeturl = self::buildURL($query['item'], 'recommended', $query['tags'], $query['limit'], 'true');
      $targetUrls[] = $targeturl;
    }

    $responses = self::getMultiCurlResponses($targetUrls);

    $xmlArray = array();

    foreach($responses as $response) {
      $xmlArray[] = self::parseXML($response);      
    }

    $count =  count($xmlArray);

    // Iterate through the XML and place IDs into an array
    for($i = 0; $i < $count; $i++) {            
      foreach($xmlArray[$i]->item->recommended as $recommended) {
        $recommendedResults[$i][] = filter_var($recommended, FILTER_SANITIZE_NUMBER_INT);
      }
    }

    return $recommendedResults;
  }

  /**
   * Returns the cURL responses given multiple target URLs
   * @param array $targetUrls Array of target URLs for cURL
   *
   * @return array cURL Responses
   */
  private function getMultiCurlResponses($targetUrls)
  {
    // Cache the count
    $count = count($targetUrls);

    // Create the multiple cURL handles
    for($i = 0; $i < $count; $i++) {
      $ch[$i] = curl_init($targetUrls[$i]);
      curl_setopt($ch[$i], CURLOPT_POST, FALSE);
      curl_setopt($ch[$i], CURLOPT_SSL_VERIFYPEER, FALSE);
      curl_setopt($ch[$i], CURLOPT_RETURNTRANSFER, TRUE);
    }

    // Initialize the multiple cURL handle
    $mh = curl_multi_init();

    // Add the handles to the curl_multi handle
    for($i = 0; $i < $count; $i++) {
      curl_multi_add_handle($mh, $ch[$i]);
    }

    $running=null;
    // Execute the handles
    do {
      curl_multi_exec($mh,$running);
    } while ($running > 0);

    $responses = array();

    // Remove the handles and return the response
    for($i = 0; $i < $count; $i++) {
      curl_multi_remove_handle($mh, $ch[$i]);

      $responses[$i] = curl_multi_getcontent($ch[$i]);
    }

    // Close the multiple cURL handle
    curl_multi_close($mh);

    return $responses;
  }
}

$uid = 3;
$de = new DirectedEdgeRest();
$query['item'] = "user".$uid;
$query['limit'] = 10;
$query['tags'] = "goal";
$queryArray[0] = $query;

$query['tags'] = "question";
$queryArray[1] = $query;


$recommended = $de->getMultiRecommended($queryArray);
echo '<pre>';
var_dump($recommended);

// Outputs...
array(2) {
  [0]=>
  array(10) {
    [0]=>
    string(3) "141"
    [1]=>
    string(2) "64"
    [2]=>
    string(2) "37"
    [3]=>
    string(2) "65"
    [4]=>
    string(2) "63"
    [5]=>
    string(1) "7"
    [6]=>
    string(2) "78"
    [7]=>
    string(1) "9"
    [8]=>
    string(2) "30"
    [9]=>
    string(2) "10"
  }
  [1]=>
  array(10) {
    [0]=>
    string(2) "97"
    [1]=>
    string(3) "125"
    [2]=>
    string(3) "133"
    [3]=>
    string(3) "127"
    [4]=>
    string(3) "101"
    [5]=>
    string(3) "134"
    [6]=>
    string(2) "69"
    [7]=>
    string(2) "80"
    [8]=>
    string(2) "19"
    [9]=>
    string(3) "129"
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文