Как Facebook, Gmail отправляют уведомления в режиме реального времени?

269

Я прочитал несколько постов на эту тему, и ответы на них - комета, обратный ajax, потоковое вещание http, загрузка сервера и т. Д.

Как работает уведомление о входящей почте в Gmail?

Как GMail Chat может делать запросы AJAX без взаимодействия с клиентом?

Я хотел бы знать, есть ли какие-либо ссылки на код, которым я могу следовать, чтобы написать очень простой пример. Многие посты или сайты просто говорят о технологиях. Трудно найти полный пример кода. Кроме того, кажется, что для реализации кометы можно использовать много методов, например Hidden IFrame, XMLHttpRequest. На мой взгляд, использование XMLHttpRequest - лучший выбор. Что вы думаете о плюсах и минусах разных методов? Какой из них использует Gmail?

Я знаю, что это нужно делать как на стороне сервера, так и на стороне клиента. Есть ли пример кода PHP и Javascript?

Билли
источник

Ответы:

428

То, как Facebook делает это, довольно интересно.

Распространенным способом выполнения таких уведомлений является опрос сценария на сервере (с использованием AJAX) в течение заданного интервала (возможно, каждые несколько секунд), чтобы проверить, произошло ли что-то. Однако это может быть довольно интенсивно в сети, и вы часто делаете бессмысленные запросы, потому что ничего не произошло.

То, как Facebook делает это, использует кометный подход, а не опрашивает интервал, как только один опрос завершается, он выдает другой. Однако каждый запрос к сценарию на сервере имеет очень длительное время ожидания, и сервер отвечает на запрос только после того, как что-то произошло. Это может произойти, если вы откроете вкладку «Консоль» Firebug на Facebook, где запросы к сценарию могут занимать минуты. Это действительно гениально, поскольку этот метод сразу сокращает количество запросов и частоту их отправки. Теперь у вас есть структура событий, которая позволяет серверу «запускать» события.

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

С точки зрения современной технологии, AJAX - это путь, потому что вы можете контролировать время ожидания запроса и многое другое. Я бы порекомендовал (клише с переполнением стека) использовать JQuery для выполнения AJAX, это устранит множество проблем с совместимостью. С точки зрения PHP, вы можете просто опрашивать таблицу базы данных журнала событий в своем PHP-сценарии и возвращаться к клиенту только тогда, когда что-то происходит? Я ожидаю, что есть много способов реализовать это.

Реализация:

Сторона сервера:

Кажется, в PHP есть несколько реализаций кометных библиотек, но, честно говоря, это действительно очень просто, что-то вроде следующего псевдокода:

while(!has_event_happened()) {
   sleep(5);
}

echo json_encode(get_events());
  • Функция has_event_happened будет просто проверять, произошло ли что-нибудь в таблице событий или что-то в этом роде, а затем функция get_events выдаст список новых строк в таблице? Зависит от контекста проблемы действительно.

  • Не забудьте изменить максимальное время выполнения PHP, иначе время ожидания истечет!

Сторона клиента:

Взгляните на плагин jQuery для взаимодействия с Comet:

Тем не менее, плагин, кажется, добавляет немного сложности, он действительно очень прост для клиента, возможно (с jQuery) что-то вроде:

function doPoll() {
   $.get("events.php", {}, function(result) {
      $.each(result.events, function(event) { //iterate over the events
          //do something with your event
      });
      doPoll(); 
      //this effectively causes the poll to run again as
      //soon as the response comes back
   }, 'json'); 
}

$(document).ready(function() {
    $.ajaxSetup({
       timeout: 1000*60//set a global AJAX timeout of a minute
    });
    doPoll(); // do the first poll
});

Все зависит от того, как ваша существующая архитектура собрана воедино.

