Android 網絡優化,使用 HTTPDNS 優化 DNS,從原理到 OkHttp 集成

1、前言

談到優化,首先第一步,確定是把一個大功能,拆分紅一個個細小的環節,再單個拎出來找到能夠優化的點,App 的網絡優化也是如此。java

在 App 訪問網絡的時候,DNS 解析是網絡請求的第一步,默認咱們使用運營商的 LocalDNS 服務。有數據統計,在這一塊 3G 網絡下,耗時在 200~300ms,4G 網絡下也須要 100ms。算法

解析慢,並非 LocalDNS 最大的問題,它還存在一些更爲嚴重的問題,例如:DNS 劫持、DNS 調度不許確(緩存、轉發、NAT)致使性能退化等等,這些纔是網絡優化最應該解決的問題。api

想要優化 DNS,如今最簡單成熟的方案,就是使用 HTTPDNS。瀏覽器

今天就來聊聊,DNS、HTTPDNS,以及在 Android 下,如何使用 OKHttp 來集成 HTTPDNS。緩存

2、DNS 和 HTTPDNS

2.1 什麼是 DNS

在說到 HTTPDNS 以前,先簡單瞭解一下什麼是 DNS?安全

在網絡的世界中,每一個有效的域名背後都有爲其提供服務的服務器,而咱們網絡通訊的首要條件,就是知道服務器的 IP 地址。服務器

可是記住域名(網址)確定是比記住 IP 地址簡單。若是有某種方法,能夠經過域名,查到其提供服務的服務器 IP 地址,那就很是方便了。這裏就須要用到 DNS 服務器以及 DNS 解析。微信

DNS(Domain Name System),它的做用就是根據域名,查出對應的 IP 地址,它是 HTTP 協議的前提。只有將域名正確的解析成 IP 地址後,後面的 HTTP 流程才能夠繼續進行下去。網絡

DNS 服務器的要求,必定是高可用、高併發和分佈式的服務器。它被分爲多個層次結構。session

  • 根 DNS 服務器:返回頂級域 DNS 服務器的 IP 地址。
  • 頂級域 DNS 服務器:返回權威 DNS 服務器的 IP 地址。
  • 權威 DNS 服務器:返回相應主機的 IP 地址。

這三類 DNS 服務器,相似一種樹狀的結構,分級存在。

當開始 DNS 解析的時候,若是 LocalDNS 沒有緩存,那就會向 LocalDNS 服務器請求(一般就是運營商),若是仍是沒有,就會一級一級的,從根域名查對應的頂級域名,再從頂級域名查權威域名服務器,最後經過權威域名服務器,獲取具體域名對應的 IP 地址。

DNS 在提供域名和 IP 地址映射的過程當中,其實提供了不少基於域名的功能,例如服務器的負載均衡,可是它也帶來了一些問題。

2.2 DNS 的問題

DNS 的細節還有不少,本文就不展開細說了,其問題總結來講就是幾點。

1. 不穩定

DNS 劫持或者故障,致使服務不可用。

2. 不許確

LocalDNS 調度,並不必定是就近原則,某些小運營商沒有 DNS 服務器,直接調用其餘運營商的 DNS 服務器,最終直接跨網傳輸。例如:用戶側是移動運營商,調度到了電信的 IP,形成訪問慢,甚至訪問受限等問題。

3. 不及時

運營商可能會修改 DNS 的 TTL(Time-To-Live,DNS 緩存時間),致使 DNS 的修改,延遲生效。

還有運營商爲了保證網內用戶的訪問質量,同時減小跨網結算,運營商會在網內搭建內容緩存服務器,經過把域名強行指向內容緩存服務器的地址,來實現本地本網流量徹底留在本地的目的。

對此不一樣運營商甚至實現都不一致,這對咱們來講就是個黑匣子。

正是由於 DNS 存在種種問題,因此牽出了 HTTPDNS。

2.3 HTTPDNS 的解決方案

DNS 不只支持 UDP,它還支持 TCP,可是大部分標準的 DNS 都是基於 UDP 與 DNS 服務器的 53 端口進行交互。

HTTPDNS 則不一樣,顧名思義它是利用 HTTP 協議與 DNS 服務器進行交互。不走傳統的 DNS 解析,從而繞過運營商的 LocalDNS 服務器,有效的防止了域名劫持,提升域名解析的效率。

