Android 架構之長連線技術

wingjay發表於2019-02-12

《億級Android架構》小專欄文章列表:

《億級 Android 架構》專欄隨談》

《Android 架構之網路連線與加速》

《Android 架構之長連線技術》

《Android 架構之高可用行動網路連線》

《Android 架構之網路安全演進》

《Android 架構之高效能移動端日誌系統》

《Android 架構之秒級移動配置中心》

正文

上一篇文章《Android 架構之網路框架(上)》中,我們談過了網路框架OkHttp、網路加速方案如HttpDNS、資料壓縮與序列化等技術點。本文我們結合騰訊Mars框架美團Shark體系等業內主流長連線方案,談一談長連線技術的各個方面。

本文會包括下面的技術點:

  • 長連線與Http短連線、Keep-Alive傻傻分不清
  • 你為什麼需要長連線
  • 長連線何時會斷開
  • 如何建立穩定長連線
  • Mars智慧心跳機制
  • 長連線資料協議及加密
  • 長連線通道建設及容災

除了大家常用的Http短連線,大型App幾乎都會搭建一套完整的TCP長連線網路通道。我們先來看下美團Shark長連線的線上資料:

Android 架構之長連線技術
Android 架構之長連線技術

圖片來源 《美團點評行動網路優化實踐

上面兩張圖片對比了長/短連線的成功率和網路延時資料,這兩個是網路模組最重要的衡量指標。可以看出,無論是成功率,還是網路延時,長連線都明顯優於短連線。

另外,大家都知道微信的訊息收發非常即時,這便歸功於背後穩定高可用的長連線系統。實際上,微信除了訊息收發,其他的小資料通訊都是通過長連線來實現的。

下面我們來講解一些長連線的一些核心技術點。

I. 長連線與Http短連線、Keep-Alive傻傻分不清

為防止大家對於長連線和短連線混淆,這裡先簡單說明下幾點區別。

長連線 vs Http短連線

這兩者分別對應的是TCP協議層長連線短連線

大家都知道,TCP會通過三次握手,建立與服務端的連線,然後傳遞資料,只不過短連線在資料傳輸完後,會主動關閉連線,而長連線會繼續保持這條連線,後續的資料讀寫繼續使用這條連線。

長連線 vs Http的Keep-Alive

上一篇文章中提到了連線複用,通過Http1.1的Keep Alive欄位,我們可以讓一條Http連線保持不被立即關閉。有些同學這時就疑惑了,是不是長連線就是Keep Alive呢?

其實不是的。長連線我們也叫TCP長連線,它是架設在TCP協議上的,而上面說的Keep Alive是Http協議的內容,連協議都不同,兩者自然不是一個東西。

開啟了Keep Alive是Http連線,我們也稱之為持久連線,和長連線並不同。感興趣可參考此文:《TCP 進階》

TCP的Keep-Alive vs Http的Keep-Alive

提到Keep Alive,有些同學就會問了,TCP協議裡也有一個Keep Alive,它和Http協議裡的Keep Alive有什麼區別嗎?

二者的用處並不同。Http協議在完成一個請求後,伺服器會自動關閉連線。這時,可以在請求裡帶上一個Keep Alive給伺服器,告訴伺服器不要立即關閉連線,我還想繼續複用這條連線;而對TCP協議層而言,是不會自動斷開的,但這也帶來了一個問題,萬一由於某些外部原因導致連線斷開,那我如何知道連線已失效呢?TCP會在2個小時間隔後,自動傳送一個Keep Alive資料包給服務端,探測一下伺服器是否還在響應。它的功能類似心跳包,只是間隔太長,不適合做真正的心跳包。

Android 架構之長連線技術

II. 你為什麼需要長連線

那麼,相比Http短連線,長連線技術能帶來什麼好處呢?

1. 不同域名的請求可以複用同一個長連線通道

以前我們不同域名的請求,需要做對應的DNS請求,然後建立對應的Http連線。上篇文章裡說的Http連線池在不同域名下不可複用,需要重新建立連線。這些都是一些資源開銷,但是如果通過長連線通道,那域名只是這個請求裡的一個欄位,可以直接複用同一條長連線通道。

2. 不依賴DNS,無DNS耗時和劫持等問題

上文中我們提到了HttpDNS,雖然它比系統DNS更優,但終歸還是要做DNS操作。而長連線都是IP直接連線,因此沒有DNS相關的開銷和耗時。

3. 如果有大量網路請求,可以明顯減少網路延時,節省頻寬

對於大型App而言,存在繁多密集的網路請求,這中間就會存在非常多次的Http斷開和重新連線,浪費了很多時間和頻寬。而通過長連線通道的話,則沒有這部分耗時,直接傳輸二進位制資料即可,節省了每次連線裡Header之類的頻寬開銷。

4. 服務端主動Push資料到客戶端

