牛骨文教育服务平台(让学习变的简单)
博文笔记

PHP发起异步请求

创建时间:2017-04-15 投稿人: 浏览次数:1941

当我们要与第三方接口进行交互的时候,经常会使用到curl来调取接口。但是,我们会面临到一个问题,就是一个页面可能需要调取多个接口,这个时候,用curl的效率可能会有点低,因为是同步调取的。

如果能够可以实现异步调取接口,那是最好的。

这时,我发现了curl_multi函数,用这个函数可以实现异步调取接口。代码奉上:

function rolling_curl($urls, $callback, $custom_options = null) {

    // make sure the rolling window isn"t greater than the # of urls
    $rolling_window = 5;
    $rolling_window = (sizeof($urls) < $rolling_window) ? sizeof($urls) : $rolling_window;

    $master = curl_multi_init();
    $curl_arr = array();

    // add additional curl options here
    $std_options = array(CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_MAXREDIRS => 5);
    $options = ($custom_options) ? ($std_options + $custom_options) : $std_options;

    // start the first batch of requests
    for ($i = 0; $i < $rolling_window; $i++) {
        $ch = curl_init();
        $options[CURLOPT_URL] = $urls[$i];
        curl_setopt_array($ch,$options);
        curl_multi_add_handle($master, $ch);
    }

    do {
        while(($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM);
        if($execrun != CURLM_OK)
            break;
        // a request was just completed -- find out which one
        while($done = curl_multi_info_read($master)) {
            $info = curl_getinfo($done["handle"]);
            if ($info["http_code"] == 200)  {
                $output = curl_multi_getcontent($done["handle"]);

                // request successful.  process output using the callback function.
                $callback($output);

                // start a new request (it"s important to do this before removing the old one)
                $ch = curl_init();
                $options[CURLOPT_URL] = $urls[$i++];  // increment i
                curl_setopt_array($ch,$options);
                curl_multi_add_handle($master, $ch);

                // remove the curl handle that just completed
                curl_multi_remove_handle($master, $done["handle"]);
            } else {
                // request failed.  add error handling.
            }
        }
    } while ($running);
    
    curl_multi_close($master);
    return true;
}
自己亲测,服务器端接收请求的结果中返回时间戳,可以看到服务器端接收请求时的时间戳是一模一样的。

也就是说请求是同时到达的。


但是,踩坑开始了。

不知道为何,当我一次性发起请求的时候,有时候会出现很奇葩的情况,服务器端接口接收到的参数混乱了,即接口2接收到了接口1的参数。

这个问题找了很久,都没有找到解决办法。

于是,决定换代码。


于是我又发现了guzzleHttp这个东西。guzzle的介绍是这样的。http://guzzle-cn.readthedocs.io/zh_CN/latest/index.html


这正是我需要的。guzzle的使用很简单,不管是使用同步还是异步。

可是使用guzzle之后,上面的问题还是存在。不晓得是服务器端的问题还是php异步自身的问题。

于是想了个办法,当发现请求结果为空的时候,再次发起一次同步请求。(虽然效率不高的说,但是总得获取到数据啊)


奉上代码:

use GuzzleHttpClient;
use GuzzleHttpPromise;
use GuzzleHttpExceptionRequestException;
use MonologLogger;
use MonologHandlerStreamHandler;

function rolling_request($data) {
    $client = new Client();
    $logger = new Logger("name");
$logger_path = "test.log";
// Initiate each request but do not block $promises = []; $logger->pushHandler(new StreamHandler($log_path), Logger::INFO); $logger->addInfo("============================BEGIN REQUEST========================="); foreach($data as $key => $item) { $logger->addInfo("url: ".$item["url"]); $logger->addInfo("data:".json_encode($item["params"])); try { $promises[$key] = $client->requestAsync("POST", $item["url"], ["connect_timeout" => 6, "http_errors" => false, "form_params" => $item["params"]]); $promises[$key]->then( function (ResponseInterface $res) { }, function (RequestException $e) { } ); } catch (RequestException $e) { //ToDO: 异常处理 $logger->addError("RequestException: ".$e->getMessage()); } } // Wait on all of the requests to complete. try { $results = Promiseunwrap($promises); } catch (RequestException $e) { //ToDO: 异常处理 $logger->addError("RequestException: ".$e->getMessage()); } $return = []; if($results) { foreach($results as $key => $value) { $content = $value->getBody()->getContents(); //STANGE:不知道为什么,发起异步请求的时候,服务器端接收到的参数有时候会对不上,为了避免这种情况,多做一个判断 // 当返回结果为空的时候,再发起一个同步请求 if($content && $content["code"] == "SUCCESS" && empty($content["result"])) { //echo "---又有空的东西啦---<br />"; $logger->addWarning("content:".$key.": 异步请求失败,将此请求转换成同步请求"); //TODO: 另外写一个同步请求的程序,重新请求 } else { $return[$key] = $content; } } } $logger->addInfo("============================END MULTI REQUEST==========================="); $logger->addInfo(""); //exit; return $return;}

为了测试同步和异步的区别,特意价格服务器端接口地址改为不可用的地址,超时都设置为6秒,一次性发起4个请求。

在同步的情况下,整个页面返回会消耗24秒多。

在异步的情况下,整个页面返回只需要消耗6秒多。


另,上面用到了Monolog来记录日志,在接口调试中一定要做好。



声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。