在while()循环中使用带有sleep()/ time_sleep_until()的Ajax进行长轮询

时间:2015-03-20 11:09:54

标签: php jquery mysql ajax symfony

我愿意设置一个长轮询Ajax调用来检查我的电子商务网络应用程序中的订单。在此应用程序中,客户能够在将来下订单的方式具有特殊性。因此,在管理面板中,我们有过去的订单和期货订单(将来可能是2个月或20分钟)。

基本上,我希望一旦未来的订单结束(未来的日期到达当前时间),就会警告后端的管理员用户。为了继续,我让用户admin执行Ajax调用(一旦他们连接到管理员)到服务器以检查要到达的期货订单。当调用等待服务器传递结果时,此Ajax调用是一个长轮询请求。如果服务器没有提供任何服务,则该请求将一直处于待处理状态,直到显示订单为止。

Ajax请求

    (function poll() {
        setTimeout(function() {
            $.ajax({
                url: '{{ path('commande_check') }}',
                method: 'post',
                success: function(r) {
                    if(r.ids) alert('New order!'); // I've simplified this part of the code to make it clean, admin are actually warned through Node.JS server
                },
                error: function() {},
                complete: poll
            });
        }, 5000);
    })();

{{path('commande_check')}} (由 Edit2 编辑)

public function checkAction(Request $request)
{

    if($request->isXmlHttpRequest())
    {
        $response = new Response();
        $em = $this->getDoctrine()->getManager();

        $ids = array();
        while(!$ids)
        {
            $ids = $em->getRepository('PaymentBundle:Commande')->findNewestOrders(new \DateTime());
            if($ids)
                break;
            else
                time_sleep_until(time() + self::SECONDS_TO_SLEEP);
        }

        if($ids)
        {
            return new JsonResponse(array(
                'ids' => $ids
            ));
        }
        $response->setStatusCode(404);
        return $response;
    }

    $response = new Response();
    $response->setStatusCode(405);
    return $response;
}

findNewestOrder()方法

public function findNewestOrders(\DateTime $datetime)
{
    $query = $this->createQueryBuilder('c')
                  ->select('c.id')
                  ->leftJoin('Kt\PaymentBundle\Entity\Paiement', 'p', \Doctrine\ORM\Query\Expr\Join::WITH, 'p.id = c.paiement')
                  ->andWhere('p.etat = 0')
                  ->where("DATE_FORMAT(c.date, '%Y-%m-%d %H:%i') = :date")
                  ->setParameter('date', $datetime->format('Y-m-d H:i'))
                  ->andWhere('c.kbis IS NULL')
                  ->andWhere('c.notif = 0')
                  ->getQuery();

    return $query->getArrayResult();
}

我的问题是警报有时永远不会显示,而数据库中的记录会更新。最奇怪的事情是它有时会发生,即使我离开页面进行Ajax调用就像它在后台运行一样。我认为问题来自time_sleep_until()函数。我试过sleep(self::SECOND_TO_SLEEP),但问题是一样的。

任何帮助都会很高兴。谢谢!

修改1

我感觉与connection_status()函数有关,因为即使用户已切换页面导致字段notif在后​​台更新,while循环似乎仍会继续。

修改2

As per my answer,我设法克服了这种情况,但问题仍然存在。管理员确实正确地收到了通知。但是,我知道Ajax调用仍在继续进行请求。

我现在的问题是:会导致服务器资源过载吗? 我愿意在这个上获得赏金,因为我渴望知道实现我想要的最佳解决方案。

4 个答案:

答案 0 :(得分:3)

我想我错了。

长时间轮询Ajax的目的是,只有一个连接保持打开状态,例如websockets(正如我所想的那样)。一个人必须提出几个请求,但比常规民意调查要少得多。

  • 定期投票

Ajax常规轮询的目的是每隔2或3秒向服务器发出一次请求,以获得实时通知的外观。这些会在一分钟内导致许多Ajax调用。

  • 长轮询