這就至關於,每家各自基於 HTTP 協議,本身實現了一套域名解析,本身去維護了一份域名與 IP 的地址簿,而不是使用同一的地址簿(DNS服務器)。

聽說微信有本身部署的 NETDNS,而各大雲服務商,阿里雲和騰訊雲也提供了本身的 HTTPDNS 服務,對於咱們普通開發者,只須要付出少許的費用,在手機端嵌入支持 HTTPDNS 的客戶端 SDK,便可使用。

3、 OKHttp 接入 HTTPDNS

既然瞭解了 HTTPDNS 的重要性,接下來看看如何在 OkHttp 中,集成 HTTPDNS。

OkHttp 是一個處理網絡請求的開源項目,是 Android 端最火熱的輕量級網絡框架。在 OkHttp 中,默認是使用系統的 DNS 服務 InetAddress 進行域名解析。

InetAddress ip2= InetAddress.getByName("www.cxmydev.com");
System.out.println(ip2.getHostAddress());
System.out.println(ip2.getHostName());
複製代碼

而想在 OkHttp 中使用 HTTPDNS,有兩種方式。

  1. 經過攔截器,在發送請求以前,將域名替換爲 IP 地址。
  2. 經過 OkHttp 提供的 .dns() 接口,配置 HTTPDNS。

對這兩種方法來講,固然是推薦使用標準 API 來實現了。攔截器的方式,也建議有所瞭解,實現很簡單,可是有坑。

3.1 攔截器接入方式

1. 攔截器接入

攔截器是 OkHttp 中,很是強大的一種機制,它能夠在請求和響應之間,作一些咱們的定製操做。

在 OkHttp 中,能夠經過實現 Interceptor 接口,來定製一個攔截器。使用時,只須要在 OkHttpClient.Builder 中,調用 addInterceptor() 方法來註冊此攔截器便可。

OkHttp 的攔截器不是本文的重點,咱們仍是回到攔截器去實現 HTTPDNS 的話題上,攔截器沒什麼好說的,直接上相關代碼。

class HTTPDNSInterceptor : Interceptor{
    override fun intercept(chain: Interceptor.Chain): Response {
        val originRequest = chain.request()
        val httpUrl = originRequest.url()

        val url = httpUrl.toString()
        val host = httpUrl.host()

        val hostIP = HttpDNS.getIpByHost(host)
        val builder = originRequest.newBuilder()

        if(hostIP!=null){
            builder.url(HttpDNS.getIpUrl(url,host,hostIP))
            builder.header("host",hostIP)
        }
        val newRequest = builder.build()
        val newResponse = chain.proceed(newRequest)
        return newResponse
    }
}
複製代碼

在攔截器中,使用 HttpDNS 這個幫助類,經過 getIpByHost() 將 Host 轉爲對應的 IP。

若是經過抓包工具抓包,你會發現,本來的相似 http://www.cxmydev.com/api/user 的請求,被替換爲:http://220.181.57.xxx/api/user

2. 攔截器接入的壞處

使用攔截器,直接繞過了 DNS 的步驟,在請求發送前,將 Host 替換爲對應的 IP 地址。

這種方案,在流程上很清晰,沒有任何技術性的問題。可是這種方案存在一些問題,例如:HTTPS 下 IP 直連的證書問題、代理的問題、Cookie 的問題等等。

其中最嚴重的問題是,此方案(攔截器+HTTPDNS)遇到 https 時,若是存在一臺服務器支持多個域名,可能致使證書沒法匹配的問題。

在說到這個問題以前,就要先了解一下 HTTPS 和 SNI。

HTTPS 是爲了保證安全的,在發送 HTTPS 請求以前,首先要進行 SSL/TLS 握手,握手的大體流程以下:

  1. 客戶端發起握手請求,攜帶隨機數、支持算法列表等參數。
  2. 服務端根據請求,選擇合適的算法,下發公鑰證書和隨機數。
  3. 客戶端對服務端證書,進行校驗,併發送隨機數信息,該信息使用公鑰加密。
  4. 服務端經過私鑰獲取隨機數信息。
  5. 雙方根據以上交互的信息,生成 Session Ticket,用做該鏈接後續數據傳輸的加密密鑰。

在這個流程中,客戶端須要驗證服務器下發的證書。首先經過本地保存的根證書解開證書鏈,確認證書可信任,而後客戶端還須要檢查證書的 domain 域和擴展域,看看是否包含本次請求的 HOST。

