PHP服务器是否有可能同时接收多个AJAX响应?

6

我想在我的网站上制作一个进度条,用于跟踪PHP脚本的执行。

PHP脚本与Google API进行多次连接,并将接收到的数据存储在数据库中。有时这个过程需要一分钟。

PHP脚本位于ajax/integrations-ajax.php文件中,并通过发送GET AJAX请求来启动,如果在网站上单击#link按钮。以下是请求的jQuery代码:

$('#link').on('click', function () {
    var interval = setInterval(trackStatus, 1000);
    $.getJSON('ajax/integrations-ajax.php', {action: 'link'}).done(function (json) {            
        if (json.result == true) {
            showMessage('The account is successfully linked.', 'success');
        } else {
            showMessage('There is an error happened.', 'danger');
        }
    })
});

这个 #link 按钮,还设置了一个间隔,每秒触发一次 trackStatus 函数:

function trackStatus() {
    $.getJSON('ajax/status-ajax.php', { 
        action: 'status'
    }).done(function (json) {
        console.log(json.status);
    });
}

如您所见,trackStatus函数发送GET AJAX请求到ajax/status-ajax.php文件,应该每秒在浏览器控制台中显示状态。
为了在服务器上实现跟踪功能,我编写了PHP脚本,存储了状态信息到数据库中,在ajax/integrations-ajax.php文件中可以看到其代码:
<?php
if(!is_ajax_request()) { exit; }
$action = isset($_GET['action']) ? (string) $_GET['action'] : '';
if ($action == 'link') {
    set_status_in_database(0);
    // some execution code;

    set_status_in_database(1);
    // some execution code;

    set_status_in_database(2);
    // some execution code;

    set_status_in_database(3);
    // some execution code;
    echo json_encode(['result' => true ]);
}

我创建了另一个名为axax/status-ajax.php的PHP文件,它可以从数据库中恢复状态:

<?php
if(!is_ajax_request()) { exit; }
$action = isset($_GET['action']) ? (string) $_GET['action'] : '';
if ($action == 'status') {
    $return['result'] = get_status_from_database();
    echo json_encode($return);
}

但是这些请求似乎无法同时工作。在没有接收到完成ajax/integrations-ajax.php脚本的响应之前,我无法收到trackStatus函数的响应。
我在浏览器中进行了分析记录,结果如下: enter image description here 那么,有可能同时执行请求吗?或者为了实现跟踪功能,我需要重新考虑整个方法?
提前感谢您的帮助!
更新: 感谢大家的建议!特别感谢@Keith,因为他的解决方案最简单且有效。我已经在脚本开头放置了session_write_close()函数,一切正常:
<?php
if(!is_ajax_request()) { exit; }
$action = isset($_GET['action']) ? (string) $_GET['action'] : '';
if ($action == 'link') {
    session_write_close();
    set_status_in_database(0);
    // some execution code;

    set_status_in_database(1);
    // some execution code;

    set_status_in_database(2);
    // some execution code;

    set_status_in_database(3);
    // some execution code;
    echo json_encode(['result' => true ]);
}

这里是浏览器的性能记录: enter image description here

1
我不是PHP大师,所以可能有其他人有更好的解决方案。但是PHP会对会话数据放置独占锁,因此这可能会使您的Ajax请求串行化。尝试尽快调用session_write_close,这将释放锁定。http://php.net/manual/en/function.session-write-close.php - Keith
1
简言之,你可以使用JavaScript同时运行和接收请求,我认为问题在于您的PHP服务被锁定了,就像@Keith提到的那样。 - Joelgullander
1
OP并不依赖于PHP会话,而是依靠数据库。 - Amjo
1
可能需要查看 https://www.htmlgoodies.com/beyond/php/show-progress-report-for-long-running-php-scripts.html 。 - Twisty
1
谢谢@Keith!你的建议很有用!我把session_write_close()放在代码开头,一切都正常了! - Andrew Shaban
显示剩余3条评论
4个回答

4
虽然PHP可以处理并发请求,但一个被序列化的领域是Session。基本上,在请求期间,PHP会为该用户在SESSION上放置一个独占锁定。也就是说:当这个锁定被使用时,同一用户的其他请求将不得不等待。通常这不是问题,但如果您有长时间运行的请求,它会阻塞其他请求,如AJax请求等。
默认情况下,PHP会在请求结束时写入会话数据。但是,如果您确定不再需要编写任何会话数据,则调用session_write_close将更快地释放锁定。
更多信息请参见-> http://php.net/manual/en/function.session-write-close.php

谢谢@Keith!你的解决方案是最简单且有效的。我已经在脚本开头放置了session_write_close()函数,现在一切都正常了。你可以从浏览器中查看性能记录:截图 - Andrew Shaban

2

建议尝试使用 EventSource。以下是一个示例:

PHP

<?php
header('Content-Type: text/event-stream');
// recommended to prevent caching of event data.
header('Cache-Control: no-cache'); 

function send_message($id, $message, $progress) {
  $d = array('message' => $message , 'progress' => $progress);
  echo "id: $id" . PHP_EOL;
  echo "data: " . json_encode($d) . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}

for($i=0; $i<4; $i++){
  set_status_in_database($i);
  // some execution code;
  send_message($i, "set status in database " . $i + 1 . " of 3' , $i*4);
  sleep(1);
}

send_message('CLOSE', 'Process complete');
?>

JavaScript

var es;

function startTask() {
  es = new eventSource('ajax/status-ajax.php');
  es.addEventListener('message', function(e) {
    var result = JSON.parse(e.data);
    console.log(result.message);
    if(e.lastEventId == 'CLOSE') {
      console.log('Received CLOSE closing');
      es.close();
      showMessage('The account is successfully linked.', 'success');
    } else {
      $('.progress').css("width", result.progress + '%');
    }
  });
  es.addEventListener('error', function(e) {
    console.log('Error occurred', e);
    es.close();
  });
}

function stopTask() {
  es.close();
  console.log('Interrupted');
}

$('#link').on('click', function(e) {
  e.preventDefault();
  startTask($(this));
});

参考资料:

希望这对你有用。


1
PHP逻辑和JavaScript语法都似乎很好;但是,由于最少量的PHP代码示例被认为会消耗资源。MySQL可能很忙,这就是为什么“get status”可能会等待MySQL的原因。
我已经通过将“更新状态”写入文件而不是竞争数据库资源来解决了这个问题。

0

既然您考虑采用不同的方法,让我推荐GraphQL作为您的数据库上方的薄层/ API。

有很多Php解决方案可供选择,例如Siler。寻找一个具有订阅功能的解决方案(并非所有解决方案都具备此功能),因为这将是您要寻找的功能。订阅用于创建websocket(在Php和Javascript之间的流),将所有与状态相关的通信减少到一个调用。

是的,这可能是“杀鸡焉用牛刀”,但也许您还有其他飞来飞去的东西,那么考虑一下可能是值得的。有一份精彩文档可以熟悉这个有趣的概念。您将能够在解析器函数中重复使用大部分与数据库相关的Php代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接