Длинный опрос зависает на всех других запросах сервера

У меня есть простой длинный запрос на опрос на моем веб-сайте, чтобы проверить, есть ли какие-либо новые уведомления, доступные для пользователя. Что касается запроса, все работает безупречно; запрос выполняется только после обновления базы данных (и для этого конкретного пользователя создано уведомление), и сразу после этого отправляется новый запрос.

Эта проблема


What I have noticed is that when the request is waiting for a response from the database (as long polls should), all other requests to the server will также hang with it - whether it be media files, АЯКС requests or even new pages loading. This means that все requests to the server will hang until I close my browser and reopen it.

Что еще более странно, так это то, что если я зайду на другой из моих localhost сайтов (мой длинный опрос проводится на сайте виртуального хоста MAMP, www.example.com), это не проблема, и я все еще могу использовать их, как будто ничего не произошло - несмотря на то, что они' re технически на том же сервере.

Мой код


Это то, что у меня есть на стороне клиента (longpoll.js):

window._Notification = {
    listen: function(){
        /* this is not jQuery's Ajax, if you're going to comment any suggestions, 
        *  please ensure you comment based on regular XMLHttpRequest's and avoid
        *  using any suggestions that use jQuery */
        xhr({
            url: "check_notifs.php",
            dataType: "json",
            success: function(res){
                /* this will log the correct response as soon as the server is 
                *  updated */
                console.info(res);
                _Notification.listen();
            }
        });
    }, init: function(){
        this.listen();
    }
}

/* after page load */
_Notification.init();

И это то, что у меня есть на стороне сервера (check_notifs.php):

header("Content-type: application/json;charset=utf-8", false);

if (/* user is logged in */){
    $_CHECKED = $user->get("last_checked");

    /* update the last time they checked their notifications */
    $_TIMESTAMP = time();
    $user->update("last_checked", $_TIMESTAMP);

    /* give the server a temporary break */
    sleep(1);

    /* here I am endlessly looping until the conditions are met, sleeping every
    *  iteration to reduce server stress */
    $_PDO = new PDO('...', '...', '...');
    while(true){
        $query = $_PDO->prepare("SELECT COUNT(*) as total FROM table WHERE timestamp > :unix");
        if ($query->execute([":unix" => $_CHECKED])){
            if ($query->rowCount()){
                /* check if the database has updated and if it has, break out of
                *  the while loop */
                $total = $query->fetchAll(PDO::FETCH_OBJ)[0]->total;
                if ($total > 0){
                    echo json_encode(["total" => $total]);
                    break;
                }
                /* if the database hasn't updated, sleep the script for one second,
                *  then check if it has updated again */
                sleep(1);
                continue;
            }
        }
    }
}   

/* for good measure */
exit;

Я читал о NodeJS и различных других фреймворках, которые предлагаются для длинных опросов, но, к сожалению, в настоящее время они мне недоступны, и я вынужден использовать PHP. Я также осмотрелся, чтобы посмотреть, может ли что-нибудь в конфигурации Apache решить мою проблему, но я наткнулся только на Как увеличить максимальное количество одновременных подключений в Apache?, и то, что упоминалось, не похоже на проблему, учитывая, что я все еще могу использовать свой другой localhost веб-сайт на сервер такой же.

Я действительно смущен тем, как я могу решить эту проблему, поэтому любая помощь приветствуется,
Cheers.

Ваша проблема, скорее всего, if ($total > 0){, вероятно, ничто не возвращает значение больше нуля, поэтому цикл никогда не заканчивается.

Get Off My Lawn 01.05.2019 17:36

Кроме того, ваш запрос всегда один и тот же в каждом цикле, поэтому цикл здесь кажется бесполезным, потому что вы всегда будете возвращать одни и те же данные. Чтобы изменить это, переместите элементы $_CHECKED, $_TIMESTAMP и $user->... в цикл while.

Get Off My Lawn 01.05.2019 17:39

@GetOffMyLawn смысл цикла в том, чтобы постоянно проверять, обновляется ли база данных :) Но проблема не в этом (сам длинный опрос работает правильно), проблема в том, что он зависает при каждом другом запросе к этому серверу [virtualhost?]

GROVER. 01.05.2019 17:46

Поднимите пожалуйста @Community

GROVER. 03.05.2019 13:09

Переместите $_PDO = new PDO('...', '...', '...'); за пределы while-цикла и посмотрите, сохраняется ли проблема.

huysentruitw 05.05.2019 14:52

@huysenruitw хорошо! единственная причина, по которой я это делаю, в первую очередь заключается в том, что я могу закрыть соединение, но не навсегда :) см. $_PDO = null;

