聊聊HTTPS环境DNS优化:美图App请求耗时节约近半案例
活動(dòng)預(yù)告:12月22~23,GIAC全球互聯(lián)網(wǎng)架構(gòu)大會(huì)將于上海舉行,本周新增阿里,京東、LinkedIn等多名講師出席,參看文末了解更多詳情。
導(dǎo)讀:移動(dòng)互聯(lián)網(wǎng)時(shí)代,APP 廠商之間的競爭非常激烈,而良好的用戶體驗(yàn)是必須優(yōu)先考慮的,美圖產(chǎn)品以高顏值著稱,對(duì)產(chǎn)品的用戶體驗(yàn)非常重視。從技術(shù)的角度來看,客戶端的體驗(yàn)優(yōu)化當(dāng)中 DNS 優(yōu)化是非常關(guān)鍵的一環(huán),怎么降低 DNS 的耗時(shí),怎么減少域名劫持等問題,都是大家需要重點(diǎn)解決的研發(fā)問題。本文介紹美圖 DNS 優(yōu)化的實(shí)踐,作者從原理到效果,整體講解的非常全面,值得學(xué)習(xí)和借鑒。
DNS 服務(wù)作用于網(wǎng)絡(luò)連接之前,將域名解析為 IP 地址供后續(xù)流程進(jìn)行連接。
DNS 查詢時(shí),會(huì)先在本地緩存中嘗試查找,如果不存在或是記錄過期,就繼續(xù)向 DNS 服務(wù)器發(fā)起遞歸查詢,這里的 DNS 服務(wù)器一般就是運(yùn)營商的 DNS 服務(wù)器。
在這過程中,會(huì)產(chǎn)生一些不可控的問題。
美圖的移動(dòng)端產(chǎn)品在實(shí)際用戶環(huán)境下會(huì)面臨 DNS 劫持、耗時(shí)波動(dòng)等問題,這些 DNS 環(huán)節(jié)的不穩(wěn)定因素,導(dǎo)致后續(xù)網(wǎng)絡(luò)請(qǐng)求被劫持或是直接失敗, 對(duì)產(chǎn)品的用戶體驗(yàn)產(chǎn)生不好的影響。?
為此,我們對(duì)移動(dòng)端產(chǎn)品的 DNS 解析進(jìn)行了優(yōu)化探索,產(chǎn)生了相應(yīng)的 SDK。在這過程中,我們參考借鑒了業(yè)內(nèi)的主流方案,也進(jìn)行了一些實(shí)踐上的思考。
下面的內(nèi)容會(huì)主要以 Android 平臺(tái)來進(jìn)行說明。
LocalDNS VS? HTTP DNS
在長期的實(shí)踐中,互聯(lián)網(wǎng)公司發(fā)現(xiàn) LocalDNS 會(huì)存在如下幾個(gè)問題:
域名緩存: 運(yùn)營商 DNS 緩存域名解析結(jié)果,將用戶導(dǎo)向網(wǎng)內(nèi)緩存服務(wù)器;
解析轉(zhuǎn)發(fā) & 出口 NAT: 運(yùn)營商 DNS 轉(zhuǎn)發(fā)查詢請(qǐng)求或是出口 NAT 導(dǎo)致流量調(diào)度策略失效;
為了解決 LocalDNS 的這些問題,業(yè)內(nèi)也催生了 HTTP DNS 的概念,它的基本原理如下:
原本用戶進(jìn)行 DNS 解析是向運(yùn)營商的 DNS 服務(wù)器發(fā)起 UDP 報(bào)文進(jìn)行查詢,而在 HTTP DNS 下,我們修改為用戶帶上待查詢的域名和本機(jī) IP 地址直接向 HTTP WEB 服務(wù)器發(fā)起 HTTP 請(qǐng)求,這個(gè) HTTP WEB 將返回域名解析后的 IP 地址。
比如 DNSPod 的實(shí)現(xiàn)原理如下:
相比 LocalDNS, HTTP DNS 會(huì)具備如下優(yōu)勢(shì):?
根治域名解析異常: 繞過運(yùn)營商的 DNS,向具備 DNS 解析功能的 HTTP WEB 服務(wù)器發(fā)起查詢;
調(diào)度精準(zhǔn): HTTP DNS 能夠直接獲取到用戶的 IP 地址,從而實(shí)現(xiàn)準(zhǔn)確導(dǎo)流;?
擴(kuò)展性強(qiáng): 本身基于 HTTP 協(xié)議,可以實(shí)現(xiàn)更強(qiáng)大的功能擴(kuò)展;
那么,是否直接全部走 HTTP DNS 呢?
美圖移動(dòng)端 DNS 優(yōu)化策略探索
HTTP DNS 相比 LocalDNS 存在一些優(yōu)勢(shì), 然而 HTTP DNS 本身也是存在一定的成本問題。
美圖的產(chǎn)品線豐富,涉及的域名也較為廣泛,為了適應(yīng)各產(chǎn)品的實(shí)際場景,在實(shí)踐中我們?cè)O(shè)計(jì)了較為靈活的策略控制。?
首先,在策略上我們并未完全放棄 LocalDNS。
一個(gè) App 涉及的域名眾多,在策略上我們能夠配置其核心 API 域名走 HTTP DNS,而對(duì)于非核心請(qǐng)求我們?nèi)韵M葒L試走 LocalDNS, 在異常情況下才升級(jí)走 HTTP DNS。
那么如何判斷 LocalDNS 的異常情況呢??
我們選擇了幾個(gè)指標(biāo)來衡量一個(gè) DNS 服務(wù)器的質(zhì)量情況:?
IP 記錄的 TTL 時(shí)間: 在 DNS 劫持發(fā)生的情況下,返回的 TTL 可能會(huì)有非常大的值;
解析耗時(shí): 如果一個(gè) DNS 服務(wù)器解析耗時(shí)不理想,那么它也不是我們希望的;?
返回的 IP 的可連接性: 對(duì)返回的 IP 進(jìn)行質(zhì)量測試,如果連接狀況不佳,那么這個(gè) DNS 服務(wù)器有劫持的可疑;
在 Android 平臺(tái)上,通過系統(tǒng)方法獲得的解析結(jié)果信息是非常有限的,上面的指標(biāo)有的將無法獲取,因此在實(shí)踐中我們會(huì)自己去構(gòu)造 DNS 查詢報(bào)文,向運(yùn)營商的多個(gè) DNS 服務(wù)器發(fā)起查詢。
通過上面幾個(gè)指標(biāo)的綜合評(píng)定,當(dāng) LocalDNS 表現(xiàn)不佳的時(shí)候,策略上我們將升級(jí)走 HTTP DNS,嘗試讓用戶獲取更好的 DNS 解析效果。
在 DNS 解析環(huán)節(jié),還有一個(gè)我們比較關(guān)心的指標(biāo),那就是 DNS 解析的耗時(shí):
LocalDNS 在過期的情況下,會(huì)發(fā)起遞歸查詢,這個(gè)時(shí)間是不可控的,在部分情況下甚至能達(dá)到數(shù)秒級(jí)別;?HTTP DNS 相對(duì)會(huì)好一些,但正常來看,也會(huì)有200ms 左右的耗時(shí)。?這個(gè)時(shí)間能否再優(yōu)化一些呢??
我們 SDK 在本地構(gòu)建了自己的記錄緩存池,每次通過 LocalDNS 或是 HTTP DNS 解析得到記錄都存在緩沖池中。
當(dāng)然,這個(gè)是普遍的做法,系統(tǒng)底層的 netdb 庫也是這樣實(shí)現(xiàn)。
區(qū)別在于我們做了一個(gè)小改動(dòng):對(duì)于過期的記錄我們采用懶更新的策略,當(dāng)查到過期的緩存記錄時(shí),先返回過期記錄給用戶,同時(shí)再異步重新發(fā)起 DNS 查詢更新緩存記錄。
這個(gè)小改動(dòng)能夠保證我們二次解析時(shí)都能命中本地緩存,極大地降低 DNS 解析耗時(shí),不過它也帶來了一定的風(fēng)險(xiǎn)性。
因此實(shí)踐中,我們也會(huì)添加異步定期的 DNS 記錄緩存池掃描功能,及時(shí)發(fā)現(xiàn)緩存中的過期記錄并進(jìn)行更新,也降低 App 命中過期記錄的情況。
無侵入的 SDK 接入方式探索
在 DNS 優(yōu)化的實(shí)踐中,我們遇到最大的問題,倒不是策略層面設(shè)計(jì)問題,而是我們的 DNS SDK 運(yùn)用到實(shí)際 App 產(chǎn)品業(yè)務(wù)上的姿勢(shì)問題。
業(yè)內(nèi)對(duì) HTTP DNS 在實(shí)際業(yè)務(wù)中的接入方式多采用 IP 直連的形式,
即原本直接請(qǐng)求 http://www.meitu.com,現(xiàn)在我們先調(diào)用 SDK 進(jìn)行域名解析,拿到 IP 地址比如 1.1.1.1,然后替換域名為: http://1.1.1.1/;?
這樣操作之后, 由于 URL 中 HOST 已經(jīng)是 IP 地址,網(wǎng)絡(luò)請(qǐng)求庫將跳過域名解析環(huán)節(jié),直接向 1.1.1.1 服務(wù)器發(fā)起 HTTP 請(qǐng)求。
在實(shí)際操作中,對(duì)于 IP 直連的方案我們踩了不少的坑。
首先,對(duì)于 HTTP 請(qǐng)求,采用 IP 直連的方案后,我們還是需要進(jìn)行的一個(gè)操作是手動(dòng)配置 Header 中的 HOST :
URL htmlUrl = new URL("http://1.1.1.1/");
HttpURLConnection connection = (HttpURLConnection) htmlUrl.openConnection();
connection.setRequestProperty("Host","www.meitu.com");
HTTP 協(xié)議相對(duì)比較容易,只需要處理 HOST,那么 HTTPS 呢?
發(fā)起HTTPS請(qǐng)求首先需要進(jìn)行 SSL/TLS 握手,其流程如下:?
客戶端發(fā)送 Client Hello,攜帶隨機(jī)數(shù)、支持的加密算法等信息;?
服務(wù)端收到請(qǐng)求后,選擇合適的加密算法,連同公鑰證書、隨機(jī)數(shù)等信息返回給客戶端;
客戶端檢驗(yàn)服務(wù)端證書的合法性,計(jì)算產(chǎn)生隨機(jī)數(shù)并用證書公鑰加密發(fā)送給服務(wù)端;?
服務(wù)端通過私鑰獲取隨機(jī)數(shù)信息,基于之前的交互信息計(jì)算得到協(xié)商密鑰并通知給客戶端;
客戶端驗(yàn)證服務(wù)端發(fā)送的數(shù)據(jù)和密鑰,通過后雙方握手完成,開始進(jìn)行加密通信;
在我們采用 IP 直連的形式后,上述 HTTPS 的第三步會(huì)發(fā)生問題, 客戶端檢驗(yàn)服務(wù)端下發(fā)的證書這動(dòng)作包含兩個(gè)步驟:?
客戶端用本地保存的根證書解開證書鏈,確認(rèn)服務(wù)端的證書是由可信任的機(jī)構(gòu)頒發(fā)的。
客戶端需要檢查證書的 Domain 域和擴(kuò)展域是否包含本次請(qǐng)求的 HOST。
證書的驗(yàn)證需要這兩個(gè)步驟都檢驗(yàn)通過才能夠進(jìn)行后續(xù)流程,否則 SSL/TLS 握手將在這里失敗結(jié)束。
由于在 IP 直連下,我們給網(wǎng)絡(luò)請(qǐng)求庫的 URL 中 host 部分已經(jīng)被替換成了 IP 地址,
因此證書驗(yàn)證的第二步中,默認(rèn)配置下 “本次請(qǐng)求的 HOST” 會(huì)是一個(gè) IP 地址,這將導(dǎo)致 domain 檢查不匹配,最終 SSL/TLS 握手失敗。
那么該如何解決這個(gè)問題??
解決 SSL/TLS 握手中域名校驗(yàn)問題的方法在于我們重新配置 HostnameVerifier, 讓請(qǐng)求庫用實(shí)際的域名去做域名校驗(yàn),
代碼示例如下:?
final URL htmlUrl = new URL("https://1.1.1.1/");
HttpsURLConnection connection = (HttpsURLConnection) htmlUrl.openConnection();
connection.setRequestProperty("Host","www.meipai.com");
connection.setHostnameVerifier(new HostnameVerifier() {
? ? ? @Override
? ? ? public boolean verify(String hostname, SSLSession session) {
? ? ? ? ? return HttpsURLConnection.getDefaultHostnameVerifier()
? ? ? ? ? ? ? ? ? ? .verify("www.meipai.com",session);
? ? ? }
});
我們又解決了一個(gè)問題,那么 IP 直連下, HTTPS 的問題都搞定了嗎?
沒有,HTTPS 還有 SNI 的場景要特殊處理。
SNI(Server Name Indication)是為了解決一個(gè)服務(wù)器使用多個(gè)域名和證書的SSL/TLS擴(kuò)展。它的基本工作原理如下:
服務(wù)端配置有多個(gè)域名和對(duì)應(yīng)的證書。客戶端在與服務(wù)器建立SSL鏈接之時(shí),先發(fā)送自己要訪問站點(diǎn)的域名。
服務(wù)器根據(jù)這個(gè)域名返回一個(gè)合適的證書。
跟上面 Domain 校驗(yàn)的情況類似,這里的網(wǎng)絡(luò)請(qǐng)求庫默認(rèn)發(fā)送給服務(wù)端的 "要訪問站點(diǎn)的域名" 就是我們替換后的 IP 地址。
服務(wù)端在收到這樣一個(gè) IP 地址形式的域名后將是一臉懵逼,找不到對(duì)應(yīng)的證書,最后只好下發(fā)一個(gè)默認(rèn)的域名證書回來。
接下來發(fā)生的是,客戶端在檢驗(yàn)證書的 Domain 域時(shí),怎么也檢查不通過,因?yàn)榉?wù)端下發(fā)的證書本來就不是對(duì)應(yīng)該域名的。
最后 SSL/TLS 握手失敗告終。
上述這個(gè) SNI 場景下的問題,我們是否有辦法解決呢??
可以解決,需用客戶端重新定制 SSLSocketFactory , 不過修改的代碼相對(duì)較多,這里就不列舉了。
如果我們 SDK 要接入到 App 實(shí)際業(yè)務(wù)中,到 HTTPS SNI 場景處理這里,相信很多同學(xué)都崩潰了,接入的工作量其實(shí)也不低。
很多情況下可能就做了妥協(xié),只有 Okhttp 場景才使用這個(gè) SDK,因?yàn)?Okhttp 本身支持 DNS 替換,沒有上面那些問題。
在美圖的實(shí)踐中,我們不僅僅希望 Okhttp 的請(qǐng)求才進(jìn)行這個(gè) DNS 優(yōu)化,我們希望在 App H5 頁面加載、播放器播放等場景也能應(yīng)用相應(yīng)的優(yōu)化。
在這樣的需求下,IP 直連的接入方案帶來的接入工作量其實(shí)不低,甚至需要改動(dòng)到部分輪子。
在最初的實(shí)踐中,我們也的確嘗試了落實(shí) IP 直連 到各個(gè)模塊,然而即使克服了改造的工作量問題,實(shí)際運(yùn)行上還是會(huì)有不少坑。?
那么,有沒有更合適的一種技術(shù)方案,能夠降低 我們 DNS SDK 的接入工作量,也能兼顧各種使用場景,比如 HTTPS、RTMP 協(xié)議等?
基于這樣的目標(biāo),我們?cè)趯?shí)踐中嘗試探索了一種對(duì)業(yè)務(wù)集成友好的無侵入式 DNS SDK 集成方案。下面我們以 Android 平臺(tái)進(jìn)行說明。
我們知道在 Java 層面上進(jìn)行 DNS 解析的基本方式是調(diào)用如下方法:
InetAddress.getAllByName("www.meipai.com");
Android 平臺(tái)上常用的 Okhttp、HttpUrlConnection 等網(wǎng)絡(luò)請(qǐng)求庫都會(huì)依賴這個(gè)形式的 DNS 解析。
我們深入分析 InetAddress 的運(yùn)行流程,其大致如下:?
在上述流程中我們可以知道,InetAddress 會(huì)有到 AddressCache 嘗試獲取已緩存記錄的動(dòng)作,而這里 AddessCache 是一個(gè) static 的 map 結(jié)構(gòu)變量。
因此,在這里我們來對(duì)它做點(diǎn)小手腳 :
模仿系統(tǒng)的 AddressCache 構(gòu)造一個(gè)我們自己的 AddressCahce 結(jié)構(gòu),不過它的 get 方法被替換為從我們 SDK 獲取解析記錄。
通過反射的形式用我們修改后的 AddressCache 替換掉系統(tǒng)的 AddressCache 變量。
這個(gè)偷天換日的操作之后,HttpsUrlConnection 等 Java 層網(wǎng)絡(luò)請(qǐng)求在進(jìn)行 DNS 解析時(shí)就會(huì)是這樣一個(gè)流程:
通過這個(gè)形式,我們能夠完美解決 Java 層的 DNS SDK 接入問題,對(duì)于業(yè)務(wù)方來說,他們并不需要做任何 URL 替換操作,對(duì)應(yīng)的 HTTPS 場景下的問題也不復(fù)存在。
Java 層的接入解決了, 那么 Native 層呢??
我們知道在 Android 平臺(tái)上,像 WebView、播放器等模塊他們進(jìn)行網(wǎng)絡(luò)連接的操作都是在 native 層進(jìn)行的,并不會(huì)調(diào)用到 Java 層的 InetAddress 方法。
首先在 C/C++ 層,我們知道進(jìn)行 DNS 解析會(huì)使用 getaddrinfo 或是 gethostbyname2 這兩個(gè)函數(shù)。
另外我們還知道,在 Android 等 Linux 系統(tǒng)下,對(duì)于 .so 這類可共享對(duì)象文件會(huì)是 ELF 的文件格式。
因此從這些已知信息,我們可以得到下列一些情況:?
我們的 App 中 a.so 中直接使用到了系統(tǒng) libc.so 中的 getaddrinfo 函數(shù),
那么根據(jù) ELF 文件規(guī)范,在 a.so 的 .rel.plt 表中會(huì)有如下關(guān)系定義: getaddrinfo ==> 0xFFFFFF 。
.rel.plt 表中的映射關(guān)系為 a.so 的運(yùn)行指出了 getaddrinfo 這個(gè)外部符號(hào)在當(dāng)前內(nèi)存空間中的絕對(duì)地址。
正常情況下,a.so 中執(zhí)行到 getaddrinfo 的函數(shù)流程是這樣的:
那么在這里,我們是否可以手動(dòng)修改這個(gè)映射表內(nèi)容,把 getaddrinfo 的內(nèi)存地址替換成我們的 my_getaddrinfo 地址呢?
這樣,a.so 在實(shí)際運(yùn)行時(shí)會(huì)被拐到我們的 my_getaddrinfo 中??
實(shí)際上,確實(shí)是可行的。 我們嘗試在 SDK 啟動(dòng)后,對(duì) a.so 的 .rel.plt 表進(jìn)行修改,達(dá)到接管 a.so DNS 的目的,
修改后的 a.so 運(yùn)行流程如下:?
通過上面的方式,我們能夠比較完美地接管 App 在 Java 層 和 Native 層 DNS 過程,實(shí)現(xiàn)業(yè)務(wù)方無任何額外改動(dòng)的情況下運(yùn)用我們的 DNS SDK 優(yōu)化效果。
SDK 上線后的效果表現(xiàn)
在實(shí)際運(yùn)用中,我們?nèi)〉昧吮容^好的效果。得益于 DNS SDK 在命中本地緩存率上的策略優(yōu)化,我們的移動(dòng)端產(chǎn)品在網(wǎng)絡(luò)請(qǐng)求中 DNS 解析環(huán)節(jié)耗時(shí)得到降低,
從實(shí)際監(jiān)控?cái)?shù)據(jù)來看,完整網(wǎng)絡(luò)請(qǐng)求的耗時(shí)也能夠降低 100ms 左右。
通過 HTTP DNS 的引入和 LocalDNS 優(yōu)化升級(jí)策略,我們的網(wǎng)絡(luò)請(qǐng)求成功率有提升,在未知主機(jī)等具體錯(cuò)誤率表現(xiàn)出下降的趨勢(shì)。
由于 SDK 層面本身做好了靈活的策略配置,我們通過線上監(jiān)控和配置也讓各產(chǎn)品在效益和成本之間取得一個(gè)最佳的平衡點(diǎn)。
號(hào)外:
美圖架構(gòu),專注于虛擬化平臺(tái)建設(shè)、流媒體、云存儲(chǔ)、千萬同時(shí)在線的通訊服務(wù)、音視頻編解碼等基礎(chǔ)設(shè)施建設(shè),現(xiàn)急需相關(guān)領(lǐng)域愛好者加入,工作地點(diǎn)可自由選擇北京、廈門、深圳,待遇從優(yōu),美女多多?,F(xiàn)緊缺崗位如下:
Go/C 開發(fā)工程師:我們 50% 以上代碼使用 Go
Java 開發(fā)工程師
音視頻編解碼研究人員
Docker 虛擬化底層研發(fā)工程師
有興趣者請(qǐng)聯(lián)系:yt@meitu.com?or yxq@meitu.com?or?zl3@meitu.com
美圖技術(shù)沙龍深圳站于12月底開啟,歡迎關(guān)注官方公眾號(hào),關(guān)注最新動(dòng)向。微信號(hào): MTtechsalon
參考閱讀:
GIAC全球互聯(lián)網(wǎng)架構(gòu)大會(huì)最新日程
App域名劫持之DNS高可用 - 開源版HttpDNS方案詳解
手機(jī)QQ上傳速度提升8倍秘訣:解決速度與成功率的“魚翅”項(xiàng)目
騰訊TMQ團(tuán)隊(duì)移動(dòng)App的網(wǎng)絡(luò)優(yōu)化:24小時(shí)流量優(yōu)化到原來15%歷程
活動(dòng)預(yù)告:
12 月 22 ~ 23 日,GIAC 全球互聯(lián)網(wǎng)架構(gòu)大會(huì)將于上海舉行。GIAC 是高可用架構(gòu)技術(shù)社區(qū)推出的面向架構(gòu)師、技術(shù)負(fù)責(zé)人及高端技術(shù)從業(yè)人員的技術(shù)架構(gòu)大會(huì)。GIAC 于 2016 年 12 月成功舉辦了第一屆,今年的 GIAC 已經(jīng)有騰訊、阿里巴巴、百度、平安、餓了么、攜程、七牛、螞蟻金服、羅輯思維、摩拜、唯品會(huì),LinkedIn, Pivotal, Mesosphere, AdMaster, Hulu 等公司專家出席。
本期 GIAC 大會(huì)上,移動(dòng)開發(fā)相關(guān)部分精彩的議題如下:
注:出品人及演講議題以最新官網(wǎng)為準(zhǔn),全部最新演講議題請(qǐng)點(diǎn)擊“閱讀原文”至官網(wǎng)查看。
參加 GIAC,盤點(diǎn)年底最新技術(shù),目前單人購買優(yōu)惠 600 元,多人購買有更多優(yōu)惠。點(diǎn)擊“閱讀原文”了解大會(huì)更多詳情。
總結(jié)
以上是生活随笔為你收集整理的聊聊HTTPS环境DNS优化:美图App请求耗时节约近半案例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蛮力法查找有序数列c语言,算法——蛮力法
- 下一篇: Unity用GL接口实现画格子