對於上面提到的微信訊息接收等場景,如果需要客戶端主動去輪詢,則會頻繁發起請求,對於伺服器會產生很大的負載壓力,浪費頻寬流量。而通過長連線,服務端可以主動把訊息下發給客戶端,做到最高實時性,且節省流量。

Android 架構之長連線技術

III. 長連線何時會斷開?

正常而言,長連線是不會斷開的。大家可以自己試一試,兩個socket建立連線,只要網路不變、一切正常,那麼這兩個socket可以一直互相傳送資料,不會斷開。

但是,在行動網路下,網路狀態複雜多變,比如網路線路被切斷、伺服器當機等,都會導致長連線中斷。除了這些線路異常外,我們需要關注下面幾個長連線斷開原因:

1. 長連線所在程式被殺

這個很容易理解,如果我們的App切換到後臺,那麼系統隨時可能將我們的App殺掉,這時長連線自然也就隨之斷開。

2. 使用者切換網路

比如手機網路斷開,或者發生Wi-Fi和蜂窩資料切換,這時會導致手機IP地址變更。而我們知道,TCP連線是基於IP + Port的,一旦IP變更,TCP連線自然也就失效了,或者說長連線也就相當於斷開了。

3. 系統休眠等導致NAT超時

這裡對NAT簡單解釋下,方便有的同學不太瞭解。當手機連線上網路時,閘道器會給我們分配一個IP地址,這個其實是內網IP,此時還未真正連線上公網,也連線不上伺服器;如果想要連線公網,需要運營商將我們的內網IP對映成一個公網IP,有了公網IP,伺服器就能與我們建立連線了。NAT指的就是這個對映過程。

也就是說,運營商會給每臺裝置分配一個公網IP,類似一張通訊證。不過,隨著連線網路的裝置不斷增多,閘道器負載也會不斷加大,這時,運營商就會對一些不太活躍的裝置進行公網IP回收了,如果下次這個裝置需要連網,那就重新分配一個IP即可。

看似沒問題,但實際上,如果我們的App在一段時間不活躍,發生了NAT超時,便會導致我們的公網IP失效,長連線也隨之失效了。

4. DHCP 租期

DHCP 租期過期,如果沒有及時續約,同樣會導致IP地址失效。

綜合而言,長連線在正常情況下是不會斷開的,但是,一旦手機的IP地址失效,這時就不得不重新建立連線了。

IV. 如何建立穩定長連線?

上面我們提到了多種長連線斷開的原因,那我們應該如何進行優化,儘可能保證長連線不斷開,或者及時斷開了,也要儘快重連呢?

1. Mars長連線獨立程式

為了減少程式被殺的機率,在Mars的Demo程式碼裡我們可以看到,它將長連線邏輯單獨提取到了一個獨立的程式裡。這個程式只做網路互動,消耗的記憶體等資源自然較少,從而減少了被系統回收的概率。

Android 架構之長連線技術

圖片來自《Android版微信後臺保活實戰分享(程式保活篇)》

2. 長連線程式復活

程式被殺難以避免,不過可以通過AlarmReceiver、 ConnectReceiver、BootReceiver,達到程式的及時喚醒。

當然,程式保活是一個比較大的話題,而且不恰當的程式保活也會對系統體驗造成危害。這裡就不深究了。

3. 心跳機制

對於心跳包很多人誤以為只是用來定期告訴服務端我們的狀態,實際並非如此。

上面我們提到了 NAT 超時,即如果App一段時間內不活躍,會導致運營商那裡刪除我們的公網IP對映關係,這會導致我們的TCP長連線斷開。因此,我們需要通過心跳機制來保證App的活躍度,防止發生 NAT 超時。

4. 斷開重連

線上上執行時,長連線很有可能會由於網路切換之類的原因斷開。這時,我們需要儘快發現長連線斷開,並立即重連。一般有下面幾種做法:

  • 建立Receiver,監控網路狀態,如果網路發生切換則立即重連;
  • 監控服務端心跳包回包,如果連續5次沒有收到回包,則認為長連線已經失效;
  • 設定心跳包超時限制,如果超過時間還沒有收到心跳回包,則重連,這種方式比較耗電;
  • 等socket IO異常丟擲,不過耗時太長,需要15s左右才能發現。

Android 架構之長連線技術

V. Mars智慧心跳機制

1. 固定心跳機制

上面我們說了,心跳機制主要是為了防止 NAT 超時,外網IP地址失效。因此,一般的做法就是在NAT失效前,保證有心跳包發出。或者說,客戶端應當以略小於NAT超時時間的間隔來傳送心跳包。

NAT超時時間

早期的微信的心跳是4.5分鐘傳送一次心跳,可以不錯的執行。

2. Mars智慧心跳策略

在儘量不影響使用者收訊息及時性的前提下,根據網路型別自適應的找出保活信令TCP連線的儘可能大的心跳間隔,從而達到減少安卓微信因心跳引起的空中通道資源消耗,減少心跳Server的負載,以及減少部分因心跳引起的耗電。

自適應心跳

