[TOC]
# 國內移動端網絡所面臨的各種DNS雜癥
1)LocalDNS劫持;
2)平均訪問延遲下降;
3)用戶連接失敗率下降。
<br>
**LocalDNS劫持:** 由于HttpDNS是通過ip直接請求http獲取服務器A記錄地址,不存在向本地運營商詢問domain解析過程,所以從根本避免了劫持問題。 (對于http內容tcp/ip層劫持,可以使用驗證因子或者數據加密等方式來保證傳輸數據的可信度)
**平均訪問延遲下降:** 由于是ip直接訪問省掉了一次domain解析過程,(即使系統有緩存速度也會稍快一些‘毫秒級’)通過智能算法排序后找到最快節點進行訪問。
**用戶連接失敗率下降:** 通過算法降低以往失敗率過高的服務器排序,通過時間近期訪問過的數據提高服務器排序,通過歷史訪問成功記錄提高服務器排序。如果ip(a)訪問錯誤,在下一次返回ip(b)或者ip(c) 排序后的記錄。
<br>
<br>
# 追根溯源,國內DNS問題的根源是什么?
我們得先得了解下現在國內各ISP運營商的LocalDNS的基本情況。
**國內運營商LocalDNS造成的這些問題,可以歸為下以下3種原因:**
> 1)域名緩存;
> 2)解析轉發;
> 3)LocalDNS遞歸出口NAT。
## 域名緩存
域名緩存很好理解,就是LocalDNS緩存了騰訊的域名的解析結果,不向騰訊權威DNS發起遞歸。
**示意圖如下:**
<br>

<br>
**為何LocalDNS要把域名解析結果進行緩存呢?原因有以下幾個:**
1)保證用戶訪問流量在本網內消化:國內的各互聯網接入運營商的帶寬資源、網間結算費用、IDC機房分布、網內ICP資源分布等存在較大差異。為了保證網內用戶的訪問質量,同時減少跨網結算,運營商在網內搭建了內容緩存服務器,通過把域名強行指向內容緩存服務器的IP地址,就實現了把本地本網流量完全留在了本地的目的;
2)推送廣告:有部分LocalDNS會把部分域名解析結果的所指向的內容緩存,并替換成第三方廣告聯盟的廣告。
<br>
**以上類型的行為就是我們常說的域名緩存,域名緩存會導致用戶產生以下的訪問異常:**
A、僅對80端口的http服務做了緩存,如果域名是通過https協議或其它端口提供服務的,用戶訪問就會出現失敗。比如支付服務、游戲通過指定端口連接connect server服務等;
B、緩存服務器的運維水平參差不齊,時有出現緩存服務器故障導致用戶訪問異常的問題。
## 6.2 解析轉發
除了域名緩存以外,運營商的LocalDNS還存在解析轉發的現象。解析轉發是指運營商自身不進行域名遞歸解析,而是把域名解析請求轉發到其它運營商的遞歸DNS上的行為。
**正常的LocalDNS遞歸解析過程是這樣的:**

<br>
<br>
**而部分小運營商為了節省資源,就直接將解析請求轉發到了其它運營的遞歸LocalDNS上去了:**

這樣的直接后果就是騰訊權威DNS收到的域名解析請求的來源IP就成了其它運營商的IP,最終導致用戶流量被導向了錯誤的IDC,用戶訪問變慢。
<br>
<br>
## LocalDNS遞歸出口NAT
LocalDNS遞歸出口NAT指的是運營商的LocalDNS按照標準的DNS協議進行遞歸,但是因為在網絡上存在多出口且配置了目標路由NAT,結果導致LocalDNS最終進行遞歸解析的時候的出口IP就有概率不為本網的IP地址。
<br>
**如下圖所示:**

