W jaki sposób Facebook, Gmail wysyła powiadomienie w czasie rzeczywistym?


269

Przeczytałem kilka postów na ten temat, a odpowiedzi to kometa, odwrotna aukcja, strumieniowanie HTTP, push serwera itp.

Jak działa powiadomienie o przychodzącej poczcie w Gmailu?

W jaki sposób GMail Chat może wysyłać żądania AJAX bez interakcji z klientem?

Chciałbym wiedzieć, czy istnieją jakieś odwołania do kodu, które mogę śledzić, aby napisać bardzo prosty przykład. Wiele postów lub stron internetowych mówi tylko o technologii. Trudno jest znaleźć pełny przykładowy kod. Wydaje się również, że do implementacji komety można zastosować wiele metod, np. Hidden IFrame, XMLHttpRequest. Moim zdaniem użycie XMLHttpRequest jest lepszym wyborem. Co sądzisz o zaletach i wadach różnych metod? Z którego korzysta Gmail?

Wiem, że musi to robić zarówno po stronie serwera, jak i klienta. Czy jest jakiś przykładowy kod PHP i JavaScript?

Odpowiedzi:


428

Sposób, w jaki robi to Facebook, jest dość interesujący.

Powszechną metodą wykonywania takich powiadomień jest odpytywanie skryptu na serwerze (za pomocą AJAX) w danym przedziale czasowym (być może co kilka sekund), aby sprawdzić, czy coś się stało. Jednak może to być dość intensywne dla sieci i często wysyłasz bezcelowe żądania, ponieważ nic się nie wydarzyło.

Sposób, w jaki robi to Facebook, wykorzystuje podejście kometowe, zamiast sondować interwał, gdy tylko jedna ankieta się zakończy, wydaje kolejną. Jednak każde żądanie do skryptu na serwerze ma bardzo długi limit czasu, a serwer odpowiada na żądanie tylko wtedy, gdy coś się wydarzyło. Możesz to zobaczyć, jeśli otworzysz kartę Konsoli Firebuga na Facebooku, a prośby o skrypt mogą potrwać kilka minut. Jest to naprawdę genialne, ponieważ metoda ta natychmiast zmniejsza zarówno liczbę żądań, jak i częstotliwość ich wysyłania. Teraz efektywnie masz strukturę zdarzeń, która pozwala serwerowi „odpalać” zdarzenia.

Za tym, pod względem faktycznej zawartości zwracanej z tych ankiet, jest odpowiedź JSON, z czymś, co wydaje się być listą zdarzeń i informacjami o nich. Jest jednak zminimalizowany, więc jest trochę trudny do odczytania.

Jeśli chodzi o rzeczywistą technologię, AJAX to sposób, aby przejść tutaj, ponieważ możesz kontrolować limity czasu żądań i wiele innych rzeczy. Polecam (tutaj stos przelewu przepełnienia) przy użyciu jQuery do AJAX, zajmie to wiele problemów związanych z kompatybilnością. Jeśli chodzi o PHP, możesz po prostu sondować tabelę bazy danych dziennika zdarzeń w swoim skrypcie PHP i powrócić do klienta tylko wtedy, gdy coś się stanie? Oczekuję, że istnieje wiele sposobów realizacji tego.

Realizowanie:

Po stronie serwera:

Wydaje się, że jest kilka implementacji bibliotek komet w PHP, ale szczerze mówiąc, jest to naprawdę bardzo proste, być może coś w rodzaju następującego pseudokodu:

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

echo json_encode(get_events());
  • Funkcja has_event_happened sprawdziłaby tylko, czy coś się wydarzyło w tabeli zdarzeń lub coś, a następnie funkcja get_events zwróciłaby listę nowych wierszy w tabeli? Naprawdę zależy od kontekstu problemu.

  • Nie zapomnij zmienić maksymalnego czasu wykonywania PHP, w przeciwnym razie upłynie limit czasu wcześniej!

Strona klienta:

Spójrz na wtyczkę jQuery do interakcji z kometą:

To powiedziawszy, wtyczka wydaje się nieco bardziej złożona, jest naprawdę bardzo prosta na kliencie, być może (z jQuery) coś takiego:

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

