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


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

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

基本上,我希望在未来的订单结束时(未来的日期到达当前时间),后端的管理员用户会收到警告。为了继续,我让用户admin对服务器进行Ajax调用(一旦他们连接到admin),以检查是否有期货订单到达。这个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('command_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()功能有问题,因为while循环似乎仍在继续,即使用户切换了页面,导致字段notif在后台更新。

编辑2

根据我的回答,我已经设法克服了这种情况,但问题仍然存在。管理员确实正确地收到了通知。然而,我知道Ajax调用仍然在继续,因为请求已经发出。

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

我想我弄错了

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

  • 定期轮询

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);
            }
        });
    })();

{{path('command_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;
}

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

感谢@StevenChilds、@CayceK和@KevinB的友好建议。

一般来说,对于这个问题,说起来有点粗糙。我们没有太多关于你的其他功能的确切信息。比如findNewestOrders。。。

我们可以假设它提取了所有尚未由管理员完成的新订单,因此将被显示。然而,如果它只寻找完全相等的订单,它们将永远不会被填满。

从理论上讲,如果没有新的订单提交,这将永远持续下去。您对此没有时间限制,因此服务器可能会觉得while永远不会是false,并且执行时间超过了执行时间。

根据您的评论

时间_睡眠_直到

Returns TRUE on success or FALSE on failure.

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

我认为可能更明智的做法是考虑为此做一项CRON工作,并建立一个不完整订单的数据库来查询。CRON可以每分钟运行一次并填充数据库。在服务器上运行不会那么好,因为无论如何,它都可能不超过30秒。

对于许多功能来说,长时间轮询可能是个好主意,但我并不完全相信这是真的。我非常推荐setInterval,因为每隔一两分钟调用30秒,服务器和客户端的负载就不会那么大。这最终是你的决定。

我个人会经常检查,而不是让一个请求长时间运行——像这样长时间运行的进程并不理想,因为它们会占用服务器连接,实际上,这只是一种糟糕的做法。此外,浏览器可能会为连接计时,这就是为什么你可能看不到你期望的响应。

您是否尝试过更改ajax调用,使其每60秒调用一次(或者您希望的频率),检查自上次轮询以来的新订单(只需在page/HTML5本地存储中跟踪这一点,使其在页面之间保持不变,并将其作为参数传递到ajax请求中),然后简单地返回一个指示,即是否有新订单,或者是否没有?

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

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

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

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

我只需要查询内存/线程级别,就可以了解这个循环使用的资源消耗量。

由于没有找到任何解决方案,除了我针对最初错误的变通方法外,我不会接受我的答案,因为问题仍然存在。

非常感谢你在这个问题上提供的帮助。