這樣的直接后果就是GSLB DNS收到的域名解析請求的來源IP還是成了其它運營商的IP,最終導致用戶流量被導向了錯誤的IDC,用戶訪問變慢。
<br>
<br>
## 必須著手解決這些問題,但傳統解決方案問題太多
運營商的LocalDNS解析域名異常,給對用戶訪問騰訊互聯網業務的體驗造成了非常大的損害。
**那么以前,我們是如何處理這些域名解析異常的問題的呢?**
**1)實時監控+商務推動:**
這種方案是目前騰訊的運營團隊一直在使用的方案。這種方案就是周期比較長,畢竟通過行政手段來推動運營商來解決這個問題是比較耗時的。另外我們通過大數據分析,得出的結論是Top 3的問題用戶均為移動互聯網用戶。對于這部分用戶,我們有什么技術手段可以解決以上的問題呢?
**2)繞過自動分配DNS,使用114dns或Google public DNS:**
這個方案看上去很美好,114dns是國內最大的中立緩存DNS,而Google又是秉承不作惡理念的互聯網工程帝國巨鱷,而且騰訊的權威DNS又支持edns-client-subnet功能,能直接識別使用Google publicDNS解析騰訊域名的用戶的IP地址,不會出現流量調度失效。
但是問題來了:
> a. 如何在用戶側構造域名請求:對于PC端的客戶端來說,構造一個標準的DNS請求包并不算什么難事。但在移動端要向一個指定的LocalDNS上發送標準的DNS請求包,而且要兼容各種iOS和android的版本的話,技術上是可行的,只是兼容的成本會很高;
> b. 推動用戶修改配置極高:如果要推動用戶手動修改PC的DNS配置的話,在PC端和手機客戶端的WiFI下面還算勉強可行。但是要用戶修改在移動互聯網環境下的DNS配置,其難度不言而喻。
**3)完全拋棄域名,自建connectcenter進行流量調度:**
如果要采用這種這種方案的話,首先你就得要拿到一份準確的IP地址庫來判斷用戶的歸屬,然后再制定個協議搭個connect center來做調度,然后再對接入層做調度改造。這種方案和2種方案一樣,不是不能做,只是成本會比較高,尤其對于騰訊這種業務規模如此龐大的公司而言。
既然上面的這些傳統方案都存在那么多的問題,那有沒有一種調度精準、成本低廉、配置方便的基于域名的流量調度系統呢?答案是肯定的,我們在下一步繼續分享。
<br>
<br>
# 當前主流的解決方案:HttpDNS出現了
## 8.1 什么HttpDNS?
HTTPDNS 利用 HTTP 協議與 DNS 服務器交互,代替了傳統的基于 UDP 協議的 DNS 交互,繞開了運營商的 Local DNS,有效防止了域名劫持,提高域名解析效率。另外,由于 DNS 服務器端獲取的是真實客戶端 IP 而非 Local DNS 的 IP,能夠精確定位客戶端地理位置、運營商信息,從而有效改進調度精確性。
**HTTPDNS的原理如下圖所示:**

## 8.2 HttpDns 主要解決的問題
**Local DNS 劫持:** 由于 HttpDns 是通過 IP 直接請求 HTTP 獲取服務器 A 記錄地址,不存在向本地運營商詢問 domain 解析過程,所以從根本避免了劫持問題。
**平均訪問延遲下降:** 由于是 IP 直接訪問省掉了一次 domain 解析過程,通過智能算法排序后找到最快節點進行訪問。
**用戶連接失敗率下降:** 通過算法降低以往失敗率過高的服務器排序,通過時間近期訪問過的數據提高服務器排序,通過歷史訪問成功記錄提高服務器排序。
## 8.3 騰訊的HttpDNS思路
經過努力,騰訊公司的GSLB 團隊推出的HttpDNS是為移動客戶端量身定做的基于Http協議和域名解析的流量調度解決方案,專治LocalDNS解析異常以及流量調度不準。
詳細介紹如下。
**騰訊的HttpDNS基本原理:**

