Android 架構之高可用行動網路連線
本文主要談一下在真實的網路環境下,存在哪些常見的網路不可用原因,以及大多數公司是如何解決並兜底,從而達到 高可用連線
這個目標的。
文章會從下面幾方面進行闡述:
- DNS 劫持與可靠 IP 獲取
- HttpDNS
- 內建 IP 列表 + 自動測速
- IP 列表的快取更新策略
- IP 列表可用性兜底策略
- 針對弱網的多 IP 複合連線測速
- 自主網路診斷
DNS 劫持與可靠 IP 獲取
我們知道,大多數的網路請求第一步就是 DNS 過程,經過 1-RTT 的時間將域名轉化為 IP 地址,然後再去發起請求。但是,有相關經驗的開發者應該瞭解,DNS 過程不僅耗時不穩定(3G 下 200ms,4G 下 100ms),而且可能解析失敗,甚至被劫持,將使用者匯入到了錯誤的 IP 地址。如果攻擊者自己做一個仿冒的網站,劫持你的 DNS 並將 IP 轉到這個假網站上,可能會造成很大的使用者資料洩漏和公司品牌損失。
為了解決這個問題,獲得可靠的 IP 列表,現有大廠會採用下面一些方案:
1. HTTPDNS
比如阿里雲和騰訊雲都推出了自己的 HttpDNS 服務,在全國多地部署相關的伺服器提供安全解析 DNS 服務。
基本的原理就是通過發起 Http 請求到 HttpDNS 伺服器,獲取某個域名對應的可用 IP 列表。這個 IP 列表可以根據使用者當前的地點進行返回,而且預設會進行 IP 測速,按速度排序。同時,伴隨這 IP 列表,伺服器還會下發一個快取有效時間 TTL,有了這個時間,客戶端可以放心的將 IP 列表快取在本地,並在即將過期前及時去更新 IP 列表,保證每次網路請求都可以使用 **當前最優 **的 IP 地址。
2. 內建 IP 列表 + 自動測速
當然,自建 HttpDNS 服務需要一定規模的機房部署、大量的客戶端測速資料上報、全球 IP 庫收集等,需要不少的投入。因此,有些公司比如 **攜程 **就採用了更加輕量一點的方案: 內建 IP 列表
。
具體原理如下:
在 APK 打包時會內建一份 IP 列表進去。當 App 啟動時,這些 IP 的權重相同,此時會隨機從裡面獲取 IP 來使用。但是這有個問題,對不同地區的使用者而言,最優 IP 肯定是不同的。比如對於上海的使用者而言,上海區伺服器的 IP 肯定是最快的,而對於深圳的使用者而言,華南區 IP 才是最快的。因此,在 App 執行過程中,我們會通過依次對 IP 列表逐個進行 Ping 測速 ,根據測速結果動態變更 IP 的權重,然後提供給網路連線使用。

IP 列表的快取更新策略
通過 HttpDNS 或內建 IP 列表
的方案,我們可以為網路層提供一份相對可靠的 IP 地址作為快取,每次需要發起請求時,直接從快取裡讀取這份 IP 列表即可建立 IP 直連。
那新的問題來了,行動網路是在不斷變化的。最常見的場景,比如我們從 Wi-Fi 切換到了 4G,獲取進入電梯後從 4G 降級成 3G,或者我們從 A Wi-Fi 換到了 B Wi-Fi,這都意味著我們的 **網路鏈路變更 **了。那麼, 之前快取的 IP 列表是否仍然可用,或者仍然最優呢?
顯然並不一定,比如從 Wi-Fi 切到了移動 4G,背後整條網路鏈路都不同了,之前的 IP 列表很有可能不是最優的了,極端情況下可能某些 IP 地址也不可用了。因此,我們需要最好 IP 列表的及時更新,保證無論網路如何切換,我們都能使用最優的 IP 地址列表。
具體有下面幾種方式:
TTL 過期時間
另外,IP 列表快取應該對不同網路型別、網路標識有對應的一份快取,可以使用 網路型別(3G、4G、Wi-Fi 等)+ 網路標識(SSID、ispCode 等)
作為 快取 Key
,當網路切換時,使用 Key 去查詢快取。
這些快取可以持久化到多個檔案,以 Key 作為檔名,同時可以基於當前網路狀態,快取一份 IP 列表到記憶體供使用,當網路狀態變化,則重新整理記憶體快取。