Алистер Эванс
источник
2
Это очень хорошее и подробное объяснение. Спасибо. У вас есть пример кода для одного из многих способов реализации этого?
Билли
45
Я думаю, что маркировка PHP как языка / платформы, которая плохо масштабируется, не обязательно соответствует действительности. Он может быть использован для разработки чрезвычайно крупных систем. Посмотри в фейсбуке. Если разработчик сделает это правильно, то он будет масштабироваться, если нет, то не будет. Использование конкретной веб-платформы не является гарантией масштабируемости. О, а также, вопрос действительно задал PHP.
Алистер Эванс
5
@Kazar: «Facebook использует PHP» немного вводит в заблуждение - в последний раз я слышал, что они разработали HipHop для явной цели преобразования PHP в C ++, поскольку PHP не работал достаточно хорошо.
Цао
14
@cHao: это справедливо, однако этот ответ был написан в 2009 году, до того, как Facebook начал использовать хип-хоп. В то время Facebook был все еще очень масштабной системой, использующей php самостоятельно.
Алистер Эванс
6
Таким образом, техника заключается в том, чтобы постоянно открывать соединение, что будет держать сервер в постоянном стрессе. Типичное количество одновременных подключений для среднего веб-сервера составляет около 200, но количество пользователей Facebook, которые одновременно находятся в сети, значительно больше. Как они это делают?
Пол
43

Обновить

Поскольку я продолжаю получать отзывы об этом, я думаю, что разумно помнить, что этому ответу 4 года. Веб развивался очень быстрыми темпами, поэтому, пожалуйста, помните об этом ответе.


У меня недавно была та же самая проблема и исследовал предмет.

Данное решение называется длинным опросом, и для его правильного использования вы должны быть уверены, что ваш AJAX-запрос имеет «большой» тайм-аут и всегда выполнять этот запрос после окончания текущего (тайм-аут, ошибка или успех).

Длинный опрос - Клиент

Здесь, для краткости кода, я буду использовать jQuery:

function pollTask() { 

    $.ajax({

        url: '/api/Polling',
        async: true,            // by default, it's async, but...
        dataType: 'json',       // or the dataType you are working with
        timeout: 10000,          // IMPORTANT! this is a 10 seconds timeout
        cache: false

    }).done(function (eventList) {  

       // Handle your data here
       var data;
       for (var eventName in eventList) {

            data = eventList[eventName];
            dispatcher.handle(eventName, data); // handle the `eventName` with `data`

       }

    }).always(pollTask);

}

Важно помнить, что (из документов jQuery ):

В jQuery 1.4.x и ниже объект XMLHttpRequest будет в недопустимом состоянии, если время ожидания истекло; доступ к любому члену объекта может вызвать исключение. Только в Firefox 3.0+ запросы на скрипты и JSONP не могут быть отменены тайм-аутом; скрипт будет работать, даже если он прибудет после истечения времени ожидания.

Длинный опрос - сервер

Это не на каком-то конкретном языке, но это будет примерно так:

function handleRequest () {  

     while (!anythingHappened() || hasTimedOut()) { sleep(2); }

     return events();

} 

Здесь hasTimedOutубедитесь, что ваш код не ждет вечно, и anythingHappenedпроверит, произошло ли какое-либо событие. Предназначен sleepдля освобождения вашей темы, чтобы делать другие вещи, пока ничего не происходит. eventsВозвращает словарь событий (или любой другой структуры данных , вы можете предпочесть) в формате JSON (или любой другой вы предпочитаете).

Это, безусловно, решает проблему, но, если вы обеспокоены масштабируемостью и производительностью, как я это делал при исследовании, вы могли бы рассмотреть другое решение, которое я нашел.

Решение

Используйте розетки!

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

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

Если вам не нравится это решение, попробуйте APE ( Ajax Push Engine ).

Надеюсь, я помог.

