【问题标题】:cURL request taking too long, something wrong with the code?cURL 请求耗时过长,代码有问题?
【发布时间】:2019-02-11 19:42:54
【问题描述】:

我一直在为游戏制作成员列表并从他的分数中获取一些数据。首先,我得到一个名称列表,然后将它们插入到我的数据库中,然后将它们提供给 cURL 以从 hiscores 中获取统计信息,然后将它们更新到我的数据库中。

问题似乎是当我发出 cURL 请求时,我设法在主机显示 503 错误(可能是由于最长执行时间)之前更新了大约 30 个名称。但是,我必须能够更新更多。我会说 100 是最低要求。

我已尝试优化代码,使其运行得更快并取得一些成功。我一次查询最多可以更新 30 人。

代码本身是否有问题,为什么需要这么长时间?下面是代码的 cURL 部分,它可能不是你见过的最漂亮的。我会假设 cURL 能够一次处理更多的数据,并且我之前有类似的解决方案,但数据库无法正常工作。原因可能是https吗?以前不需要,但现在需要。

<?php
$ch = curl_init();
if(isset($_POST['submit'])){ //check if form was submitted
$conn = new mysqli($servername, $username, $password, $dbname);
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    } 
            //get users
    $stmt = $conn->prepare("SELECT m.name, m.id, m.group_id, p.field_1, g.prefix, g.suffix FROM members m INNER JOIN pfields_content p ON m.id = p.id INNER JOIN groups g ON g.g_id = m.group_id WHERE
    m.group_id = 1
    ");
    $stmt->execute();
    $result = $stmt->get_result();

    while($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {

    // add new member ID to database
    $conn = new mysqli($servername, $username, $password, $dbname);
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    } 
    $stmt = $conn->prepare("INSERT IGNORE INTO `table` (`member_id`, `name`, `dname`) VALUES ('".$row['member_id']."', '".$row['name']."', '".$row['field_1']."')");
    $stmt->execute();

        // dname
        if($row['field_1'] != '' || $row['field_1'] != NULL) {

            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
            curl_setopt($ch, CURLOPT_URL, "https://secure.runescape.com/m=hiscore_oldschool/index_lite.ws?player=".$row['field_1']);
            curl_setopt($ch, CURLOPT_HEADER, 0);

            // grab HTML
            $data = curl_exec($ch);
            $array = array();
            $array = explode(',', $data);

            //formula
            if (!empty($array[15]) && (is_numeric($array[15]))) {
                $level = ((round($array[13]/2, 0, PHP_ROUND_HALF_DOWN)+$array[9]+$array[7])/4) + (($array[3]+$array[5])*0.325);
                $level = number_format($level, 2);
                // if valid name, update
                $conn = new mysqli($servername, $username, $password, $dbname);
                if ($conn->connect_error) {
                    die("Connection failed: " . $conn->connect_error);
                } 
                $stmt = $conn->prepare("UPDATE table SET  
                member_id = '".$row['id']."',
                name = '".$row['name']."',
                cb = '".$level."' WHERE member_id = ".$row['id']."");
                $stmt->execute();
                $conn->close();
            }}}}

【问题讨论】:

  • 那是一些看起来很疯狂的代码 - 新的数据库连接,循环中的准备好的语句......真的吗?
  • 您查看日志了吗?也许错误就在你身边,例如 max-executiontime/memory 等。
  • @RamRaider 就像我说的,绝不是漂亮。我只是在空闲时间编写代码,所以如果您有建议该怎么做,我会全力以赴
  • @johnSmith 我还没有这样做,但我想我需要看看

标签: php mysql database curl


【解决方案1】:

好的,看到了一些值得一提的东西:

1) 为什么你只能做这么多?这是最可能的罪魁祸首:

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
        curl_setopt($ch, CURLOPT_URL, "https://secure.runescape.com/m=hiscore_oldschool/index_lite.ws?player=".$row['field_1']);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        // grab HTML
        $data = curl_exec($ch);

您正在为每个站点进行外部 curl 调用,这意味着您将受制于其他站点以及解决调用需要多长时间。您可以在 curl 调用周围添加一些回声,以查看每个调用的时间。但是,遗憾的是,您可能无法从代码中获得更多速度,因为您依赖于外部进程。这可能是因为 https,或者只是他们的系统过载。就像我上面说的,如果您真的想知道每个需要多长时间,请在其周围添加一些回声,例如:

        echo "About to curl runescape " . date("H:i:s");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
        curl_setopt($ch, CURLOPT_URL, "https://secure.runescape.com/m=hiscore_oldschool/index_lite.ws?player=".$row['field_1']);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        // grab HTML
        $data = curl_exec($ch);
        echo "Done with call to runescape " . date("H:i:s");

您的代码的其余部分在速度方面似乎不是问题。但是:

