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

Long polling with Ajax with sleep()/time_sleep_until() in while() loop

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

基本上,我希望后台的管理员用户在未来订单结束时(未来日期到达当前时间)立即收到警告。为了继续,我让用户管理员对服务器进行 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);
    })();

{{ 路径('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

,我设法克服了这种情况,但问题仍然存在。管理员确实收到了正确的通知。但是,我知道 Ajax 调用在请求发出后仍在继续。

我现在的问题是:这会导致服务器资源过载吗? 我愿意为此悬赏,因为我很想知道实现我想要的目标的最佳解决方案。

总的来说这个问题说起来有点粗糙。关于您的其他功能的确切功能,我们没有太多信息。喜欢 findNewestOrders...

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

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

根据您的评论

time_sleep_until

Returns TRUE on success or FALSE on failure.

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

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

长轮询对于许多函数来说可能是个好主意,但我不完全相信它确实如此。我会认真推荐 setInterval,因为服务器和客户端上的负载在每分钟或两分钟的 30 秒调用中不会那么大。终究是你的决定。

我个人会经常检查,而不是让一个请求运行很长时间 - 拥有像这样的 运行 长进程并不理想,因为它们会占用服务器连接,实际上,这只是一种不好的做法.此外,浏览器可能会超时连接,这就是为什么您可能看不到预期的响应的原因。

您是否尝试过更改 ajax 调用,例如每 60 秒调用一次(或者您希望的任何频率),检查自上次轮询以来的新订单(只需跟踪这在页面 / HTML5 本地存储中,因此它跨页面持续存在并将其作为参数传递到 ajax 请求中)然后简单地 returns 表示是的,有新订单,还是没有?

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

我终于克服了这个错误,但没有深入挖掘问题。

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

因此,在第一次 ajax 调用成功时,通过发出新的 ajax 请求来更新该字段,从而更新该字段。因此,管理员总是会收到通知。

我只需要在 memory/thread 级别查询即可了解此循环使用的资源消耗量。

尽管我解决了最初的错误,但仍未找到解决方案,我不会接受我的回答,因为问题仍然存在。

非常感谢您对这个问题的所有帮助。

我想我全错了。

长轮询 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、@CayceK 和@KevinB 的建议。