Làm thế nào để facebook, gmail gửi thông báo thời gian thực?


269

Tôi đã đọc một số bài viết về chủ đề này và các câu trả lời là sao chổi, ajax ngược, http streaming, đẩy máy chủ, v.v.

Thông báo thư đến trên Gmail hoạt động như thế nào?

Làm thế nào để trò chuyện GMail có thể thực hiện các yêu cầu AJAX mà không cần tương tác của khách hàng?

Tôi muốn biết nếu có bất kỳ tài liệu tham khảo mã nào mà tôi có thể theo dõi để viết một ví dụ rất đơn giản. Nhiều bài viết hoặc trang web chỉ nói về công nghệ. Thật khó để tìm thấy một mã mẫu hoàn chỉnh. Ngoài ra, có vẻ như nhiều phương thức có thể được sử dụng để triển khai sao chổi, ví dụ: IFrame ẩn, XMLHttpRequest. Theo tôi, sử dụng XMLHttpRequest là một lựa chọn tốt hơn. Bạn nghĩ gì về ưu và nhược điểm của các phương pháp khác nhau? Gmail nào sử dụng?

Tôi biết nó cần phải làm cả ở phía máy chủ và phía máy khách. Có mã mẫu PHP và Javascript nào không?

Câu trả lời:


428

Cách Facebook làm điều này khá thú vị.

Một phương pháp phổ biến để thực hiện các thông báo như vậy là thăm dò tập lệnh trên máy chủ (sử dụng AJAX) trong một khoảng thời gian nhất định (có lẽ cứ sau vài giây), để kiểm tra xem có điều gì đã xảy ra không. Tuy nhiên, điều này có thể khá chuyên sâu về mạng và bạn thường đưa ra những yêu cầu vô nghĩa, vì không có gì xảy ra.

Cách Facebook thực hiện bằng cách sử dụng phương pháp sao chổi, thay vì bỏ phiếu trong một khoảng thời gian, ngay sau khi một cuộc thăm dò hoàn thành, nó sẽ đưa ra một cách khác. Tuy nhiên, mỗi yêu cầu đối với tập lệnh trên máy chủ có thời gian chờ cực kỳ dài và máy chủ chỉ đáp ứng yêu cầu khi có sự cố xảy ra. Bạn có thể thấy điều này xảy ra nếu bạn mở tab Bảng điều khiển của Fireorms khi ở trên Facebook, với các yêu cầu tới một kịch bản có thể mất vài phút. Nó thực sự khá khéo léo, vì phương pháp này cắt giảm ngay lập tức cả về số lượng yêu cầu và tần suất bạn phải gửi chúng. Bây giờ bạn thực sự có một khung sự kiện cho phép máy chủ 'kích hoạt' các sự kiện.

Đằng sau điều này, về nội dung thực tế được trả về từ các cuộc thăm dò ý kiến ​​đó, đó là phản hồi JSON, với những gì dường như là một danh sách các sự kiện và thông tin về chúng. Mặc dù vậy, nó được rút gọn, vì vậy hơi khó đọc.

Về mặt công nghệ thực tế, AJAX là cách để đi đến đây, bởi vì bạn có thể kiểm soát thời gian chờ yêu cầu và nhiều thứ khác. Tôi khuyên bạn nên (Xếp chồng sáo rỗng ở đây) bằng cách sử dụng jQuery để thực hiện AJAX, sẽ giải quyết được rất nhiều vấn đề về khả năng tương thích chéo. Về mặt PHP, bạn chỉ có thể thăm dò bảng cơ sở dữ liệu nhật ký sự kiện trong tập lệnh PHP của mình và chỉ quay lại máy khách khi có điều gì đó xảy ra? Có, tôi mong đợi, nhiều cách để thực hiện điều này.

Thực thi:

Phía máy chủ:

Dường như có một vài triển khai các thư viện sao chổi trong PHP, nhưng thành thật mà nói, nó thực sự rất đơn giản, một cái gì đó có lẽ giống như mã giả sau đây:

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

echo json_encode(get_events());
  • Hàm has_event_happened sẽ chỉ kiểm tra xem có bất cứ điều gì đã xảy ra trong bảng sự kiện hay điều gì đó không, và sau đó hàm get_events sẽ trả về danh sách các hàng mới trong bảng? Phụ thuộc vào bối cảnh của vấn đề thực sự.

  • Đừng quên thay đổi thời gian thực hiện tối đa PHP của bạn, nếu không nó sẽ hết thời gian sớm!

Phía khách hàng:

Hãy xem plugin jQuery để thực hiện tương tác Comet:

Điều đó nói rằng, plugin dường như thêm một chút phức tạp, nó thực sự rất đơn giản trên máy khách, có lẽ (với jQuery) một cái gì đó như:

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