2) 你的连接有点乱。您打开一个连接,然后进行查询。然后一段时间开始,您打开第二个连接并进行查询。然后,如果满足正确的条件,您打开第三个连接并做一些工作,然后关闭它。最初的 2 个连接永远不会关闭,而第二个连接实际上在您的循环中被打开了多次。为什么不重复使用原来的 $conn 而不是每次都打开一个新连接?

3) 最后,如果您需要让您的 php 文件运行超过 60 秒,请在顶部添加如下内容:

set_time_limit(0);

以上内容应该可以有效地让脚本随心所欲地运行。不过,像上面这样的东西在 CLI 上作为 cronjob 运行比通过浏览器长时间运行的脚本要好得多。

【讨论】:

  • 谢谢,我很欣赏这种反馈。我感觉连接搞砸了,而且有很多不必要的连接,所以这肯定是我需要清理的东西。什么来卷曲,我需要看看回声,看看它需要多长时间。 Cronjob 是我研究过的东西,但还没有尝试这样做。
  • 创建第一个连接 ($conn) 后,您可以为所有查询重复使用相同的连接。每一个都是与数据库的开放连接并消耗资源(内存、打开的文件等)。
  • 另外,我更新了我的答案,以举例说明 curl 调用前后的打印时间......
  • 谢谢,我将重做连接并删除不必要的连接,看看是否有任何效果。我确实尝试了回声,13 次通话大约需要 40 秒,所以每次通话大约需要 3 秒。这听起来很正常,也可以接受,至少在我看来是这样。
  • 每次通话 3 秒听起来并不过分。这使得 30 次调用大约需要 90 秒,对于超时来说也不算太离谱。
【解决方案2】:

其他人似乎做得很好,可以弄清楚为什么代码这么慢(您正在执行一堆 cURL 请求,每个请求都需要时间),以及代码的其他一些问题(您的缩进被弄乱了)向上;我没有深入挖掘,抱歉)。

如何解决性能问题?

这里的答案取决于您的需求:您需要将处理后的数据发送回原始请求者,还是仅将其保存到数据库中?

如果您只是将其保存到数据库中:

执行您的数据库查找以及您需要执行的所有操作除了 cURL 请求,然后生成一个单独的系统进程,该进程将在您执行所有 cURL 请求(并将数据保存到数据库)时异步执行发回“好的,我们正在处理”回复。

如果您需要将此数据发送回调用方:

同时执行所有 cURL 请求我实际上并不认为这可以在 PHP 中完成(请参阅下面的 curl_multi)。在其他一些语言中,这很容易。最暴力的方法是为每个 cURL 请求分离一个异步系统进程,并将 PHP 置于睡眠/检查循环中,直到它看到所有子进程都已将其结果写入数据库。

当您开始使用异步的东西时,您会遇到很多进一步的问题,而且完全不清楚您是否以最佳方式解决问题。也就是说,如果你走这条路,我认为你需要的第一个函数是exec。例如,这会产生一个独立的异步进程,它将永远向虚无中喊叫(实际上不要这样做):

exec('yes > /dev/null &')

最后,我自己的议程:这是一个很好的机会,让您将一些执行从 PHP 中移出!虽然您可能只需使用 curl_multi 就可以完成所需的一切,甚至还有一些 bypassing cURL and building your own HTTP requests 的选项,但我建议使用更适合手头任务的工具。