IP 列表可用性兜底策略
通過更新機制,我們可以保證本地 IP 列表快取動態更新的及時性。那麼, 如果 HttpDNS 伺服器出現故障呢,或者首次開啟 App,HttpDNS 還沒有完成,或者大面積 DNS 劫持等,怎麼辦呢?
所以說,除了及時獲取最優 IP 列表,我們還要考慮,如果獲取不到 IP 列表,如何進行兜底?保證使用者的網路請求不受影響。
在線上執行中,可以採取下面四組 IP 兜底策略,按優先順序排列如下:
- HttpDNS IP:即大廠自建的 HttpDNS 服務獲取動態 IP;
- DNS IP:即常規 Local DNS 獲取 IP;
- Auth IP:通過配置下發的動態保底 IP 列表;
- Hardcode IP:本地寫死的保底 IP 列表
前面兩種動態 IP 不用多說,大家都清楚,這兩者可以動態獲取 IP,效果最好。但是,如果發生故障,導致這兩個方案都不可用,比如大面積 DNS 劫持之類的,這時客戶端必須能夠自動降級到 靜態兜底 IP
,保證網路服務可用。
但這也可能存在一個問題,就是 靜態兜底 IP
對應伺服器訪問量可能會突然暴增,如果峰值太高可能造成更大的危害如 雪崩
。因此,除了內建靜態兜底 IP,還需要為客戶端提供一個可通過 配置動態下發
的 兜底 IP 列表
,可以做到負載均衡,將流量分散到不同機器上。而且這些靜態 IP 貴精不貴多,並且要有高可用的後臺服務保證,作為全域性網路服務的兜底。
針對弱網的多 IP 複合連線測速
通過上面的幾套方案,可以保證使用者能夠 高可用的獲取最優 IP 列表
,提高使用者訪問速度,而且能應對各種複雜的網路狀態。
那麼現在考慮這樣一種情況,上面的 IP 列表我們能夠正常的獲取,但是, 使用者處於弱網狀態下,IP 連線成功率很低,怎麼辦呢?
針對弱網一般有兩種方式:
- 序列連線:先連線第一個 IP,直到發生了超時,再去對第二個 IP 建連;
- 並行連線:同時對多個 IP 建立連線,哪個連成功了就用哪個;
這兩種方案的缺點是:序列連線可能需要很長時間的試錯,才能找到可用的 IP,而且這裡還取決於如何選擇超時時間,如果超時時間較長,則需要很長時間才能找到可用 IP;如果很短,則可能會漏掉一些相對優質的 IP,不斷去嘗試新 IP,惡性迴圈;而並行連線則會對服務端造成極大的連線負載壓力和一定程度的浪費,對於電量也有一定程度開銷。
因此,這裡我們介紹下 Mars 裡的複合連線策略
作為學習參考:

在弱網狀態下,依次發起對 5 組 IP+Port 的連線,10s 作為超時時間。當前一個連線發起了 4s 鍾還未成功,則立即發起下一個連線,以此類推。當其中有一個連線建立成功,則立即停止其他連線。這樣的方式可以兼備序列連線和並行連線的優勢:較快找到可用 IP,同時對於伺服器不會造成過大的連線壓力。至於這個超時時間 10s,則可以通過上報資料來動態統計,找到一個合理的超時時間。

自主網路診斷
在真實的線上環境我們發現,即使 IP 和後臺服務均有效,仍有一部分使用者的網路連線會出現失敗。而此時單純從 IP 地址已經分析不出原因,很有可能是該使用者的網路鏈路上存在問題導致連線失敗。
這時就需要我們主動去探測這個使用者的網路連線並診斷整條連線鏈路。
因此,為了準確瞭解線上網路錯誤的使用者的真實情況,我們會在客戶端裡內建網路診斷策略,通過 Ping
或者 TraceRoute
探測使用者手機到伺服器的整條網路鏈路上的情況,並將資料儲存上報,用於分析使用者的真實網路錯誤原因。
Ping 大家比較熟悉,目的是為了測試另一臺主機是否可達,向目標主機發送 Echo 包並等待回包;而 TraceRoute 可以獲取資料包在 IP 網路經過的路由器的 IP 地址,原理如下:
- 程式是利用增加存活時間(TTL)值來實現其功能的。每當資料包經過一個路由器,其存活時間就會減 1。當其存活時間是 0 時,主機便取消資料包,併發送一個 ICMP TTL 資料包給原資料包的發出者。
- 程式發出的首 3 個數據包 TTL 值是 1,之後 3 個是 2,如此類推,它便得到一連串資料包路徑。注意 IP 不保證每個資料包走的路徑都一樣。
在 Android 上一般有兩種方式來實現這個診斷:
iputils
感興趣的可以參考文末提供的開源專案 LDNetDiagnoService
,通過診斷可以把日誌上報用於分析,並作出相關的調整和優化。
小結
本文針對如何提高網路連線的高可用性做了講解和分析,線上方案最重要考慮的就是兜底,無論發生何種問題,都要保證網路服務可用。如果使用者連我們的伺服器都連線不上,那可能會帶來非常嚴重的災難;當然,我們也要考慮伺服器負載,不能造成伺服器壓力過大,導致雪崩之類的問題。
有相關疑問歡迎隨時留言。
最後給大家分享一份非常系統和全面的Android進階技術大綱及進階資料,及面試題集
想學習更多Android知識,請加入Android技術開發交流 7520 16839
進群與大牛們一起討論,還可獲取Android高階架構資料、原始碼、筆記、視訊
包括 高階UI、Gradle、RxJava、小程式、Hybrid、移動架構、React Native、效能優化等全面的Android高階實踐技術講解效能優化架構思維導圖,和BATJ面試題及答案!
群裡免費分享給有需要的朋友,希望能夠幫助一些在這個行業發展迷茫的,或者想系統深入提升以及困於瓶頸的
朋友,在網上部落格論壇等地方少花些時間找資料,把有限的時間,真正花在學習上,所以我在這免費分享一些架構資料及給大家。希望在這些資料中都有你需要的內容。