因此,在固定心跳機制下,微信又研究了一套動態計算心跳的方案,動態的探測最大的NAT超時時間,然後選定合適的心跳間隔區間去傳送心跳包。這裡說一下大致思路:

首先,如果心跳間隔越久,產生的負載和消耗也會越小。因此微信採用了自適應心跳:當找到一個有效心跳間隔後,我們主動去加大這個間隔,然後測試是否能成功,如果不能,則使用比上一次成功間隔稍短的時間作為間隔;否則繼續加大間隔,直到找到可用的有效間隔。

那麼,如何判斷一個心跳間隔有效呢?微信採用的方案是使用固定短心跳直到滿足三次連續短心跳成功,則認為這個間隔有效。

探測過程大致為:60秒短心跳,連續發3次後開始探測,90,120,150,180,210,240,270

前後臺策略

另外,考慮到App在前後臺對於長連線的需求是不同的。因此當微信在前臺活躍態時,採用了固定心跳機制;在前臺熄屏態或者後臺活躍態(進入後臺10分鐘內)時,先用幾次最小心跳維持長連線,然後進入自適應心跳機制;在後臺穩定態(超過10分鐘),則採用自適應心跳計算出來的最大心跳作為固定值。

如果在執行過程中,發生了心跳失敗,則進行重連。同時將心跳間隔調整為斷線前間隔減去20s,重新走自適應心跳;如果連續5次均失敗,則以初始心跳180s繼續測試。

Alarm對齊策略

對於Android系統而言,為了減少頻繁喚醒系統導致的電量損耗,提供了Alarm對齊喚醒機制:把一定時間段內的多次Alarm喚醒合併成一次,減少系統被喚醒次數,增加待機時間。

而我們的心跳包就是需要在定時結束後自動觸發一次心跳包的傳送,因此,在Mars裡面的心跳時間也是按照Alarm對齊時間來做心跳間隔,減少電量損耗。

其他

對於微信心跳策略感興趣的話可以閱讀文末的參考文獻,程式碼可以參考smart_heartbeat

VI. 長連線資料協議及加密

長連線傳遞的是二進位制資料,前後端可以自行協商每個位元組要存放的內容即可。當然,也可以考慮採用一些通用協議:比如SMTP、ProtoBuf等序列化方案。

參考文章:《一個基於TCP/WebSockets的超級精簡的長連線訊息協議》.

另外,在資料加密方面,可以結合非對稱加密演算法RSA和對稱加密演算法AES來對資料進行加密傳輸。

這一點不是本文的重點,不做過多贅述。

VII. 長連線通道建設及容災

上面講了長連線的優勢,那我們該如何搭建整個長連線通道呢?這裡我們以美團的長連線通道為例子進行說明,各大廠的方案也是類似的。

美團長連通道
上面是一個簡圖,大體流程如下:

  1. 客戶端與代理長連伺服器建立長連線,代理伺服器可全國多地部署,在建立長連時可以選擇最近的伺服器IP就近接入;
  2. 長連線建立好後,客戶端對要傳送的二進位制資料進行加密並傳輸;
  3. 代理伺服器收到後,可以通過內部專線或普通Http請求來訪問業務伺服器;
  4. 如果長連線出現問題導致不可用,為保障客戶端執行,需要立即降級成普通Http短連或者UDP通道。

小結

本文結合了國內大廠如騰訊、美團等長連線框架,針對長連線這個技術點做了完整的介紹和剖析,如有不對或疑問,歡迎留言。


謝謝。

wingjay


《億級Android架構》小專欄介紹

業務的快速增長離不開穩定可靠的架構。《億級Android架構》小專欄會基於作者實際工作經驗,結合國內大廠如阿里、騰訊、美團等基礎架構現狀,嘗試談談如何設計一套好的架構來支援業務從0到1,甚至到億,希望與大家多多探討。

本專欄主要內容:

  1. 當前大廠有哪些Android架構;
  2. 這些架構能解決什麼問題;
  3. 這些架構的原理是什麼;
  4. 學習這些架構對我們自身的意義。

《億級Android架構》小專欄文章列表:

《億級 Android 架構》專欄隨談》

《Android 架構之網路連線與加速》

《Android 架構之長連線技術》

《Android 架構之高可用行動網路連線》

《Android 架構之網路安全演進》

《Android 架構之高效能移動端日誌系統》


參考:

《移動端IM實踐:實現Android版微信的智慧心跳機制》

《Android端訊息推送總結:實現原理、心跳保活、遇到的問題等》

《美團點評行動網路優化實踐》

《Android版微信後臺保活實戰分享(網路保活篇)》

《移動 APP 網路優化概述》

《高效 保活長連線:手把手教你實現 自適應的心跳保活機制》

《一種Android端IM智慧心跳演算法的設計與實現探討》

《HTTP長連線說明》

《TCP 進階》

公眾號,專注於Android、Java、大前端等技術領域,也包含程式猿成長、跳槽等內容。

Android 架構之長連線技術

相關文章