【讨论】:

    【解决方案3】:

    我研究了您的代码并尝试对其进行重组,以便更好地利用数据库连接和 curl 请求。由于 curl 请求的目标 url 通过 HTTPS,我修改了 curl 选项以包含证书信息和其他一些可能需要也可能不需要的修改 - 我无法完全测试此代码,因此可能会出现错误!

    • 初始查询不必是prepared statement,因为它不使用任何用户提供的数据,因此很安全。
    • 使用prepared statements 时,只创建一次(所以不在循环中),如果语句创建正常,则将占位符绑定到变量。在那个阶段,变量不需要实际存在(至少在使用 mysqli 时 - 在 PDO 中不同)
    • 只创建一个数据库连接 - 糟糕的数据库服务器试图在一个循环中创建新连接,因此可能会因此受到影响。
    • 运行语句后,应将其处理掉,以便创建新语句。
    • 如果您使用 prepared statements,请不要在 sql 中嵌入变量(我知道在这种情况下不是用户输入)来破坏数据库 - 使用占位符作为参数!

    我希望以下内容有所帮助...我能够使用随机名称进行一些测试,而不使用任何数据库调用 ~ 6 个用户在 5 秒内


    <?php
    
        try{
    
            $start=time();
            $cacert='c:/wwwroot/cacert.pem'; # <-------edit as appropriate
            $baseurl='https://secure.runescape.com/m=hiscore_oldschool/index_lite.ws';
    
            if( isset( $_POST['submit'], $servername, $username, $password, $dbname ) ){
    
                /* should only need the one curl connection */
                $curl=curl_init();
                curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
                curl_setopt( $curl, CURLOPT_BINARYTRANSFER, true );
                curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true );
                curl_setopt( $curl, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)" );
                curl_setopt( $curl, CURLOPT_HEADER, false );
                curl_setopt( $curl, CURLINFO_HEADER_OUT, false );
                curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, true );
                curl_setopt( $curl, CURLOPT_SSL_VERIFYHOST, 2 );
                curl_setopt( $curl, CURLOPT_CAINFO, $cacert );
                curl_setopt( $curl, CURLOPT_MAXREDIRS, 10 );
                curl_setopt( $curl, CURLOPT_ENCODING, '' );
    
    
                /* only need the one db connection */
                $conn = new mysqli( $servername, $username, $password, $dbname );
    
                /* initial db query does not need to be a prepared statement as there are no user supplied parameters */
                $sql='select m.`name`, m.`id`, m.`group_id`, p.`field_1`, g.`prefix`, g.`suffix`
                        from members m 
                        inner join pfields_content p on m.`id` = p.`id`
                        inner join groups g on g.`g_id` = m.`group_id`
                        where m.`group_id` = 1';
                $res=$conn->query( $sql );
                if( $res ){
    
                    /* create the prepared statement for inserts ONCE, outside the loop */
                    $sql='insert ignore into `table` ( `member_id`, `name`, `dname` ) values ( ?,?,? )';
                    $stmt=$conn->prepare( $sql );
    
                    if( $stmt ){
    
                        /* bind the placeholders to variables - the variables do not need to exist YET in mysqli */
                        $stmt->bind_param('iss', $id, $name, $field_1 );
    
                        /* placeholder arrays for bits of the recordset */
                        $data=array();
                        $urls=array();
    
                        /* 
                            collect all the relevant player names into an array
                            and store info for use in INSERT query
                        */
                        while( $rs=$res->fetch_object() ){
    
                            if( !empty( $rs->field_1 ) ) {
                                $urls[ $rs->field_1 ]=(object)array( 
                                    'name'  =>  $rs->name,
                                    'id'    =>  $rs->id
                                );
                            }
    
                            $data[]=array( 
                                'name'      =>  $rs->name,
                                'id'        =>  $rs->id,    /* original code references `member_id` which does not exist in the recordset */
                                'field_1'   =>  $rs->field_1
                            );
                        }
    
                        /* now loop through $data to do the inserts */
                        foreach( $data as $obj ){
                            /* create/dimension the variables for the prepared statement parameters */
                            $name=$obj->name;
                            $id=$obj->id;
                            $field_1=$obj->field_1;
    
                            /* run the insert cmd */
                            $stmt->execute();
                        }
    
                        /* we should now be finished with the initial prepared statement */
                        $stmt->free_result();
                        $stmt->close();
    
                        /*
                            now for the curl calls... no idea how many there will be but this should be known
                            by sizeof( $urls )
    
                            Dependant upon the number you might opt to perform the curl calls in chunks or use
                            `curl_multi_init` ~ more complicated but perhaps could help.
    
                            Also need to define a new sql statement ~ which sort of does not make sense as it was
                            ~ do not need to update the `member_id`!
                        */
                        $sql='update `table` set `name`=?, `cb`=? where `member_id`=?';
                        $stmt=$conn->prepare( $sql );
                        if( $stmt ){
                            $stmt->bind_param( 'ssi', $name, $level, $id );
    
                            foreach( $urls as $player => $obj ){
    
                                $url = $baseurl . '?player=' . $player;
    
                                /* set the url for curl */
                                curl_setopt( $curl, CURLOPT_URL, $url );
    
                                /* execute the curl request... */
                                $results=curl_exec( $curl );
                                $info=(object)curl_getinfo( $curl );
                                $errors=curl_error( $curl );
    
                                if( $info->http_code==200 ){
                                    /* curl request was successful */
                                    $array=explode( ',', $results );
                                    if( !empty( $array[15] ) && is_numeric( $array[15] ) ) {
    
                                        $level = ((round($array[13]/2, 0, PHP_ROUND_HALF_DOWN)+$array[9]+$array[7])/4) + (($array[3]+$array[5])*0.325);
                                        $level = number_format($level, 2);
    
                                        /* update db ~ use $obj from urls array + level defined above */
                                        $name=$obj->name;
                                        $id=$obj->id;
    
                                        $stmt->execute();
                                    }
                                } else {
                                    throw new Exception( sprintf('curl request to %s failed with status %s', $url, $info->http_code ) );
                                }
                            }// end loop
    
                            $stmt->free_result();
                            $stmt->close();
                            curl_close( $curl );
    
                            printf( 'Finished...Operation took %ss',( time() - $start ) );
    
                        }else{
                            throw new Exception( 'Failed to prepare sql statement for UPDATE' );
                        }
                    }else{
                        throw new Exception( 'Failed to prepare sql statement for INSERT' );
                    }
                }else{
                    throw new Exception( 'Initial query returned no results' );
                }
            }
        }catch( Exception $e ){
            exit( $e->getMessage() );
        }
    ?>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-31
      • 2013-02-04
      • 1970-01-01
      相关资源
      最近更新 更多