facebook, gmail ส่งการแจ้งเตือนตามเวลาจริงอย่างไร


269

ฉันได้อ่านบางกระทู้เกี่ยวกับหัวข้อนี้และคำตอบคือดาวหาง, ย้อนกลับ ajax, การสตรีม http, การพุชเซิร์ฟเวอร์, ฯลฯ

การแจ้งเตือนจดหมายขาเข้าใน Gmail ทำงานอย่างไร

GMail Chat สามารถส่งคำขอ AJAX โดยไม่มีการโต้ตอบกับลูกค้าได้อย่างไร

ฉันต้องการทราบว่ามีการอ้างอิงรหัสใด ๆ ที่ฉันสามารถติดตามเพื่อเขียนตัวอย่างง่าย ๆ ได้หรือไม่ โพสต์หรือเว็บไซต์มากมายพูดถึงเทคโนโลยี มันยากที่จะหารหัสตัวอย่างที่สมบูรณ์ นอกจากนี้ยังดูเหมือนว่ามีวิธีการมากมายที่สามารถนำไปใช้กับดาวหางเช่น Hidden IFrame, XMLHttpRequest ในความคิดของฉันการใช้ XMLHttpRequest เป็นตัวเลือกที่ดีกว่า คุณคิดอย่างไรกับข้อดีข้อเสียของวิธีการต่าง ๆ Gmail ใช้อันไหน

ฉันรู้ว่ามันต้องทำทั้งในฝั่งเซิร์ฟเวอร์และฝั่งไคลเอ็นต์ มีรหัสตัวอย่าง PHP และ Javascript หรือไม่

คำตอบ:


428

วิธีที่ Facebook ทำนั้นน่าสนใจทีเดียว

วิธีทั่วไปในการแจ้งเตือนดังกล่าวคือการสำรวจความคิดเห็นของสคริปต์บนเซิร์ฟเวอร์ (ใช้ AJAX) ในช่วงเวลาที่กำหนด (อาจจะทุกสองสามวินาที) เพื่อตรวจสอบว่ามีบางอย่างเกิดขึ้นหรือไม่ อย่างไรก็ตามนี่อาจเป็นเครือข่ายที่ค่อนข้างใช้งานได้บ่อยและคุณมักจะทำการร้องขอแบบไม่มีจุดหมายเพราะไม่มีอะไรเกิดขึ้น

วิธีที่ Facebook ใช้คือวิธีดาวหางแทนที่จะทำการสำรวจในช่วงเวลาหนึ่งทันทีที่การสำรวจครั้งหนึ่งเสร็จสิ้น อย่างไรก็ตามแต่ละคำขอไปยังสคริปต์บนเซิร์ฟเวอร์มีการหมดเวลานานมากและเซิร์ฟเวอร์ตอบสนองต่อการร้องขอเมื่อเกิดอะไรขึ้น คุณสามารถเห็นสิ่งนี้เกิดขึ้นได้หากคุณเปิดแท็บคอนโซลของ Firebug ขึ้นบน Facebook โดยอาจมีการขอให้สคริปต์ใช้เวลาสักครู่ มันค่อนข้างแยบยลจริงๆเนื่องจากวิธีนี้ลดจำนวนคำขอลงทันทีและความถี่ที่คุณต้องส่งคำขอ ตอนนี้คุณมีเฟรมเวิร์กของเหตุการณ์ที่อนุญาตให้เซิร์ฟเวอร์ 'เริ่มต้น' เหตุการณ์ได้อย่างมีประสิทธิภาพ

เบื้องหลังสิ่งนี้ในแง่ของเนื้อหาจริงที่ส่งคืนจากการสำรวจเหล่านั้นเป็นการตอบสนองของ JSON โดยสิ่งที่ดูเหมือนจะเป็นรายการของเหตุการณ์และข้อมูลเกี่ยวกับพวกเขา มันลดขนาดลงเล็กน้อยดังนั้นจึงเป็นการยากที่จะอ่าน

ในแง่ของเทคโนโลยีที่แท้จริง AJAX เป็นวิธีที่จะไปที่นี่เพราะคุณสามารถควบคุมการหมดเวลาการร้องขอและสิ่งอื่น ๆ อีกมากมาย ฉันขอแนะนำ (Stack cliche cliche ที่นี่) โดยใช้ jQuery เพื่อทำ AJAX มันจะใช้เวลามากในการข้ามปัญหา compability ในแง่ของ PHP คุณสามารถสำรวจตารางฐานข้อมูลบันทึกเหตุการณ์ในสคริปต์ PHP ของคุณและกลับไปหาลูกค้าเมื่อเกิดอะไรขึ้นเท่านั้น? ฉันคาดหวังว่าจะมีหลายวิธีในการดำเนินการนี้

การดำเนินการ:

ฝั่งเซิร์ฟเวอร์:

ดูเหมือนจะมีการใช้งานของห้องสมุดดาวหางใน PHP แต่จริงๆแล้วมันเรียบง่ายมากบางสิ่งบางอย่างอาจเหมือนกับ pseudocode ต่อไปนี้:

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 เป็นภาษา / แพลตฟอร์มที่มีขนาดไม่ดีไม่จำเป็นต้องเป็นเรื่องจริง มันสามารถใช้ในการพัฒนาระบบขนาดใหญ่มาก ดูที่ Facebook หากผู้พัฒนาทำถูกต้องแล้วมันจะทำการขยายขนาดหากไม่ทำเช่นนั้นจะไม่เกิดขึ้น การใช้แพลตฟอร์มเว็บที่ระบุไม่ได้รับประกันความสามารถในการขยาย โอ้และคำถามก็ถามหา PHP
Alistair Evans