Toàn bộ điều này phụ thuộc rất nhiều vào cách kiến ​​trúc hiện tại của bạn được kết hợp với nhau.


2
Đó là một lời giải thích rất hay và chi tiết. Cảm ơn bạn. Bạn có mã mẫu nào cho một trong nhiều cách để thực hiện điều đó không?
Billy

45
Tôi nghĩ việc gắn nhãn PHP là một ngôn ngữ / nền tảng không có quy mô tốt không nhất thiết phải đúng. Nó có thể được sử dụng để phát triển các hệ thống quy mô cực lớn. Nhìn vào facebook. Nếu nhà phát triển làm đúng, thì nó sẽ mở rộng, nếu không, thì nó sẽ không. Sử dụng một nền tảng web cụ thể không phải là một sự đảm bảo về khả năng mở rộng. Ồ, và còn nữa, câu hỏi đã hỏi về PHP.
Alistair Evans

5
@Kazar: "Facebook sử dụng PHP" hơi sai lệch - lần trước tôi nghe nói, họ đã phát triển HipHop với mục đích rõ ràng là chuyển đổi PHP sang C ++, vì PHP không hoạt động đủ tốt.
cHao

14
@cHao: Đó là một điểm công bằng, tuy nhiên câu trả lời này đã được viết vào năm 2009, trước khi facebook bắt đầu sử dụng hiphop. Vào thời điểm đó, facebook vẫn là một hệ thống có quy mô rất lớn sử dụng php.
Alistair Evans

6
Vì vậy, kỹ thuật là giữ cho một kết nối liên tục mở, điều này sẽ khiến máy chủ luôn bị căng thẳng. Số lượng kết nối đồng thời điển hình cho một máy chủ web trung bình là khoảng 200, nhưng số lượng người dùng Facebook trực tuyến đồng thời là một cách lớn hơn. Làm thế nào để họ làm điều đó?
Paul

43

Cập nhật

Khi tôi tiếp tục nhận được những đánh giá cao về điều này, tôi nghĩ thật hợp lý khi nhớ rằng câu trả lời này đã 4 tuổi. Web đã phát triển với tốc độ rất nhanh, vì vậy hãy chú ý đến câu trả lời này.


Tôi đã có cùng một vấn đề gần đây và nghiên cứu về chủ đề này.

Giải pháp đưa ra được gọi là bỏ phiếu dài và để sử dụng chính xác, bạn phải chắc chắn rằng yêu cầu AJAX của bạn có thời gian chờ "lớn" và luôn thực hiện yêu cầu này sau khi kết thúc hiện tại (hết thời gian, lỗi hoặc thành công).

Bỏ phiếu dài - Khách hàng

Ở đây, để giữ mã ngắn, tôi sẽ sử dụng 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);

}

Điều quan trọng cần nhớ là (từ tài liệu jQuery ):

Trong jQuery 1.4.x trở xuống, đối tượng XMLHttpRequest sẽ ở trạng thái không hợp lệ nếu hết yêu cầu; truy cập bất kỳ thành viên đối tượng có thể ném một ngoại lệ. Chỉ trong Firefox 3.0+, các yêu cầu script và JSONP không thể bị hủy bởi thời gian chờ; tập lệnh sẽ chạy ngay cả khi nó đến sau khoảng thời gian chờ.

Bỏ phiếu dài - Máy chủ

Nó không có trong bất kỳ ngôn ngữ cụ thể, nhưng nó sẽ là một cái gì đó như thế này:

function handleRequest () {  

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

     return events();

} 

Tại đây, hasTimedOutsẽ đảm bảo mã của bạn không chờ đợi mãi mãi và anythingHappenedsẽ kiểm tra xem có sự kiện nào xảy ra không. Đây sleeplà để phát hành chủ đề của bạn để làm những thứ khác trong khi không có gì xảy ra. Các eventssẽ trở lại một cuốn từ điển về các sự kiện (hoặc bất kỳ cấu trúc dữ liệu khác mà bạn có thể thích) ở định dạng JSON (hoặc bất kỳ khác mà bạn thích).

Nó chắc chắn giải quyết được vấn đề, nhưng, nếu bạn lo lắng về khả năng mở rộng và độ hoàn hảo như khi tôi nghiên cứu, bạn có thể xem xét một giải pháp khác mà tôi tìm thấy.

Giải pháp

Sử dụng ổ cắm!

Về phía khách hàng, để tránh mọi vấn đề tương thích, hãy sử dụng socket.io . Nó cố gắng sử dụng ổ cắm trực tiếp và có dự phòng cho các giải pháp khác khi ổ cắm không có sẵn.

Về phía máy chủ, tạo một máy chủ bằng NodeJS (ví dụ ở đây ). Máy khách sẽ đăng ký kênh này (người quan sát) được tạo bằng máy chủ. Bất cứ khi nào một thông báo phải được gửi, nó sẽ được công bố trong kênh này và người đăng ký (khách hàng) sẽ được thông báo.