Wszystko zależy w dużej mierze od tego, jak tworzona jest twoja architektura.


2
To bardzo ładne i szczegółowe wyjaśnienie. Dziękuję Ci. Czy masz jakiś przykładowy kod dla jednego z wielu sposobów, aby to zaimplementować?
Billy,

45
Myślę, że oznaczanie PHP jako języka / platformy, która nie skaluje się dobrze, niekoniecznie jest prawdą. Można go wykorzystać do opracowania systemów o bardzo dużej skali. Spójrz na facebooka. Jeśli programista zrobi to dobrze, skaluje się, jeśli nie, to nie będzie. Korzystanie z określonej platformy internetowej nie jest gwarancją skalowalności. No i pytanie dotyczyło PHP.
Alistair Evans,

5
@Kazar: „Facebook używa PHP” jest nieco mylące - ostatnio słyszałem, że opracowali HipHop w celu wyraźnej konwersji PHP na C ++, ponieważ PHP nie działało wystarczająco dobrze.
cHao

14
@cHao: To słuszna kwestia, jednak ta odpowiedź została napisana w 2009 roku, zanim Facebook zaczął używać hiphopu. W tym czasie Facebook był wciąż systemem na dużą skalę, który sam korzystał z php.
Alistair Evans

6
Tak więc techniką jest utrzymywanie stałego połączenia, które utrzyma serwer w ciągłym stresie. Typowa liczba równoczesnych połączeń dla przeciętnego serwera internetowego to około 200, ale liczba użytkowników Facebooka, którzy są jednocześnie online, jest znacznie większa. Jak oni to robią?
Paul,

43

Aktualizacja

Ponieważ nadal otrzymuję opinie na ten temat, myślę, że rozsądnie jest pamiętać, że ta odpowiedź ma 4 lata. Sieć rozwija się w naprawdę szybkim tempie, więc proszę uważaj na tę odpowiedź.


Ostatnio miałem ten sam problem i szukałem informacji na ten temat.

Podane rozwiązanie nazywa się długim odpytywaniem. Aby poprawnie z niego korzystać, należy upewnić się, że żądanie AJAX ma „duży” limit czasu i zawsze wysyłać to żądanie po bieżącym zakończeniu (limit czasu, błąd lub sukces).

Long Polling - Klient

Tutaj, aby kod był krótki, użyję 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);

}

Ważne jest, aby pamiętać, że (z dokumentów jQuery ):

W jQuery 1.4.xi niższych obiekt XMLHttpRequest będzie w niepoprawnym stanie, jeśli upłynie limit czasu żądania; dostęp do dowolnego elementu obiektu może spowodować wyjątek. Tylko w przeglądarce Firefox 3.0+ żądania skryptu i żądania JSONP nie mogą zostać anulowane po przekroczeniu limitu czasu; skrypt uruchomi się, nawet jeśli dotrze po upływie limitu czasu.

Długie odpytywanie - serwer

Nie jest w żadnym konkretnym języku, ale wyglądałoby to tak:

function handleRequest () {  

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

     return events();

} 

Tutaj hasTimedOutupewni się, że Twój kod nie czeka wiecznie, i anythingHappenedsprawdzi, czy jakieś zdarzenie się wydarzyło. Ma sleepto na celu uwolnienie twojego wątku do robienia innych rzeczy, gdy nic się nie dzieje. eventsPowróci słownika zdarzeń (lub jakiekolwiek inne struktury danych może wolisz) w formacie JSON (lub innego wolisz).

To z pewnością rozwiązuje problem, ale jeśli martwisz się skalowalnością i wydajnością, tak jak ja podczas badań, możesz rozważyć inne rozwiązanie, które znalazłem.

Rozwiązanie

Użyj gniazd!

Po stronie klienta, aby uniknąć problemów ze zgodnością, użyj socket.io . Próbuje używać gniazda bezpośrednio i ma awarie innych rozwiązań, gdy gniazda nie są dostępne.

Po stronie serwera utwórz serwer za pomocą NodeJS (przykład tutaj ). Klient zasubskrybuje ten kanał (obserwator) utworzony za pomocą serwera. Za każdym razem, gdy trzeba wysłać powiadomienie, jest ono publikowane na tym kanale, a subskrybor (klient) zostaje powiadomiony.

