《億級Android架構》小專欄文章列表:
正文
上一篇文章《Android 架構之網路框架(上)》中,我們談過了網路框架OkHttp、網路加速方案如HttpDNS、資料壓縮與序列化等技術點。本文我們結合騰訊Mars框架和美團Shark體系等業內主流長連線方案,談一談長連線技術的各個方面。
本文會包括下面的技術點:
- 長連線與Http短連線、Keep-Alive傻傻分不清
- 你為什麼需要長連線
- 長連線何時會斷開
- 如何建立穩定長連線
- Mars智慧心跳機制
- 長連線資料協議及加密
- 長連線通道建設及容災
除了大家常用的Http短連線,大型App幾乎都會搭建一套完整的TCP長連線
網路通道。我們先來看下美團Shark長連線
的線上資料:
圖片來源 《美團點評行動網路優化實踐》
上面兩張圖片對比了長/短連線的成功率和網路延時資料,這兩個是網路模組最重要的衡量指標。可以看出,無論是成功率,還是網路延時,長連線都明顯優於短連線。
另外,大家都知道微信的訊息收發非常即時,這便歸功於背後穩定高可用的長連線系統。實際上,微信除了訊息收發,其他的小資料通訊都是通過長連線來實現的。
下面我們來講解一些長連線的一些核心技術點。
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
資料包給服務端,探測一下伺服器是否還在響應。它的功能類似心跳包,只是間隔太長,不適合做真正的心跳包。
II. 你為什麼需要長連線
那麼,相比Http短連線,長連線技術能帶來什麼好處呢?
1. 不同域名的請求可以複用同一個長連線通道
以前我們不同域名的請求,需要做對應的DNS請求,然後建立對應的Http連線。上篇文章裡說的Http連線池
在不同域名下不可複用,需要重新建立連線。這些都是一些資源開銷,但是如果通過長連線通道,那域名只是這個請求裡的一個欄位,可以直接複用同一條長連線通道。
2. 不依賴DNS,無DNS耗時和劫持等問題
上文中我們提到了HttpDNS
,雖然它比系統DNS更優,但終歸還是要做DNS操作。而長連線都是IP直接連線,因此沒有DNS相關的開銷和耗時。
3. 如果有大量網路請求,可以明顯減少網路延時,節省頻寬
對於大型App而言,存在繁多密集的網路請求,這中間就會存在非常多次的Http斷開和重新連線,浪費了很多時間和頻寬。而通過長連線通道的話,則沒有這部分耗時,直接傳輸二進位制資料即可,節省了每次連線裡Header之類的頻寬開銷。
4. 服務端主動Push資料到客戶端
對於上面提到的微信訊息接收等場景,如果需要客戶端主動去輪詢,則會頻繁發起請求,對於伺服器會產生很大的負載壓力,浪費頻寬流量。而通過長連線,服務端可以主動把訊息下發給客戶端,做到最高實時性,且節省流量。
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程式碼裡我們可以看到,它將長連線邏輯單獨提取到了一個獨立的程式裡。這個程式只做網路互動,消耗的記憶體等資源自然較少,從而減少了被系統回收的概率。
2. 長連線程式復活
程式被殺難以避免,不過可以通過AlarmReceiver、 ConnectReceiver、BootReceiver,達到程式的及時喚醒。
當然,程式保活是一個比較大的話題,而且不恰當的程式保活也會對系統體驗造成危害。這裡就不深究了。
3. 心跳機制
對於心跳包很多人誤以為只是用來定期告訴服務端我們的狀態,實際並非如此。
上面我們提到了 NAT 超時,即如果App一段時間內不活躍,會導致運營商那裡刪除我們的公網IP對映關係,這會導致我們的TCP長連線斷開。因此,我們需要通過心跳機制來保證App的活躍度,防止發生 NAT 超時。
4. 斷開重連
線上上執行時,長連線很有可能會由於網路切換之類的原因斷開。這時,我們需要儘快發現
長連線斷開,並立即重連
。一般有下面幾種做法:
- 建立Receiver,監控網路狀態,如果網路發生切換則立即重連;
- 監控服務端心跳包回包,如果連續5次沒有收到回包,則認為長連線已經失效;
- 設定心跳包超時限制,如果超過時間還沒有收到心跳回包,則重連,這種方式比較耗電;
- 等socket IO異常丟擲,不過耗時太長,需要15s左右才能發現。
V. Mars智慧心跳機制
1. 固定心跳機制
上面我們說了,心跳機制主要是為了防止 NAT 超時,外網IP地址失效。因此,一般的做法就是在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. 長連線通道建設及容災
上面講了長連線的優勢,那我們該如何搭建整個長連線通道呢?這裡我們以美團的長連線通道為例子進行說明,各大廠的方案也是類似的。
上面是一個簡圖,大體流程如下:- 客戶端與代理長連伺服器建立長連線,代理伺服器可全國多地部署,在建立長連時可以選擇最近的伺服器IP就近接入;
- 長連線建立好後,客戶端對要傳送的二進位制資料進行加密並傳輸;
- 代理伺服器收到後,可以通過內部專線或普通Http請求來訪問業務伺服器;
- 如果長連線出現問題導致不可用,為保障客戶端執行,需要立即降級成普通Http短連或者UDP通道。
小結
本文結合了國內大廠如騰訊、美團等長連線框架,針對長連線這個技術點做了完整的介紹和剖析,如有不對或疑問,歡迎留言。
謝謝。
wingjay
《億級Android架構》小專欄介紹
業務的快速增長離不開穩定可靠的架構。《億級Android架構》小專欄會基於作者實際工作經驗,結合國內大廠如阿里、騰訊、美團等基礎架構現狀,嘗試談談如何設計一套好的架構來支援業務從0到1,甚至到億,希望與大家多多探討。
本專欄主要內容:
- 當前大廠有哪些Android架構;
- 這些架構能解決什麼問題;
- 這些架構的原理是什麼;
- 學習這些架構對我們自身的意義。
《億級Android架構》小專欄文章列表:
參考:
《Android端訊息推送總結:實現原理、心跳保活、遇到的問題等》
公眾號,專注於Android、Java、大前端等技術領域,也包含程式猿成長、跳槽等內容。