使用会话

时间:2018-05-20 13:01:12

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

我非常想对这种方法有第二个想法我正在实施处理Web应用程序中的很长的进程。

问题

我有一个Web应用程序,全部用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。这只是为了表达这个想法。

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

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

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

我认为很棒的界面是一个带有"的详细信息"使用此详细日志打开textarea的按钮。

有意义吗?

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

有没有更好的方法来确保它能在非常有限的服务器上运行?

2 个答案:

答案 0 :(得分:2)

您的方法有几个缺点:

  1. 您的大量请求可能会阻止其他请求。通常,您对处理Web请求的并发PHP进程有限制。如果限制为10,并且所有插槽都是通过处理您的繁重请求而获得的,那么在其中一些请求完成释放另一个轻量级请求的插槽之前,您的网站将无法工作。

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

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

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

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

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

答案 1 :(得分:0)

感谢所有输入。我只想在这里记录一些非常好的答案。

一些名为Woocommerce的WordPress插件已经合并了来自" WP后台处理"库,不再是mantained,但实现了Cron方法并带来了一些重要的改进。请参阅此博客文章:

https://deliciousbrains.com/background-processing-wordpress/

实际的图书馆住在这里:https://github.com/A5hleyRich/wp-background-processing

虽然这是一个特定于WordPress的库,但我认为这种方法适用于任何情况。

对于WordPress,还有一个名为Action Scheduler的库,它不仅可以在后台执行procprocses,还可以安排它们。值得一看:

https://github.com/Prospress/action-scheduler