Jeśli nie podoba ci się to rozwiązanie, wypróbuj APE ( Ajax Push Engine ).

Mam nadzieję, że pomogłem.


czy uważasz, że 1 jest zamiennikiem drugiego, czy też potrzebne są obie technologie w tym samym projekcie?
tq

Jeśli masz na myśli APE i NodeJS, możesz wybrać jeden z nich. jeśli masz na myśli okresowe żądania AJAX i to, które zasugerowałem, moje rozwiązanie może wrócić do systemu ajax, gdy brakuje obsługi gniazd (patrz dokumentacja socket.io). W obu przypadkach potrzebujesz tylko jednego rozwiązania.
Walter Macambira,

Hej Walter, chciałbym skorzystać z twojej sugestii na jednej z moich stron. Czy wiesz, gdzie mogę dostać serwer Sockets? Dzięki!
Progo

1
Możesz to zaimplementować. Węzeł sprawia, że ​​jest to naprawdę proste.
Walter Macambira

Jak wykryć hasTimedOut()?
Mobasher Fasihy

18

Według pokazu slajdów na temat systemu wiadomości Facebooka, Facebook używa technologii kometowej do „wypychania” wiadomości do przeglądarek internetowych. Serwer komet Facebooka jest zbudowany na otwartym serwerze internetowym Erlang mochiweb.

Na poniższym obrazku wyrażenie „klastry kanałów” oznacza „serwery komet”.

Przegląd systemu

Wiele innych dużych witryn buduje własny serwer komet, ponieważ istnieją różnice między potrzebami każdej firmy. Ale zbudowanie własnego serwera komet na otwartym serwerze komet jest dobrym podejściem.

Możesz wypróbować icomet , serwer komet C1000K C ++ zbudowany z libevent. icomet zapewnia również bibliotekę JavaScript, jest łatwy w użyciu tak prosty jak:

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 obsługuje szeroki zakres przeglądarek i systemów operacyjnych, w tym Safari (iOS, Mac), IE (Windows), Firefox, Chrome itp.


Ten obraz bardzo dobrze opisuje scenariusz. Byłoby świetnie, gdyby podano przykład działania. Na przykład, co się dzieje, gdy dana osoba otwiera (inicjuje) czat ze znajomym? Jak Facebook dostosowuje się do tej konkretnej rozmowy i przesyła wiadomości na oba końce? (tylko zgaduję: mogę sobie tylko wyobrazić, że aplikacja otwiera gniazdo i wiąże oba adresy klientów, a następnie po prostu słucha i pisze za każdym razem, gdy wiadomość jest zapisywana w skrzynce)
edam

5

Facebook używa MQTT zamiast HTTP. Push jest lepszy niż odpytywanie. Poprzez HTTP musimy stale odpytywać serwer, ale poprzez serwer MQTT przekazuje wiadomość do klientów.

Porównanie MQTT i HTTP: http://www.youtube.com/watch?v=-KNPXPmx88E

Uwaga: moje odpowiedzi najlepiej pasują do urządzeń mobilnych.


3
Ponadto Google korzysta z usługi GCM dla Androida, może być używana przez programistów do wdrażania usługi wiadomości push. developer.android.com/google/gcm/index.html Proszę zaakceptować, jeśli uznasz odpowiedź za przydatną.
abhi

5

Jednym z ważnych problemów z długim odpytywaniem jest obsługa błędów. Istnieją dwa rodzaje błędów:

  1. Żądanie może przekroczyć limit czasu, w którym to przypadku klient powinien natychmiast ponownie nawiązać połączenie. Jest to normalne zdarzenie przy długim odpytywaniu, gdy nie nadeszły żadne wiadomości.

  2. Błąd sieci lub błąd wykonania. Jest to rzeczywisty błąd, który klient powinien z wdzięcznością zaakceptować i poczekać, aż serwer powróci on-line.

Głównym problemem jest to, że jeśli moduł obsługi błędów natychmiast ponownie ustanowi połączenie również w przypadku błędu typu 2, klienci prześlą serwer DOS na serwer.

Obie odpowiedzi z przykładowym kodem tego brakuje.

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.