5
@Kazar: "Facebook ใช้ PHP" เป็นเรื่องที่ทำให้เข้าใจผิดเล็กน้อย - ล่าสุดที่ฉันได้ยินพวกเขาพัฒนา HipHop เพื่อวัตถุประสงค์ในการแปลง PHP เป็น C ++ อย่างรวดเร็วเนื่องจาก PHP ทำงานได้ไม่ดีพอ
cHao

14
@cHao: มันเป็นจุดที่ยุติธรรม แต่คำตอบนี้ถูกเขียนในปี 2009 ก่อนที่ Facebook จะเริ่มใช้ฮิปฮอป ในขณะนั้น facebook ยังคงเป็นระบบที่มีขนาดใหญ่มากโดยใช้ php ด้วยตัวเอง
อลิสแตร์อีแวนส์

6
ดังนั้นเทคนิคคือการทำให้การเชื่อมต่อเปิดอย่างต่อเนื่องซึ่งจะทำให้เซิร์ฟเวอร์อยู่ในสภาวะที่มีความเครียดคงที่ จำนวนทั่วไปของการเชื่อมต่อพร้อมกันสำหรับเว็บเซิร์ฟเวอร์โดยเฉลี่ยคือประมาณ 200 แต่จำนวนผู้ใช้ Facebook ที่ออนไลน์ในเวลาเดียวกันนั้นใหญ่กว่า พวกเขาจะทำอย่างไร
พอล

43

ปรับปรุง

ในขณะที่ฉันยังคงได้รับคะแนนโหวตนี้ต่อไปฉันคิดว่ามันสมเหตุสมผลที่จะจำไว้ว่าคำตอบนี้มีอายุ 4 ปี เว็บเติบโตอย่างรวดเร็วดังนั้นโปรดระวังคำตอบนี้


ฉันมีปัญหาเดียวกันเมื่อเร็ว ๆ นี้และค้นคว้าเกี่ยวกับเรื่องนี้

การแก้ปัญหาที่ให้เรียกว่าการทำโพลแบบยาวและการใช้อย่างถูกต้องคุณต้องแน่ใจว่าคำขอ AJAX ของคุณมีการหมดเวลา "ใหญ่" และจะต้องทำคำขอนี้เสมอหลังจากการสิ้นสุดปัจจุบัน (หมดเวลาข้อผิดพลาดหรือความสำเร็จ)

Long Polling - ไคลเอนต์

ที่นี่เพื่อให้รหัสสั้น ๆ ฉันจะใช้ 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 docs ):

ใน jQuery 1.4.x และต่ำกว่าวัตถุ XMLHttpRequest จะอยู่ในสถานะที่ไม่ถูกต้องหากคำขอหมดเวลา การเข้าถึงสมาชิกวัตถุอาจทำให้เกิดข้อยกเว้น ใน Firefox 3.0+ เท่านั้นคำขอสคริปต์และ JSONP ไม่สามารถยกเลิกได้โดยการหมดเวลา สคริปต์จะทำงานแม้ว่าจะมาถึงหลังจากหมดเวลา

Long Polling - เซิร์ฟเวอร์

มันไม่ได้เป็นภาษาเฉพาะ แต่จะเป็นเช่นนี้:

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 docs) ในทั้งสองกรณีคุณต้องการเพียงโซลูชันเดียว
วอลเตอร์ Macambira

เฮ้วอลเตอร์ฉันต้องการใช้คำแนะนำของคุณกับหนึ่งในเว็บไซต์ของฉัน คุณรู้ไหมว่าฉันจะหาเซิร์ฟเวอร์ Sockets ได้ที่ไหน ขอบคุณ!
Progo

1
คุณสามารถใช้มัน โหนดทำให้มันง่ายจริงๆ
Walter Macambira

วิธีการตรวจจับhasTimedOut()?
Mobasher Fasihy

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), IEs (Windows), Firefox, Chrome ฯลฯ


ภาพนี้อธิบายสถานการณ์ได้ดีมาก คงจะดีมากถ้าได้รับตัวอย่างจากการกระทำ ตัวอย่างเช่นจะเกิดอะไรขึ้นเมื่อบุคคลเปิด (เริ่มต้น) กล่องแชทกับเพื่อน facebook ปรับแต่งวิธีการสนทนานี้โดยเฉพาะและส่งข้อความถึงปลายทั้งสองอย่างไร (แค่เดา: ฉันสามารถจินตนาการว่าโปรแกรมเปิดซ็อกเก็ตและผูกทั้งที่อยู่ของลูกค้าและแล้วก็ให้ฟังและการเขียนข้อความเมื่อใดก็ตามที่เขียนไว้ในกล่อง)
Edam

5

Facebook ใช้ MQTT แทน HTTP ดันดีกว่าการเลือกตั้ง ผ่าน HTTP เราจำเป็นต้องสำรวจเซิร์ฟเวอร์อย่างต่อเนื่อง แต่ผ่านเซิร์ฟเวอร์ MQTT จะส่งข้อความไปยังลูกค้า

การเปรียบเทียบระหว่าง MQTT และ HTTP: http://www.youtube.com/watch?v=-KNPXPmx88E

หมายเหตุ: คำตอบของฉันเหมาะสมที่สุดสำหรับอุปกรณ์มือถือ


3
นอกจากนี้ Google ยังใช้บริการ GCM สำหรับ Android ซึ่งนักพัฒนาสามารถใช้สำหรับการใช้บริการข้อความแบบพุชได้ developer.android.com/google/gcm/index.html โปรดยอมรับหากคุณพบว่าคำตอบมีประโยชน์
abhi

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.