由于服务器等待要将新数据传递给浏览器,因此每分钟只需要发出最少数量的请求。由于我每分钟都在数据库中检查新订单,使用长轮询可以让我将每分钟的请求数减少到1

  • 在我的情况下

因此,应用程序的特殊性使得使用Ajax长轮询不必要。只要对特定分钟进行了MySQL查询,就不需要在同一分钟内再次运行查询。这意味着我可以以60000毫秒的间隔进行定期轮询。此处也不需要使用sleep()time_sleep_until()

这是我最终如何做到的:

JS投票功能

    (function poll() {
        $.ajax({
            url: '{{ path('commande_check') }}',
            method: 'post',
            success: function(r) {
                if(r.ids)
                    alert('New orders');
            },
            error: function() {},
            complete: function() {
                setTimeout(poll, 60000);
            }
        });
    })();

{{路径(' commande_check')}}

public function checkAction(Request $request)
{

    if($request->isXmlHttpRequest())
    {
        $em = $this->getDoctrine()->getManager();
        $ids = $em->getRepository('PaymentBundle:Commande')->findNewestOrders(new \DateTime());

        if($ids)
        {
            return new JsonResponse(array(
                'ids' => $ids
            ));
        }
        $response = new Response();
        $response->setStatusCode(404);
        return $response;
    }
    $response = new Response();
    $response->setStatusCode(405);
    return $response;
}

因此,我最终每分钟会收到一个检查新订单的请求。

感谢@SteveChilds,@ CherceK和@KevinB提出的善意建议。

答案 1 :(得分:1)

一般来说,这个问题有点粗糙。我们没有很多关于您的其他功能的信息。像findNewestOrders ...

我们可以假设它会提取管理员尚未完成的所有新订单,因此会显示。但是,如果它只查找完全相同的订单,它们将永远不会被填充。

理论上,如果没有提交新订单,这将永远运行。您没有时间限制,因此服务器可能感觉您遇到while永远不会false并执行超出执行时间的情况。

根据您的评论

time_sleep_until

Returns TRUE on success or FALSE on failure.

它失败的唯一方法是,如果函数本身失败或某些服务器端问题导致失败返回。由于您从未正式访问该页面,并且没有让您的ajax页面提交失败响应的行为,因此它永远不会真正失败。

我认为考虑为此做一个CRON工作可能更明智,并且有一个您查询的不完整订单的数据库。 CRON可以每分钟运行并填充数据库。服务器上的运行不会那么好,因为它很可能不会超过30秒。

对于许多功能而言,长轮询可能是一个好主意,但我并不完全相信它就是这种情况。我会认真推荐setInterval作为服务器上的负载,并且客户端在每分钟或每两分钟30秒的呼叫中不会那么好。这是你最后的电话。

答案 2 :(得分:1)

我个人会频繁检查,而不是有一个运行很长时间的请求 - 这不是很理想的长期运行这样的流程,因为它们会占用服务器连接,实际上,它只是不好的做法。此外,浏览器可能会将连接时间排除在外,这就是您可能看不到预期响应的原因。

您是否尝试过更改ajax调用,以便每隔60秒(或者经常需要)调用,检查自上次轮询以来的新订单(只需在页面/ HTML5中跟踪它)本地存储,因此它会持续跨页面并在ajax请求中作为参数传递它,然后只返回是否有新订单的指示,或者没有没有'

如果有新订单,您可以显示消息。

答案 3 :(得分:0)

我终于设法克服了这个错误,但没有深入研究这个问题。

我已将用于更新notif字段的代码与获取新订单的代码分开。这样,while循环仍然继续,但无法更新字段。

因此,通过发出新的ajax请求来更新字段,可以在第一个ajax调用成功时更新该字段。因此,管理员始终会收到通知。

我只需要查询内存/线程级别,看看这个循环使用了多少资源消耗。

由于没有找到任何解决方案,因为最初的错误导致了我的解决方法,因为问题仍然存在,我不会接受我的回答。

非常感谢有关该问题的所有帮助。