**HttpDNS的原理非常簡單,主要有兩步:**
A、客戶端直接訪問HttpDNS接口,獲取業務在域名配置管理系統上配置的訪問延遲最優的IP。(基于容災考慮,還是保留次選使用運營商LocalDNS解析域名的方式);
B、客戶端向獲取到的IP后就向直接往此IP發送業務協議請求。以Http請求為例,通過在header中指定host字段,向HttpDNS返回的IP發送標準的Http請求即可。
<br>
<br>
**HttpDNS帶來的優勢:**
從原理上來講,HttpDNS只是將域名解析的協議由DNS協議換成了Http協議,并不復雜。
**但是這一微小的轉換,卻帶來了無數的收益:**
A、根治域名解析異常:由于繞過了運營商的LocalDNS,用戶解析域名的請求通過Http協議直接透傳到了騰訊的HttpDNS服務器IP上,用戶在客戶端的域名解析請求將不會遭受到域名解析異常的困擾;
B、調度精準:HttpDNS能直接獲取到用戶IP,通過結合騰訊自有專利技術生成的IP地址庫以及測速系統,可以保證將用戶引導的訪問最快的IDC節點上;
C、實現成本低廉:接入HttpDNS的業務僅需要對客戶端接入層做少量改造,無需用戶手機進行root或越獄;而且由于Http協議請求構造非常簡單,兼容各版本的移動操作系統更不成問題;另外HttpDNS的后端配置完全復用現有權威DNS配置,管理成本也非常低。總而言之,就是以最小的改造成本,解決了業務遭受域名解析異常的問題,并滿足業務精確流量調度的需求;
D、擴展性強:HttpDNS提供可靠的域名解析服務,業務可將自有調度邏輯與HttpDNS返回結果結合,實現更精細化的流量調度。比如指定版本的客戶端連接請求的IP地址,指定網絡類型的用戶連接指定的IP地址等。
<br>
<br>
**當然各位可能會問:** 用戶將首選的域名解析方式切換到了HttpDNS,那么HttpDNS的高可用又是如何保證的呢?另外不同運營商的用戶訪問到同一個HttpDNS的服務IP,用戶的訪問延遲如何保證?
為了保證高可用及提升用戶體驗,HttpDNS通過接入了騰訊公網交換平臺的BGP Anycast網絡,與全國多個主流運營商建立了BGP互聯,保證了這些運營商的用戶能夠快速地訪問到HttpDNS服務;另外HttpDNS在多個數據中心進行了部署,任意一個節點發生故障時均能無縫切換到備份節點,保證用戶解析正常。
**接入效果及未來展望:**
當前騰訊的HttpDNS方案已在騰訊內部接入了多個業務,覆蓋數億用戶,并已持續穩定運行超過一年時間。而接入了HttpDNS的業務在用戶訪問體驗方面都有了非常大的提升。
以某個接入HttpDNS的業務為例,該業務僅通過接入HttpDNS,在未做任何其它優化的情況下,用戶平均訪問延遲下降超過10%,訪問失敗率下降了超過五分之一,用戶訪問體驗的效果提升非常顯著。另外騰訊的HttpDNS服務除了在騰訊內部被廣泛使用以外,也受到了業務同行的肯定。國內最大的publicDNS服務商114dns在受到騰訊DNS的啟發下,也推出了HttpDNS服務。
在未來的日子里,騰訊GSLB團隊將會在騰訊內部進一步推廣HttpDNS服務,并將在實際業務的需求下對HttpDNS服務進行升級,如提供更為通用、安全、簡單的接入協議,進一步提升接入用戶的網絡訪問體驗等等。希望HttpDNS能為各位在解決域名解析異常及全局流量調度失效方面提供一個簡單、可行的思路。
<br>
<br>
# 作為創業團隊,如何改造APP并支持HttpDNS?
作為創業團隊,人力、財力、技術力量可能都無暇顧及這一塊,但是移動端APP卻實實在在面臨文中提到的各種DNS問題,我們該怎么辦呢?
## 9.1 使用第3方云服務商提供的HttpDNS接口
目前,國內有一部分廠商已經提供了這個解析服務,可以直接使用第三方服務。
目前,提供 HttpDns 解析服務的第3方服務商越來多,比如:[阿里云HttpDNS](https://link.zhihu.com/?target=https%3A//help.aliyun.com/product/30100.html)、[騰訊云HttpDNS](https://link.zhihu.com/?target=https%3A//cloud.tencent.com/product/hd)、[華為云HttpDNS](https://link.zhihu.com/?target=https%3A//bbs.huaweicloud.com/forum/thread-2076-1-1.html)等。
以阿里云的 HttpDNS為便,它的API 比較標準,直接發一個 Get 請求,帶上請求參數,返回結果以 json 返回:
> [http://203.107.1.1/d?host=http://www.52im.net](https://link.zhihu.com/?target=http%3A//203.107.1.1/d%3Fhost%3Dhttp%3A//www.52im.net)
請求成功時,返回結果如下:
> {
> "host": "[http://www.linkedkeeper.com](https://link.zhihu.com/?target=http%3A//www.linkedkeeper.com)",
> "ips": \[
> "115.238.23.241",
> "115.238.23.251"
> \],
> "ttl": 57
> }
在移動端,將由 HttpDns 獲得的 IP 地址在原有 URL 的基礎上,將域名替換為 IP,然后用新的 URL 發起 HTTP 請求。
## 新浪微博團隊分享的開源HttpDNS庫:HTTPDNSLib
**HTTPDNSLib的Github地址:**[https://github.com/CNSRE/HTTPDNSLib](https://link.zhihu.com/?target=https%3A//github.com/CNSRE/HTTPDNSLib)
**HTTPDNSLib中實現的HttpDNS交互流程原理:**

從上圖中可以看出來 整個業務的交互流程,用戶向查詢模塊傳入一個URL地址,然后查詢模塊會檢查緩存是否存在,不存在從httpdnsapi接口查詢, 然后經過評估模塊返回。在用戶請求URL過程完畢時,需要將這次請求的結果反饋給 lib庫的評估模塊由評估模塊入庫記錄本次質量數據。
**HttpDns Lib庫交互詳細流程:**

上圖這張詳細流程圖就更深入的說了下 lib的工作原理。有兩條豎線講圖片分為了三個區域,分別是左部分、中間部分 和 右部分。
左部分是app主線程操作的事情,中間部分是app調用者線程中處理lib庫事件邏輯的事情,右面部分是新線程獨立處理事件的邏輯。
開始是里客戶端調用方,傳入一個 url,獲取domain信息后由查詢模塊查詢domain記錄,查詢模塊會從內存緩存層查詢,內存緩存層沒有數據會,查詢數據庫,如果數據庫也沒有數據會請求本地 LocalDNS。從三個環節中任何一個環節拿到數據后, 都會進入下一個環節,如果沒有拿到數據返回null結束。進入評估模塊,根據五個插件進行排序, 排序后返回數據給客戶端。
lib模塊設定定時器,根據ttl過期時間來檢查domain是否需要更新。 定時器是獨立線程, 不會影響app主線程。 httpdns api請求數據, 先從自己配置的 httpdns api接口獲取數據,如果獲取不到會從 dnspod api接口獲取如果也獲取不到 直接從本地 localDNS獲取數據,(從本地localDNS獲取數據后期會改為發送UDP包封裝dns協議從公共dns服務器直接獲取,比如114dns等。dns服務器地址可自行設定。 )獲取到數據后進入測速模塊。 測速模塊最新版本可以配置兩種方式,一種是http空請求。 兩個http頭的交互,類似tcp首保耗時時間原理 ,用來測試鏈路最快。 另一種是ping命令,(icmp協議)來盡量最小化流量的消耗,考慮倒可能有的服務器禁ping就使用空http測速即可。 測速后將數據插入本地 cache 即可。
<br>
<br>
**下面是測試Demo的截圖:**

<br>
<br>

<br>
<br>

<br>
<br>

<br>
<br>
有關HttpDns Lib庫的詳細介紹,請見:《[App域名劫持之DNS高可用 - 開源版HttpDNS方案詳解](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzAwMDU1MTE1OQ%3D%3D%26mid%3D209805123%26idx%3D1%26sn%3Dced8d67c3e2cc3ca38ef722949fa21f8%26scene%3D21%23wechat_redirect)》。
# 參考資料
[全面了解移動端DNS域名劫持等雜癥:原理、根源、HttpDNS解決方案等](https://zhuanlan.zhihu.com/p/51529142)
- 第一部分 HTML
- meta
- meta標簽
- HTML5
- 2.1 語義
- 2.2 通信
- 2.3 離線&存儲
- 2.4 多媒體
- 2.5 3D,圖像&效果
- 2.6 性能&集成
- 2.7 設備訪問
- SEO
- Canvas
- 壓縮圖片
- 制作圓角矩形
- 全局屬性
- 第二部分 CSS
- CSS原理
- 層疊上下文(stacking context)
- 外邊距合并
- 塊狀格式化上下文(BFC)
- 盒模型
- important
- 樣式繼承
- 層疊
- 屬性值處理流程
- 分辨率
- 視口
- CSS API
- grid(未完成)
- flex
- 選擇器
- 3D
- Matrix
- AT規則
- line-height 和 vertical-align
- CSS技術
- 居中
- 響應式布局
- 兼容性
- 移動端適配方案
- CSS應用
- CSS Modules(未完成)
- 分層
- 面向對象CSS(未完成)
- 布局
- 三列布局
- 單列等寬,其他多列自適應均勻
- 多列等高
- 圣杯布局
- 雙飛翼布局
- 瀑布流
- 1px問題
- 適配iPhoneX
- 橫屏適配
- 圖片模糊問題
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 內存空間
- 作用域
- 執行上下文棧
- 變量對象
- 作用域鏈
- this
- 類型轉換
- 閉包(未完成)
- 原型、面向對象
- class和extend
- 繼承
- new
- DOM
- Event Loop
- 垃圾回收機制
- 內存泄漏
- 數值存儲
- 連等賦值
- 基本類型
- 堆棧溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍歷對象屬性
- 寬度、高度
- performance
- 位運算
- tostring( ) 與 valueOf( )方法
- JavaScript技術
- 錯誤
- 異常處理
- 存儲
- Cookie與Session
- ES6(未完成)
- Babel轉碼
- let和const命令
- 變量的解構賦值
- 字符串的擴展
- 正則的擴展
- 數值的擴展
- 數組的擴展
- 函數的擴展
- 對象的擴展
- Symbol
- Set 和 Map 數據結構
- proxy
- Reflect
- module
- AJAX
- ES5
- 嚴格模式
- JSON
- 數組方法
- 對象方法
- 函數方法
- 服務端推送(未完成)
- JavaScript應用
- 復雜判斷
- 3D 全景圖
- 重載
- 上傳(未完成)
- 上傳方式
- 文件格式
- 渲染大量數據
- 圖片裁剪
- 斐波那契數列
- 編碼
- 數組去重
- 淺拷貝、深拷貝
- instanceof
- 模擬 new
- 防抖
- 節流
- 數組扁平化
- sleep函數
- 模擬bind
- 柯里化
- 零碎知識點
- 第四部分 進階
- 計算機原理
- 數據結構(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 選擇排序
- 插入排序
- 快速排序
- 搜索算法
- 動態規劃
- 二叉樹
- 瀏覽器
- 瀏覽器結構
- 瀏覽器工作原理
- HTML解析
- CSS解析
- 渲染樹構建
- 布局(Layout)
- 渲染
- 瀏覽器輸入 URL 后發生了什么
- 跨域
- 緩存機制
- reflow(回流)和repaint(重繪)
- 渲染層合并
- 編譯(未完成)
- Babel
- 設計模式(未完成)
- 函數式編程(未完成)
- 正則表達式(未完成)
- 性能
- 性能分析
- 性能指標
- 首屏加載
- 優化
- 瀏覽器層面
- HTTP層面
- 代碼層面
- 構建層面
- 移動端首屏優化
- 服務器層面
- bigpipe
- 構建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack優化
- Webpack原理
- 實現loader
- 實現plugin
- tapable
- Webpack打包后代碼
- rollup.js
- parcel
- 模塊化
- ESM
- 安全
- XSS
- CSRF
- 點擊劫持
- 中間人攻擊
- 密碼存儲
- 測試(未完成)
- 單元測試
- E2E測試
- 框架測試
- 樣式回歸測試
- 異步測試
- 自動化測試
- PWA
- PWA官網
- web app manifest
- service worker
- app install banners
- 調試PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 餓了么整理
- 樣式
- 技巧
- Vue音樂播放器
- Vue源碼
- Virtual Dom
- computed原理
- 數組綁定原理
- 雙向綁定
- nextTick
- keep-alive
- 導航守衛
- 組件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 動畫(未完成)
- 異常監控、收集(未完成)
- 數據采集
- Sentry
- 貝塞爾曲線
- 視頻
- 服務端渲染
- 服務端渲染的利與弊
- Vue SSR
- React SSR
- 客戶端
- 離線包
- 第五部分 網絡
- 五層協議
- TCP
- UDP
- HTTP
- 方法
- 首部
- 狀態碼
- 持久連接
- TLS
- content-type
- Redirect
- CSP
- 請求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服務端
- Linux
- Linux命令
- 權限
- XAMPP
- Node.js
- 安裝
- Node模塊化
- 設置環境變量
- Node的event loop
- 進程
- 全局對象
- 異步IO與事件驅動
- 文件系統
- Node錯誤處理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服務
- 負載均衡
- 獲取用戶IP
- 解決跨域
- 適配PC與移動環境
- 簡單的訪問限制
- 頁面內容修改
- 圖片處理
- 合并請求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自動化(未完成)
- docker
- 創建CLI
- 持續集成
- 持續交付
- 持續部署
- Jenkins
- 部署與發布
- 遠程登錄服務器
- 增強服務器安全等級
- 搭建 Nodejs 生產環境
- 配置 Nginx 實現反向代理
- 管理域名解析
- 配置 PM2 一鍵部署
- 發布上線
- 部署HTTPS
- Node 應用
- 爬蟲(未完成)
- 例子
- 反爬蟲
- 中間件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源碼
- app.js
- config.js
- 消息隊列
- RPC
- 性能優化
- 第七部分 總結
- Web服務器
- 目錄結構
- 依賴
- 功能
- 代碼片段
- 整理
- 知識清單、博客
- 項目、組件、庫
- Node代碼
- 面試必考
- 91算法
- 第八部分 工作代碼總結
- 樣式代碼
- 框架代碼
- 組件代碼
- 功能代碼
- 通用代碼