Nếu bạn không thích giải pháp này, hãy thử APE ( Ajax Push Engine ).

Hy vọng tôi đã giúp.


Bạn có nghĩ rằng 1 là sự thay thế cho cái kia hoặc có cần cả hai công nghệ trên cùng một dự án không?
tq

Nếu bạn có nghĩa là APE và NodeJS, bạn có thể chọn một trong số họ. nếu bạn có nghĩa là các yêu cầu AJAX định kỳ và yêu cầu tôi đề xuất, giải pháp của tôi có thể chuyển sang ajax khi thiếu hỗ trợ ổ cắm (tham khảo tài liệu socket.io). Trong cả hai trường hợp, bạn chỉ cần một giải pháp.
Walter Macambira

Này Walter, tôi muốn sử dụng đề xuất của bạn trên một trong những trang web của tôi. Bạn có biết nơi tôi có thể có một máy chủ Sockets không? Cảm ơn!
Progo

1
Bạn có thể thực hiện nó. Nút làm cho nó thực sự đơn giản.
Walter Macambira

Làm thế nào để phát hiện hasTimedOut()?
Mobasher Fasihy

18

Theo trình chiếu về hệ thống Nhắn tin của Facebook , Facebook sử dụng công nghệ sao chổi để "đẩy" tin nhắn lên trình duyệt web. Máy chủ sao chổi của Facebook được xây dựng trên máy chủ web Erlang có nguồn gốc mochiweb.

Trong hình bên dưới, cụm từ "cụm kênh" có nghĩa là "máy chủ sao chổi".

Tổng quan hệ thống

Nhiều trang web lớn khác xây dựng máy chủ sao chổi của riêng họ, bởi vì có sự khác biệt giữa nhu cầu của mọi công ty. Nhưng xây dựng máy chủ sao chổi của riêng bạn trên máy chủ sao chổi nguồn mở là một cách tiếp cận tốt.

Bạn có thể thử icomet , một máy chủ sao chổi C1000K C ++ được xây dựng với sự phù hợp. icomet cũng cung cấp một thư viện JavaScript, nó rất dễ sử dụng đơn giản như:

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 hỗ trợ nhiều Trình duyệt và HĐH, bao gồm Safari (iOS, Mac), IE (Windows), Firefox, Chrome, v.v.


Hình ảnh này mô tả kịch bản rất tốt. Sẽ là tuyệt vời nếu một ví dụ trong hành động được đưa ra. Ví dụ: điều gì xảy ra khi một người mở (bắt đầu) một hộp trò chuyện với một người bạn? Làm thế nào facebook điều chỉnh cuộc trò chuyện cụ thể này và đẩy tin nhắn đến cả hai? (chỉ là một phỏng đoán: Tôi chỉ có thể tưởng tượng rằng các chương trình ứng dụng mở một socket và ràng buộc cả hai địa chỉ khách hàng và sau đó chỉ cần giữ nghe và viết bất cứ khi nào thông điệp được viết bằng hộp)
edam

5

Facebook sử dụng MQTT thay vì HTTP. Đẩy là tốt hơn bỏ phiếu. Thông qua HTTP, chúng ta cần thăm dò máy chủ liên tục nhưng thông qua máy chủ MQTT sẽ đẩy thông điệp đến máy khách.

So sánh giữa MQTT và HTTP: http://www.youtube.com/watch?v=-KNPXPmx88E

Lưu ý: câu trả lời của tôi phù hợp nhất cho thiết bị di động.


3
Ngoài ra, google sử dụng dịch vụ GCM cho Android, nhà phát triển có thể sử dụng dịch vụ này để triển khai dịch vụ tin nhắn đẩy. developer.android.com/google/gcm/index.html Vui lòng chấp nhận nếu bạn thấy câu trả lời hữu ích.
abhi

5

Một vấn đề quan trọng với bỏ phiếu dài là xử lý lỗi. Có hai loại lỗi:

  1. Yêu cầu có thể hết thời gian trong trường hợp khách hàng nên thiết lập lại kết nối ngay lập tức. Đây là một sự kiện bình thường trong cuộc bỏ phiếu dài khi không có tin nhắn nào đến.

  2. Một lỗi mạng hoặc một lỗi thực thi. Đây là một lỗi thực tế mà khách hàng nên chấp nhận một cách duyên dáng và chờ máy chủ hoạt động trở lại.

Vấn đề chính là nếu trình xử lý lỗi của bạn thiết lập lại kết nối ngay lập tức đối với lỗi loại 2, các máy khách sẽ DOS máy chủ.

Cả hai câu trả lời với mẫu mã đều bỏ lỡ điều này.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.