GROVER. 05.05.2019 14:53

Почему вы удаляете соединение PDO и создаете новое на каждой итерации? Также я склонен думать, что это больше проблема конфигурации веб-сервера. У вас есть конкретная конфигурация Apache? Вы используете mod-php или php-fpm? Если FPM, пожалуйста, опубликуйте эту конфигурацию.

ferdynator 05.05.2019 14:53

Закройте соединение PDO после цикла while.

huysentruitw 05.05.2019 14:54

@ferdynator в настоящее время использую конфигурацию MAMP по умолчанию. Так что я предполагаю, что это будет mod_php :)

GROVER. 05.05.2019 14:55

IMO PHP-скрипты не должны так зависать... Утечка памяти, вероятно, будет огромной... Почему бы вам просто не запрашивать PHP каждые 10 секунд на стороне клиента, чтобы проверить, существуют ли новые уведомления?

Carlos Alves Jorge 06.05.2019 11:48

@CarlosAlvesJorge, потому что я пытаюсь избежать бессмысленных HTTP-запросов. По мнению значительной части сообщества программистов (ну, по крайней мере, из того, что я видел), длинный опрос намного эффективнее, чем короткий опрос.

GROVER. 06.05.2019 11:52

@ГРУВЕР. Это никогда не будет более эффективным, если у вас есть бесконечный цикл, запрашивающий вашу базу данных... По сути, вы сами будете DDNS всего с парой пользователей...

Carlos Alves Jorge 06.05.2019 11:57

@CarlosAlvesJorge, но это основная идея длительного опроса ... постоянная проверка того, была ли база данных обновлена ​​​​на стороне сервера

GROVER. 06.05.2019 11:58

@ГРУВЕР. Длинный пул используется для последовательной проверки изменений событий (которые в идеале не требуют больших ресурсов), чтобы использовать его для бесконечного выполнения «SELECT COUNT (*)» в базе данных — это призыв к катастрофе. Если вы не можете использовать веб-сокеты для уведомлений в реальном времени, просто выполните короткий пул на стороне клиента. Если вы хотите сохранить свой подход, по крайней мере, дайте гораздо больший тайм-аут, чем одна секунда...

Carlos Alves Jorge 06.05.2019 12:08

@CarlosAlvesJorge, как Facebook, Twitter и Instagram делали это в те дни, когда WebSockets не были доступны?

GROVER. 06.05.2019 12:09

Кроме того, контролируя клиентскую сторону, вы можете прекратить делать запросы, когда окно не сфокусировано или когда нет движения мыши дольше периода. Я бы хотел, чтобы люди постоянно пользовались моей базой данных, когда они забывают об открытии сеанса...

Carlos Alves Jorge 06.05.2019 12:10
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
16
247
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

На самом деле происходит то, что php ожидает завершения этого скрипта (заблокированного), чтобы обслуживать следующие запросы к тому же файлу.

Как вы можете прочитать здесь:

  • there is some lock somewhere -- which can happen, for instance, if the two requests come from the same client, and you are using file-based sessions in PHP : while a script is being executed, the session is "locked", which means the server/client will have to wait until the first request is finished (and the file unlocked) to be able to use the file to open the session for the second user.
  • the requests come from the same client AND the same browser; most browsers will queue the requests in this case, even when there is nothing server-side producing this behaviour.
  • there are more than MaxClients currently active processes -- see the quote from Apache's manual just before.

На самом деле где-то есть какой-то замок. Вам нужно проверить, какая блокировка происходит. Возможно, у $_PDO есть блокировка, и вы должны закрыть ее до sleep(1), чтобы она оставалась разблокированной, пока вы не сделаете следующий запрос.

Вы можете попробовать повысить свой MaxClients и/или применить этот ответ

Perform session_write_close() (or corresponding function in cakephp) to close the session in the begin of the ajax endpoint.

Где я должен разместить функцию session_write_close?

GROVER. 06.05.2019 11:54

@ГРУВЕР. В начале файла. Но имейте в виду, что это было потому, что в этом случае это был замок. В вашем случае вы должны искать свой замок. Вы используете сеансы?

Jorge Fuentes González 06.05.2019 12:11

Я использую сеансы :), но только для определенных пользовательских данных

GROVER. 06.05.2019 12:11

@ГРУВЕР. у вашего $user может быть блокировка сеанса. Попробуйте разблокировать после последнего $user->update или чего-то еще, что требует открытой сессии.

Jorge Fuentes González 06.05.2019 12:19

Поздравляю с репутацией 8K. ;)

GROVER. 08.05.2019 09:22

Другие вопросы по теме