轉:實現一個簡單的服務端推送方案

來源:互聯網
上載者:User

標籤:

原文來自於:http://blog.csdn.net/jiao_fuyou/article/details/17090355

用戶端和服務端的互動有推和拉兩種方式:如果是用戶端拉的話,通常就是Polling;如果是服務端推的話,一般就是Comet,目前比較流行的Comet實現方式是Long Polling。

註:如果不清楚相關名詞含義,可以參考:Browser 與 Server 持續同步的作法介紹。

先來看看Polling,它其實就是我們平常所說的輪詢,大致如下所示:

Polling

因為服務端不會主動告訴用戶端它是否有新資料,所以Polling的即時性較差。雖然可以通過加快輪詢頻率的方式來緩解這個問題,但相應付出的代價也不小:一來會使負載居高不下,二來也會讓頻寬捉襟見肘。

再來說說Long Polling,如果使用傳統的LAMP技術去實現的話,大致如下所示:

Long Polling

用戶端不會頻繁的輪詢服務端,而是對服務端發起一個長串連,服務端通過輪詢資料庫來確定是否有新資料,一旦發現新資料便給用戶端發出響應,這次互動便結束了。用戶端處理好新資料後再重新發起一個長串連,如此周而復始。

在上面這個Long Polling方案裡,我們解決了Polling中用戶端輪詢造成的負載和頻寬的問題,但是依然存在服務端輪詢,資料庫的壓力可想而知,此時我們雖然可以通過針對資料庫使用主從複製,分區等技術來緩解問題,但那畢竟只是治標不治本。

我們的目標是實現一個簡單的服務端推方案,但簡單絕對不意味著簡陋,輪詢資料庫是不可以接受的,下面我們來看看如何解決這個問題。在這裡我們放棄了傳統的LAMP技術,轉而使用Nginx與Lua來實現。

Modified Long Polling

此方案的主要思路是這樣的:使用Nginx作為服務端,通過Lua協程來建立長串連,一旦資料庫裡有新資料,它便主動通知Nginx,並把相應的標識(比如一個自增的整數ID)儲存在Nginx共用記憶體中,接下來,Nginx不會再去輪詢資料庫,而是改為輪詢本地的共用記憶體,通過比對標識來判斷是否有新訊息,如果有便給用戶端發出響應。

註:服務端維持大量長串連時核心參數的調整請參考:http長串連200萬嘗試及調優。

首先,我們簡單寫一點代碼實現輪詢(篇幅所限省略了查詢資料庫的操作):

lua_shared_dict config 1m;server {    location /push {        content_by_lua ‘            local id = 0;            local ttl = 100;            local now = ngx.time();            local config = ngx.shared.config;            if not config:get("id") then                config:set("id", "0");            end            while id >= tonumber(config:get("id")) do                local random = math.random(ttl - 10, ttl + 10);                if ngx.time() - now > random then                    ngx.say("NO");                    ngx.exit(ngx.HTTP_OK);                end                ngx.sleep(1);            end            ngx.say("YES");            ngx.exit(ngx.HTTP_OK);        ‘;    }    ...}

註:為了處理服務端不知道用戶端何時中斷連線的情況,代碼中引入逾時機制。

其次,我們需要做一些基礎工作,以便操作Nginx的共用記憶體:

lua_shared_dict config 1m;server {    location /config {        content_by_lua ‘            local config = ngx.shared.config;            if ngx.var.request_method == "GET" then                local field = ngx.var.arg_field;                if not field then                    ngx.exit(ngx.HTTP_BAD_REQUEST);                end                local content = config:get(field);                if not content then                    ngx.exit(ngx.HTTP_BAD_REQUEST);                end                ngx.say(content);                ngx.exit(ngx.HTTP_OK);            end            if ngx.var.request_method == "POST" then                ngx.req.read_body();                local args = ngx.req.get_post_args();                for field, value in pairs(args) do                    if type(value) ~= "table" then                        config:set(field, value);                    end                end                ngx.say("OK");                ngx.exit(ngx.HTTP_OK);            end        ‘;    }    ...}

如果要寫Nginx共用記憶體的話,可以這樣操作:

shell> curl -d "id=123" http://<HOST>/config

如果要讀Nginx共用記憶體的話,可以這樣操作:

shell> curl http://<HOST>/config?field=id

註:實際應用時,應該加上許可權判斷邏輯,比如只有限定的IP地址才能使用此功能。

當資料庫有新資料的時候,可以通過觸發器來寫Nginx共用記憶體,當然,在應用程式層通過觀察者模式來寫Nginx共用記憶體通常會是一個更優雅的選擇。

如此一來,資料庫就徹底翻身做主人了,雖然系統仍然存在輪詢,但已經從輪詢別人變成了輪詢自己,效率不可相提並論,相應的,我們可以加快輪詢的頻率而不會造成太大的壓力,從而在根本上提升使用者體驗。

突然想起另一個有趣的服務端推的做法,不妨在一起嘮嘮:如果DB使用Redis的話,那麼可以利用其提供的BLPOP方法來實現服務端推,這樣的話,連sleep都不用了,不過有一點需要注意的是,一旦使用了BLPOP方法,那麼Nginx和Redis之間的串連便會一直保持下去,從Redis的角度看,Nginx是用戶端,而用戶端的可用連接埠數量是有限的,這就意味著一台Nginx至多隻能建立六萬多個串連(net.ipv4.ip_local_port_range),有點兒少。

當然,本文的描述只是滄海一粟,還有很多技術可供選擇,比如Pub/Sub,WebSocket,Nginx_Http_Push_Module等等,篇幅所限,這裡就不多說了,有興趣的讀者請自己查閱。

轉:實現一個簡單的服務端推送方案

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.