在這一步就出現了問題,當使用攔截器時,請求的 URL 中,HOST 會被替換成 HTTPDNS 解析出來的 IP。當服務器存在多域名和證書的狀況下,服務器在創建 SSL/TLS 握手時,沒法區分到底應該返回那個證書,此時的策略可能返回默認證書或者不返回,這就有可能致使客戶端在證書驗證 domain 時,出現不匹配的狀況,最終致使 SSL/TLS 握手失敗。

這就引起出來 SNI 方案,SNI(Server Name Indication)是爲了解決一個服務器使用多個域名和證書的 SSL/TLS 擴展。

SNI 的工做原理,在鏈接到服務器創建 SSL 鏈接以前,先發送要訪問站點的域名(hostname),服務器根據這個域名返回正確的證書。如今,大部分操做系統和瀏覽器,都已經很好的支持 SNI 擴展。

3. 攔截器 + HTTPDNS 的解決方案

這個問題,其實也有解決方案,這裏簡單介紹一下。

針對 "domain 不匹配" 的問題,能夠經過 hook 證書驗證過程當中的第二步,將 IP 直接替換成原來的域名,再執行證書驗證。

而 HttpURLConnect,提供了一個 HostnameVerifier 接口,實現它便可完成替換。

public interface HostnameVerifier {
    public boolean verify(String hostname, SSLSession session);
}
複製代碼

若是使用 OkHttp,能夠參考 OkHostnameVerifier (source://src/main/java/okhttp3/internal/tls/OkHostnameVerifier.java) 的實現,進行替換。

自己 OkHttp 就不建議經過攔截器去作 HTTPDNS 的支持,因此這裏就不展開討論了,這裏只提出解決的思路,有興趣能夠研究研究源碼。

3.2 OKHttp 標準 API 接入

OkHttp 其實自己已經暴露了一個 Dns 接口,默認的實現是使用系統的 InetAddress 類,發送 UDP 請求進行 DNS 解析。

咱們只須要實現 OkHttp 的 Dns 接口,便可得到 HTTPDNS 的支持。

在咱們實現的 Dns 接口實現類中,解析 DNS 的方式,換成 HTTPDNS,將解析結果返回。

class HttpDns : Dns {
    override fun lookup(hostname: String): List<InetAddress> {
        val ip = HttpDnsHelper.getIpByHost(hostname)
        if (!TextUtils.isEmpty(ip)) {
            //返回本身解析的地址列表
            return InetAddress.getAllByName(ip).toList() 
        } else {
            // 解析失敗,使用系統解析
            return Dns.SYSTEM.lookup(hostname)
        }
    }
}
複製代碼

使用也很是的簡單,在 OkHttp.build() 時,經過 dns() 方法配置。

mOkHttpClient = httpBuilder
        .dns(HttpDns())
        .build();
複製代碼

這樣作的好處在於:

  1. 仍是用域名進行訪問,只是底層 DNS 解析換成了 HTTPDNS,以確保解析的 IP 地址符合預期。
  2. HTTPS 下的問題也獲得解決,證書依然使用域名進行校驗。

OkHttp 既然暴露出 dns 接口,咱們就儘可能使用它。

4、小結時刻

如今你們知道,在作 App 的網絡優化的時候,第一步就是使用 HTTPDNS 優化 DNS 的步驟。

全部的優化固然是以最終效果爲目的,這裏提兩條大廠公開的數據,對騰訊的產品,在接入 HTTPDNS 後,用戶平均延遲降低超過 10%,訪問失敗率降低超過五分之一。而百度 App 的 Feed 業務,Android 劫持率由 0.25% 下降到 0.05%。

此種優化方案,很是依賴 HTTPDNS 服務器,因此建議使用 阿里雲、騰訊雲 這樣相對穩定的雲服務商。

references:

【1】百度App網絡深度優化系列一 DNS 優化

【2】SIN:https://blog.csdn.net/makenothing/article/details/53292335

【3】鵝廠網事,HTTPDNS 服務詳解


公衆號後臺回覆成長『成長』,將會獲得我準備的學習資料,也能回覆『加羣』,一塊兒學習進步;你還能回覆『提問』,向我發起提問。

推薦閱讀:

關於字符編碼,你須要知道的都在這裏 | 圖解:HTTP 範圍請求 | Java 異常處理 | 安卓防止用戶關閉動畫致使動畫失效 | Git 找回遺失的代碼 | 阿里的 Alpha 助力 App 啓動速度優化

相關文章
相關標籤/搜索