基於 HTTP 協議的 3 種實時數據獲取技術 HTTP協議 方式一:短輪詢 方式二:長輪詢 方式三:WebSocket 《Netty 實現原理與源碼解析 —— 精品合集》 《Spring 實現原理與源碼解析 —— 精品合集》 《MyBatis 實現原理與源碼解析 —— 精品合集》 《Spring MVC 實現原理與源碼解析 —— 精品合集》 《Spring Boot 實現原理與源碼解析 —— 精品合集》 《資料庫實體設計合集》 《Java 面試題 —— 精品合集》 《Java 學習指南 —— 精品合集》 HTTP協議HTTP協議大家都很熟悉了,開始本文之前,首先簡單回顧一下HTTP協議。 HTTP協議是建立在TCP協議上的應用層協議,協議的本質是請求----應答: 即對於HTTP協議來說,服務端給一次響應後整個請求就結束了,這是HTTP請求最大的特點,也是由於這個特點,HTTP請求無法做到的是服務端向客戶端主動推送數據。但由於HTTP協議的廣泛應用,很多時候確實又想使用HTTP協議去實現實時的數據獲取,這種時候應當怎麼辦呢?下面首先介紹幾種基於HTTP協議的實時數據獲取方法。 方式一:短輪詢 輪詢是最普遍的基於HTTP協議獲取實時數據的方式,輪詢又分為短輪詢和長輪詢。短輪詢非常簡單,用一張圖表示一下: 客戶端向服務端請求數據,服務端立即將數據返回給客戶端,客戶端沒有拿到想要的數據(比如返回結果告訴客戶端,數據處理中),客戶端繼續發請求,服務端繼續立即響應,周而復始。這種實時數據獲取的方式比較粗暴,優點在於編程簡單,客戶端發請求,服務端實時迴響應即可。缺點主要有兩個: 無效請求多,每一次無效請求都在浪費帶寬和伺服器的計算資源 對伺服器壓力大,定時發請求,並發一高,可能服務端瞬間會收到成千上萬個請求,很容易拖垮伺服器甚至導致宕機 那麼短輪詢適合哪種使用場景呢,按照我的理解如果數據變化比較頻繁或者能預期到數據在短時間內會發生一次變化的場景可以使用短輪詢,比如: 用戶在PC端買了一個東西喚起網頁端,由於PC端和網頁端是不通的,我們預期到用戶應該很快會完成付款,這種時候為了開發簡單短輪詢是一種可以使用的方式,直接服務端提供一個介面告訴客戶端訂單狀態,客戶端每5秒請求一次即可,拿到結果就可以不用請求了。使用短輪詢注意要做好請求次數上限的控制,比如請求100次還沒檢測到用戶付款,可以彈窗"請完成付款後去我的訂單頁面查詢"就可以不用請求了。 方式二:長輪詢 長輪詢是另一種實時獲取數據的方式,看一下流程: 本質上沒有改變,依然是客戶端在沒有收到自己想要數據的情況下不斷發送請求給服務端,差別在於服務端收到請求不再直接給響應,而是將請求掛起,自己去定時判斷數據的變化,有變化就立馬返回給客戶端,沒有就等到超時為止。可以很明顯的看到,長輪詢的優點就是客戶端的請求少了很多避免了無謂的客戶端請求,缺點則是服務端會掛起大量請求增加資源消耗且伺服器對HTTP請求並發數量是有限制的。微信網頁版的登陸是一個典型的長輪詢的例子: 從圖上看,客戶端不斷發送請求到伺服器,伺服器第一時間並沒有給出回應,於是客戶端等待,在超時的情況下繼續發送請求。總的來說我理解一般使用長輪詢會更多一點,短輪詢更加看重的是編程簡單,適合小型應用。像微信網頁端登錄這種,成千上萬個用戶同時登陸,隔一段時間服務端收成千上個請求去處理哪裡受得了,堆機器分攤每台伺服器上處理請求的數量終究不是解決問題的辦法。 方式三:WebSocket 上面介紹了兩種輪詢方式,但是兩種綜合起來都有比較明顯的缺點,總結起來有以下幾個: 偽實時,即上述兩種方式都不是真正的實時,無論短輪詢的客戶端輪詢時間多短,還是長輪詢的服務端輪詢時間多短,都存在一定程度的延時 所有的輪詢只要沒有需要的數據返回,都是對計算資源的一種浪費 HTTP協議本身是一個重的協議,每一次都必須帶有HTTP首部+HTTP頭部,實際上對我們來說需要的只是HTTP Body而已,多餘的數據都是對帶寬的一種浪費 因此,最好我們可以做到的事情是:客戶端和服務端之間有一條通路,當服務端數據有變化的時候,服務端可以主動推送到客戶端。WebSocket就是HTML5之後為了做到這一點而誕生的一種協議,雖然這是一種新的協議,但也是基於HTTP協議的。看一下WebSocket的原理,很簡單: WebSocket客戶端首先通過HTTP協議發送幾個特別的header到服務端,告訴服務端現在我發起的是HTTP請求,但我要升級到WebSocket了: Upgrade:websocket Connection:Upgrade Sec-WebSocket-Key: XXX Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: XX 只要伺服器支持WebSocket協議(Tomcat7、Jetty7之後都是支持WebSocket的),那麼服務端收到請求且建立連接成功後會返回Sec-WebSocket-Accept、Sec-WebSocket-Protocol這兩個header給客戶端,且Http Status為101表示協議切換成功,這樣客戶端和服務端只要任意一方沒有斷開連接,就可以基於這一條通路進行通訊了。再談一下之前提的WebSocket相比長短輪詢對於帶寬資源的節省。有一個測試,假設HTTP Header是871位元組,WebSocket由於數據傳輸是基於幀的,幀傳輸更加高效,對比長短輪詢,2個位元組即可代替871個位元組的Header,測試結果為: 相同的每秒客戶端輪詢的次數,當次數高達10W/s的高頻率次數的時候,輪詢需要消耗665Mbps,而WebSocket僅僅只花費了1.526Mbps,將近435倍。WebSocket做到了真正的實時且大量節省帶寬資源,但是我理解也有自己的問題,就是開發成本比較高,這裡的開發成本倒不是說自己去實現WebSocket,這個在Java語言層面上直接使用Netty-Socketio即可,API很簡單,提供了對WebSocket完整的實現,真正的開發成本在於分散式環境下的數據同步問題。舉個例子,有一個在線聊天系統10W人同時在線,此時有一個用戶發了一條1K的語音消息,單機保持10W的連接倒是可以(這裡不是HTTP請求,因此不受連接池數影響),問題在於帶寬。單機同時向10W用戶推送1K語音消息,需要的帶寬至少10M,這還只是純粹推送數據出去,沒有考慮到數據進來的場景,實際運行過程中需要的帶寬會更多,對於企業來說這是一筆非常大的成本。因此,大量連接的場景下都會做集群(實際就算沒有大量連接,為了高可用性,也會做集群),10W並發分出5台機器,平均每台機器有2W連接,考慮集群下會出現的問題: 客戶端1把數據發送到伺服器1,伺服器1連接的所有客戶端都可以推送該條語音,但是問題在於: 伺服器2~伺服器5連的所有客戶端如何拿到數據?簡單的一種方式是使用消息隊列,將數據通過消息隊列發送到所有訂閱的伺服器上 那如果傳輸的是一張1M的圖片,數據太大不適合使用消息隊列怎麼辦,可以先將數據存儲下來,消息隊列只發送id,收到消息的伺服器再根據id去取真正的數據並推送 如果依賴消息隊列,那麼不僅僅需要對應用進行代碼開發,還需要對消息伺服器做分散式集群、做壓力測試,保證高可用 2W連接正常預計發送1K的消息是沒問題的,但是萬一用戶發送了1M圖片導致遠超預估帶寬怎麼辦,是業務上取捨不能發送超過XXX的數據還是技術上處理 其他太多需要考慮的問題沒有列出來,總而言之,用WebSocket在大量請求、高並發的場景下,代碼開發成本是非常高的。但是由於WebSocket可以做到真正的實時服務端對客戶端的數據推送且對帶寬資源有大量的節省,因此很多IM、音視頻、彈幕等應用都會使用WebSocket。來源:http://t.cn/E6rUVEV 推薦閱讀: 相关文章 {{#data}} {{title}} {{/data}}