php - 使用 Sessions 在 Web 应用程序中处理又长又耗时的 ajax 调用

标签 php web timeout single-page-application background-process

我非常希望重新考虑我正在实现的这种方法,以处理网络应用程序中很长的流程。

问题

我有一个网络应用程序,全部用 javascript 编写,它通过 API 与服务器通信。该应用程序有一些“批量操作”,需要花费大量时间才能执行。我想以安全的方式执行它们,确保服务器不会超时,并向用户提供丰富的反馈,以便他/她知道发生了什么。

通常的方法

正如我在研究中看到的,推荐的方法是在服务器中触发后台进程,并让它在某处写入其运行情况,以便您可以发出请求来检查它并向用户提供反馈。由于我在后端使用 php,因此该方法或多或少是此处描述的:http://humblecontributions.blogspot.com.br/2012/12/how-to-run-php-process-in-background.html

添加一些必需条件

由于我正在开发一个开源项目(一个 WordPress 插件),我希望它能够在各种情况和环境中工作。我不想添加服务器端要求,据我所知,后台进程方法可能不适用于多个共享托管解决方案。

我希望它能够在(几乎)任何具有典型 WordPress 支持的服务器中开箱即用,即使它最终成为速度较慢的解决方案。

我的方法

这个想法是通过许多小请求增量运行来打破这个过程。

因此,浏览器第一次发送运行该进程的请求时,它只会运行其中的一小步,并返回有用的信息以给用户一些反馈。然后浏览器发出另一个请求,并重复该请求,直到服务器通知该过程已完成。

为了做到这一点,我会将此对象存储在 session 中,因此第一个请求将给我一个 id,后续请求将将此 id 发送到服务器,以便服务器操作同一个对象。

这是一个概念示例:

class LongProcess {

    function __construct() {

        $this->id = uniqid();
        $_SESSION[$this->id] = $this;
        $this->step = 1;
        $this->total = 100;

    }


    function run() {
        // do stuff based on the step you are in
        $this->step = $this->step + 10;
        if ($this->step >= $this->total)
            return -1;
        return $this->step;
    }

}

function ajax_callback() {

    session_start();

    if (!isset($_POST['id']) || empty($_POST['id'])) {
        $object = new LongProcess();
    } else {
        $object = $_SESSION[$_POST['id']];
    }

    $step = $object->run();

    echo json_encode([
        'id' => $object->id,
        'step' => $return,
        'total' => $object->total
    ]);

}

有了这个,我可以让我的客户端递归地发送请求,并在收到响应时更新给用户的反馈。

    function recursively_ajax(session_id)
    {
        $.ajax({
            type:"POST",
            async:false, // set async false to wait for previous response
            url: "xxx-ajax.php",
            dataType:"json",
            data:{
                action: 'bulk_edit',
                id: session_id
            },
            success: function(data)
            {
                updateFeedback(data);
                if(data.step != -1){
                    recursively_ajax(data.id);
                } else {
                    updateFeedback('finish');
                }
            }
        });
    }  

    $('#button').click(function() {
        recursively_ajax(); 
    });

当然这只是一个概念证明,我什至没有在实际代码中使用 jQuery。这只是为了表达这个想法。

请注意,存储在 session 中的这个对象应该是一个非常轻量级的对象。正在处理的任何实际数据都应存储在数据库或文件系统中,并且仅在对象中引用它,以便它知道在哪里查找内容。

一个典型的案例是处理大型 CSV 文件。该文件将存储在文件系统中,并且该对象将存储指向最后处理的行的指针,以便它知道在下一个请求中从哪里开始。

该对象还可能返回更详细的日志,描述已完成的所有操作并报告错误,以便用户完全了解已完成的操作。

我认为很棒的界面是一个带有“查看详细信息”按钮的进度条,该按钮将打开一个包含详细日志的文本区域。

有意义吗?

所以现在我问。看起来怎么样?这是一个可行的方法吗?

有没有更好的方法来做到这一点并确保它可以在非常有限的服务器上工作?

最佳答案

您的方法有几个缺点:

  1. 您的繁重请求可能会阻止其他请求。通常,处理 Web 请求的并发 PHP 进程会受到限制。如果限制为 10,并且所有槽位都被处理繁重的请求占用,则您的网站将无法工作,直到其中一些请求完成释放槽位以供另一个轻量级请求使用。

  2. 您(可能)无法估计完成一个步骤需要多长时间。根据服务器负载,可能需要 5 或 50 秒。 50 秒可能会超出大多数共享主机的时间执行限制。

  3. 此任务将由客户端控制 - 客户端的任何中断(网络问题、关闭浏览器选项卡)都会中断该任务。

  4. 根据 session 后端,使用 session 存储当前状态可能会导致竞争条件错误 - 来自同一客户端的并发请求可能会覆盖后台任务在 session 中所做的更改。默认情况下,PHP 对 session 使用锁定,因此不应该是这种情况,但如果有人在没有锁定的情况下使用替代后端 session (DB、redis),这将导致严重且难以调试的错误。

这里有一个明显的权衡。对于优先考虑简化安装和配置的小型网站,您的方法是可以的。在任何其他情况下,我都会坚持使用简单的基于 cron 的队列在后台运行任务,并仅使用 AJAX 请求来检索任务的当前状态。到目前为止,我还没有见过没有 cron 支持的托管,并且向 cron 添加任务对于最终用户来说应该不会那么困难(有适当的文档)。

在这两种情况下,我都不会使用 session 作为存储。将任务及其状态保存在数据库中,并使用某种锁定系统来确保只有一个进程可以修改一项任务的数据。这确实比使用 session 更加健壮和灵活。

关于php - 使用 Sessions 在 Web 应用程序中处理又长又耗时的 ajax 调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50435102/

相关文章:

perl - 如何清理仍在使用的 HTTP::Async

php - 在后端模块中获取记录本地化

php - 将更改的变量分配给同一变量是否安全?

django - Django 应该用于大型、复杂的网站吗?

java - 网站移动兼容性

laravel - Composer 检测到您的平台存在问题 : Your Composer dependencies require a PHP version ">= 8.0.0"

batch-file - 如何使 bat 文件在 x 秒内未完成时自行中止

jquery - 延迟后返回上一页

php - SelectionGroup 不添加额外的属性

PHPExcel - 它可以像从数据库中获取一样逐行写入吗