Вальтер Макамбира
источник
Как вы думаете, 1 является заменой другого или есть необходимость в обеих технологиях в одном проекте?
tq
Если вы имеете в виду APE и NodeJS, вы можете выбрать один из них. если вы имеете в виду периодические запросы AJAX и тот, который я предложил, моё решение может быть заменено на ajax, когда отсутствует поддержка сокетов (см. документацию по socket.io). В обоих случаях вам нужно только одно решение.
Вальтер Макамбира
Привет Уолтер, я хотел бы использовать ваше предложение на одном из моих сайтов. Вы знаете, где я могу получить сервер Sockets? Спасибо!
Progo
1
Вы можете реализовать это. Узел делает это действительно простым.
Вальтер Макамбира
Как обнаружить hasTimedOut()?
Мобашер Фасихи
18

Согласно слайд-шоу о системе обмена сообщениями Facebook, Facebook использует технологию комет для « передачи » сообщений в веб-браузеры. Кометный сервер Facebook построен на открытом веб-сервере Erlang mochiweb.

На рисунке ниже фраза «кластеры каналов» означает «серверы комет».

Системный Обзор

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

Вы можете попробовать icomet , кометный сервер C1000K C ++, созданный с libevent. icomet также предоставляет библиотеку JavaScript, ее легко использовать так же просто, как:

var comet = new iComet({
    sign_url: 'http://' + app_host + '/sign?obj=' + obj,
    sub_url: 'http://' + icomet_host + '/sub',
    callback: function(msg){
        // on server push
        alert(msg.content);
    }
});

icomet поддерживает широкий спектр браузеров и операционных систем, включая Safari (iOS, Mac), IE (Windows), Firefox, Chrome и т. д.

ideawu
источник
Это изображение очень хорошо описывает сценарий. Было бы здорово, если бы был приведен пример в действии. Например, что происходит, когда человек открывает (инициирует) чат с другом? Как Facebook настроиться на этот конкретный разговор и подтолкнуть сообщения в обоих концах? (всего лишь предположение: я могу только представить, что прикладная программа открывает сокет и связывает оба клиентских адреса, а затем просто продолжает слушать и писать всякий раз, когда сообщение пишется в коробке)
edam
5

Facebook использует MQTT вместо HTTP. Толчок лучше, чем опрос. Через HTTP нам нужно постоянно опрашивать сервер, но через MQTT-сервер отправляет сообщение клиентам.

Сравнение между MQTT и HTTP: http://www.youtube.com/watch?v=-KNPXPmx88E

Примечание: мои ответы лучше всего подходят для мобильных устройств.

абхи
источник
3
Кроме того, Google использует сервис GCM для Android, он может быть использован разработчиками для реализации службы push-сообщений. developer.android.com/google/gcm/index.html Пожалуйста, примите, если вы найдете ответ полезным.
Абхи
5

Одна важная проблема с длинным опросом - обработка ошибок. Есть два типа ошибок:

  1. Время ожидания запроса может истечь, и в этом случае клиент должен немедленно восстановить соединение. Это нормальное событие при длительном опросе, когда сообщения не поступили.

  2. Ошибка сети или ошибка выполнения. Это фактическая ошибка, которую клиент должен изящно принять и дождаться, пока сервер вернется в оперативный режим.

Основная проблема заключается в том, что если ваш обработчик ошибок немедленно восстанавливает соединение и для ошибки типа 2, клиенты будут DOS сервером.

Оба ответа с примером кода пропускают это.

function longPoll() { 
        var shouldDelay = false;

        $.ajax({
            url: 'poll.php',
            async: true,            // by default, it's async, but...
            dataType: 'json',       // or the dataType you are working with
            timeout: 10000,          // IMPORTANT! this is a 10 seconds timeout
            cache: false

        }).done(function (data, textStatus, jqXHR) {
             // do something with data...

        }).fail(function (jqXHR, textStatus, errorThrown ) {
            shouldDelay = textStatus !== "timeout";

        }).always(function() {
            // in case of network error. throttle otherwise we DOS ourselves. If it was a timeout, its normal operation. go again.
            var delay = shouldDelay ? 10000: 0;
            window.setTimeout(longPoll, delay);
        });
}
longPoll(); //fire first handler
Ronenz
источник