本文根據馮磊和趙星宇在“高可用架構”微信群所做的HttpDNS智能緩存庫原理整理而成,轉發請注明來自微信公眾號ArchNotes。
馮磊,目前主要從事手機應用平台的構建,任職新浪網技術中國研發中心技術保障部架構師。5+年互聯網,移動終端,游戲從業經驗。歷任軟件工程師,高級軟件工程師,技術經理。
趙星宇,HttpDNS的合作者。目前就職於新浪微博,從事手機微博的基礎架構開發,任android高級研發工程師職位。
HttpDNS是使用HTTP協議向DNS服務器的80端口進行請求,代替傳統的DNS協議向DNS服務器的53端口進行請求,繞開了運營商的Local DNS,從而避免了使用運營商Local DNS造成的劫持和跨網問題。 (具體httpdns是什么?詳細閱讀見(【鵝廠網事】全局精確流量調度新思路-HttpDNS服務詳解):http://mp.weixin.qq.com/s?__biz=MzA3ODgyNzcwMw==&mid=201837080&idx=1&sn=b2a152b84df1c7dbd294ea66037cf262&scene=2&from=timeline&isappinstalled=0&utm_source=tuicool)
鵝廠往事中提到
那么對於騰訊這樣的域名數量在10萬級別的互聯網公司來講,域名解析異常的情況到底有多嚴重呢?每天騰訊的分布式域名解析監測系統在不停地對全國所有的重點LocalDNS進行探測,騰訊域名在全國各地的日解析異常量是已經超過了80萬條。這給騰訊的業務帶來了巨大的損失。為此騰訊建立了專業的團隊與各個運營商進行了深度溝通,但是由於各種原因,處理效率及效果均不能達到騰訊各業務部門的需求。除了和運營商進行溝通,有沒有一種技術上的方案,能從根源上解決域名解析異常及用戶訪問跨網的問題呢?
HttpDNS主要解決三類問題:
-
LocalDNS劫持
-
平均訪問延遲下降
-
用戶連接失敗率下降
LocalDNS劫持: 由於HttpDNS是通過ip直接請求http獲取服務器A記錄地址,不存在向本地運營商詢問domain解析過程,所以從根本避免了劫持問題。 (對於http內容tcp/ip層劫持,可以使用驗證因子或者數據加密等方式來保證傳輸數據的可信度)
平均訪問延遲下降: 由於是ip直接訪問省掉了一次domain解析過程,(即使系統有緩存速度也會稍快一些‘毫秒級’)通過智能算法排序后找到最快節點進行訪問。
用戶連接失敗率下降: 通過算法降低以往失敗率過高的服務器排序,通過時間近期訪問過的數據提高服務器排序,通過歷史訪問成功記錄提高服務器排序。如果ip(a)訪問錯誤,在下一次返回ip(b)或者ip(c) 排序后的記錄。(LocalDNS很可能在一個ttl時間內(或多個ttl)都是返回記錄
HttpDNSLib庫組成
HttpDNSLib庫主要由三個模塊組成,查詢模塊,緩存模塊,評估模塊。
查詢模塊:
-
檢查本地是否有對應的 domain 緩存
-
如果沒有 則從本地LocalDNS獲取然后從httpdns更新domain記錄
-
有數據則檢測是否過期 已過期則更新記錄返回 LocalDNS 記錄, 未過期則直接返回緩存層數據。
-
從HttpDNS 接口查詢本次app開啟后使用過的domain 記錄定時訪問,更新內存緩存,數據庫緩存等記錄
數據模塊:
-
根據SP(或Wifi名)緩存域名信息
-
更具SP(或Wifi名)緩存服務器ip信息、優先級
-
記錄服務器ip每次請求成功數、錯誤數
-
記錄服務器ip最后成功訪問時間、最后測速
-
添加 內存 -》數據庫 之間的緩存層
評估模塊:
-
根據本地數據,對一組IP排序
-
處理用戶反饋回來的請求明細,入庫
-
針對用戶反饋是失敗請求,進行分析上報預警
-
給HttpDns服務端智能分配A記錄提供數據依據
HttpDNS交互流程
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 即可。
代碼結構
工程代碼一共有八個主要package包,分別是cache、httpdns、log、model、query、score、speedtest、networktype。
cache包數據緩存層
IDnsCache是該包的對外主要接口。DnsCacheManager 實現該接口,封裝了管理該包的所有邏輯調度,ConcurrentHashMap是內存緩存層的介質,當初使用過非線程安全的hashMap遇到了很多線程鎖的問題,沒有更好的辦法自己控制鎖管理,就替換成線程安全的concurrenthashmap對象了。DBConstants 設定了數據庫名字表名字以及表字段,包含全部sql語句。 DNSCacheDatabaseHelper 用來操作數據庫,所有和數據庫交互的邏輯都在該類。
networktype包用來監控網絡變化和檢測當前網絡狀態
Constants 設定了網絡狀態的相關常量。 NetworkManager類也是這個包的主入口類所有網絡狀態的獲取都是通過這個類來獲取。 NetworkStateReceiver用來注冊網絡廣播來接受網絡發生變化的事件 。
httpdns包封裝了所有HttpDNS api交互請求
IHttpDNS接口定義了該包和外部交互的所有數據格式,HttpDnsManager 實現了IHttpDNS接口。 HttpDnsConfig定義了使用到的常量配置, 以及dns api接口的開關,和順序。 requests包里INetworkRequests接口輕量級的定義了 網絡請求的實現, 目前使用ApacheHttp實現的該接口,如果用戶有需求更換網絡實現方式實現INetworkRequests 接口即可。 IJsonParser 接口定義了 httpdns api返回數據解析json的方式, 目前使用 android jsonObject實現。 如果需要擴展直接實現該接口即可。
log包實現了記錄dnscachelib庫記錄日志倒文件的工具類
IDnsLog約定了寫log和獲取log的方法。 HttpDnsLogManager實現該接口,並管理log模塊。該模塊還有一個寫文件的工具類。
model包封裝了全部數據交互模型
DomainModel對應數據庫domain表, IpModel對應數據庫ip表。 HttpDnsPack是獲取httpdns api接口數據的模型 。 ConnectFailModel用來記錄所有異常錯誤。
query包是查詢模塊的入口
IQuery定義了該包對外的協議接口。 QueryManager實現該接口封裝了所有查詢相關操作。
Score包也是前面說的評估模塊
IScore定義該包對外實現的接口,ScoreManager實現該接口。 PlugInManager用來管理所有評估插件。 所有的評估插件均實現 IPlugIn接口協議,規定輸入輸出。使用者可以自行添加評估插件。
speedtest包實現測速邏輯
ISpeedtest規定該包對外的接口協議, SpeedtestManager實現ISpeedtest接口。 封裝了測速相關邏輯, 包括空http請求,以及ping命令測速。
另外場景包種有幾個類簡單介紹下。 DNSCache類是lib的主入口類,用戶的所有操作均調用該入口類,該類是單利類直接獲取實例調用即可,也是主場景。
由於內部model數據過於復雜,為用戶專門封裝DomainInfo模型。 該類僅返回用戶使用的相關數據。
DNSCacheConfig 是httpdns庫的全局配置文件, 可以直接修改該文件,也可以外部調用方法設置參數 。 該文件還封裝了雲端動態更新緩存配置。
代碼結構如下圖所示:
在編寫該庫的時候遇到最頭疼的問題可能就是多線程同時訪問導致遇到的數據異常錯誤。比如用戶訪問 api.weibo.cn 域名該域名目前數據庫中沒有緩存,內存中也沒有緩存。在同時有多個請求以來來獲取該域名的ip的時候, 因為沒有數據會去請求api接口獲取數據, 導致同時開啟多個線程訪問數據。 解決辦法在請求api接口前增加正在請求隊列,
任何需要請求數據的domain都先要在該隊列檢測是否有請求存在如果沒有在繼續進入后面流程如果有則丟掉本次請求指令。 另外在操作數據庫的時候使用了 對象鎖和 synchronized 方法鎖, 導致了程序有鎖死的情況,后改成全部使用對象鎖就解決了該問題。全程的調試數據也是最頭疼的環節,后直接編寫測試程序,時時調試所有環節的數據(看到時時數據后發現了很多程序的bug,后都一一解決)。
以上為馮老師的分享,接下來是星宇跟大家分享下項目從研發倒現在所遇到的一些主要問題和大家有疑問的點。
開發過程中,常見的一些問題
1、手機網絡從3G 切換到 Wifi下處理了什么?
NetworkStateReceiver類來監聽網絡是否發生變化,在網絡有變化的時候,會刷新 NetworkManager類中的網絡環境,在客戶端內如果是手機網絡可以知道網絡類型(2G、3G、4G)也可以知道當前SP(移動、聯通、電信),如果是Wifi網絡環境可以知道SSID(wifi名字)在刷新網絡環境后,會重新查詢緩存內是否有當前鏈路下的最優A記錄,如果沒有則從LocalDNS獲取第一次,然后馬上更新httpdns記錄。
2、網絡發生變化后,返回的A記錄還一樣么?
數據庫中緩存的數據,是根據當前sp來緩存的,也就是說當自身網絡環境變化后,返回的a記錄是不一樣的 。手機網絡下會根據當前sp來緩存 a記錄服務器ip,如果是wifi網絡環境下 根據當前ssid來緩存a記錄,因為wifi環境下庫自己沒有辦法明確判斷出自己的運營商,但相同的ssid不會發生頻繁的網絡運營商變化。 所以在wifi下請求回來的a記錄直接關聯ssid名字即可,即使wifi sp發生變化,最多延遲一個ttl時間就更新成最新的a記錄了。
3、怎樣進行測速?
在從HttpDNS獲取回來a記錄的時候進行測速,測速的方式有兩種: ping和空的http請求。考慮倒有些服務器不支持ping來進行鏈路質量評判,可以使用空的http請求,僅僅是兩個http頭的流量開銷,而ping的方式流量開銷就更小了。 這個功能可以在庫中自己配置。這里的測速其實是模擬首保接收的時間來做的, 同時對於流量控制嚴格的可以在庫中配置測速頻繁度,比如一台服務器在5分鍾內有過測速記錄則不進行測速。
4、域名ttl剛剛過期,庫還沒有從HttpDNS拉取回來數據怎么辦?
ttl過期的前10秒去請求數據,在ttl過期后的10秒內庫也認為當前a記錄是有效的,會給你直接返回。
5、lib庫目前只能使用 dnspod 服務商么? 支持dnspod 企業版本么?
目前庫可以支持自定義的 HttpDNS api接口, 只需要實現IHttpDns接口類即可,在配置了dnspod企業key和id 的時候自動啟用企業版本加密傳輸,支持企業版本。
6、使用這個庫會不會降低應用請求網絡的訪問速度?
從目前的測試數據來看是不會的,HttpDNS庫返回a記錄的時間平均在5毫秒以內,有時會出現內存緩存中沒有該域名記錄,數據庫中也沒有的時候會從LocalDNS獲取a記錄,時間會稍長一些
一旦從LocalDNS獲取后,會緩存倒內存中,在HttpDNS獲取數據后會更新內存中得domain記錄。 從庫中獲取a記錄會比從LocalDNS獲取a記錄快一些。 在訪問網絡的時候由於是使用ip直鏈,可以起到一些加速效果,lib庫獲取domainA記錄 + ip直接訪問服務器 耗時小於 直接域名請求服務器。相關數據圖片
下面給出一個測試系統的截圖
7、lib庫里面訪問網絡使用的是哪個網絡庫? json庫用的是哪個?
考慮到該庫的輕量級,使用的是android系統的org.apache.http.client.HttpClient庫訪問網絡,如果需要切換到工程在使用的網絡庫可以實現 INetworkRequests 接口即可切換網絡庫。 json解析使用的也是android系統自帶的org.json.JSONObject 如有需求切換json解析庫,可直接實現IJsonParser接口即可切換。
8、使用該網絡庫會給我的app帶來多大的體積增加?
目前該lib庫沒有引用任何外部的庫文件。一切本着使用系統自身的api為原則,來保證庫的輕量級,和兼容性。 目前lib庫打包后70多k,代碼在5千行左右。 測試工程代碼在6千行左右。
9、lib庫的配置必須要通過修改源代碼的方式來進行配置么?
任何參數都可以在庫調用方配置,DNSCacheConfig 類是整個庫的配置文件。 並且支持雲端動態更新配置 需要實現DNSCacheConfig.ConfigText_API 更新地址。 具體配置api接口請參考 dome工程中設置庫的方式。
10、緩存domain記錄是存儲成文件還是數據庫,或者android內部的一些存儲方式?
lib緩存數據是通過數據庫存儲的。SQLiteDatabase, 具體的表接口和sql語句請參考 DBConstants 類文件。
11、 評估模塊有什么功能?
評估模塊目前由五個插件組成, 速度插件、推薦優先級插件、歷史成功次數插件、歷史錯誤數插件、最近一次成功時間插件 。 每一個a記錄服務器ip,都會經過這五個插件進行評估排序后返回給使用者。 所有插件評估分值比重可以配置, 根據自己的需求以及不同的使用場景,調整出最合理的權重分配。
下面給出評估模塊算法細節圖
12、速度插件具體算法?
比如速度插件評分體系, 滿分100分, 那么有3個服務器ip, 1號服務器http請求耗時10毫秒, 2號服務器20毫秒, 3號服務器30毫秒。 那么經過插件計算后 1號服務器100分, 2號服務器50分, 3號服務器25分。
13、優先級插件又是什么?
如果是自定義的服務器,可以返回服務器優先級字段,該的字段代表推薦使用該服務器的權重, 比如該字段服務端可以和監控系統結合起來,甚至是用來分流。 相應的權重值, 也會算出來不同的分值。
14、歷史成功次數插件是什么? 歷史錯誤次數插件是什么?
在當前sp當前鏈路下, 會記錄訪問過的該服務器ip的成功次數, 成功次數越多認為該服務器相對穩定。 會在排序的時候根據權重比值進行影響最終排序結果, 該插件權重不建議過高。 同理歷史錯誤插件也是記錄當前鏈路下服務器出過錯誤的次數,次數越高認為越不穩定。 排序盡量靠后。 同樣該插件權重不建議過高。
15、最后一次成功時間?
如果該服務器在 很近的時間內訪問過,那么評估系統則認為他鏈路是通的,則會給一個分值, 越接近現在的時間的服務器 分值越高。 24小時以前訪問的分值為0 。
16、評估插件就這五個嗎?
該五個插件屬於拋磚引玉, 可以自定義插件 只需要實現 IPlugIn 接口即可。 所有的插件啟用和停止都在 配置文件中可以修改,以及配置每個插件的權重比。
17、智能評估一定會帶來好的效果嗎?
首先httpdns返回的a記錄已經是 經過當前地域和當前sp返回的最優記錄結果集, 至少不會降低效率。
18、我可以不使用智能評估模塊么?
可以的在配置文件中關掉智能評估即可,具體代碼參照demo即可。 關掉智能評估模塊后,會在多個a記錄中隨機排序返回。
19、該Lib庫塊兼容性如何?
使用testin兼容性測試 測試兼容性結果:99.49%。 Android平台全部由java代碼開發,沒有使用任何特殊特性,覆蓋全部系統版本。
最后附幾張測試工程效果圖:
-
模擬了客戶端訪問http請求,分別標識了每個任務的詳細信息。
-
這個頁面全都是數據庫相關配置,在代碼中可以直接找到具體設置庫文件的接口。
-
數據報表入口,包含全部任務加速效果延遲效果數據記錄, lib庫耗時走向,每個ip直接訪問請求和domain訪問請求速度對比, 統計了服務器平局速度。
-
-
-
緩存數據標簽中包含了 當前庫的所有狀態, 能實時的看到內存緩存層的所有數據狀態,包括數據庫中得所有數據狀態。 每秒鍾刷新一次。 在這里可以清空緩存層數據、數據層數據、已經當前測試工程的數據。 在這里你可以清楚的看到 ip 和 domain的對應關系, 以及數據庫表中 每項的關系。 和所有的domain 以及 ip 的狀態。
全部代碼 均已開源 https://github.com/SinaMSRE/HTTPDNSLib , 包括測試工程 也開源了 設計文檔 和 流程圖都在 git 上有。 測試工程的 ui psd 貌似也在git上
Q&A
Q1、每次請求url都需要去ping么?
不需要每次都ping的, 測試鏈路是否通暢 會在 從 httpdns api接口獲取數據后, 在測試鏈路是否通暢, 每次請求 httpdns api間隔是一個 domain 設置的 ttl時間
ping 直發一個包, 最小化的減少 流量開銷。
檢測鏈路 如果配置成 http 空的請求, 也是同理 在 httpdns api請求結束后, 才會檢測鏈路是否暢通。
Q2、前面提到的並發請求,被丟棄的請求是怎么處理的
並發請求是說 客戶端請求 HttpDNS lib庫 ,同時發 api.weibo.cn 的請求么?
因為 去問 HttpDNS api接口的時候 , 只需要有一個請求去問就可以了, 去問 HttpDNS api的時候 已經切換到 非客戶端主線程, 在客戶端調用的主線程中 如果沒有緩存數據 就從 本地獲取 dns 的a記錄返回了。 所以直接丟棄這個訪問 HttpDNS api的請求即可, 不會影響到其他流程邏輯。
Q3、南北網絡之間請求有特別處理么?
南方電信,北方網通,運營商ip不一樣
首先 HttpDNS 返回的a記錄會根據你的出口ip 來從權威的 dns 服務器問出來結果。 如果你是南方的ip 肯定給你的a記錄 也是南方的, httpdns 返回的記錄理論應該是和 傳統的 dns 返回的 a 記錄是一樣的。 而去問 httpdns 的api 地址 是 bgp的機房。 所以 也是 兼容多鏈路 多地域。有遇見過 傳統 dns 出口可能是 電信的, 但業務訪問的 ip 出口是聯通的情況。 所以 HttpDNS 訪問 a記錄 也能避免這類一部分錯誤。
Q4、用dnspod是用的他的接口么?如果dnspod上是配置的是cname,會遞歸解析出最終的ip緩存下來么?
會的。 這個依賴dnspod的返回結果, 同時也支持 cname 的返回結果。
比如 圖片使用 cdn 如果返回的是 cname 結果。 那么數據庫中記錄的也是 cname 結果。 通過 cname 家 host 頭來訪問也是可以的。
Q5、數據庫中記錄的是cname,還是cname解析出的ip?
數據庫中記錄的是 cname , 並不是ip 。
因為測試過, 從一棟大樓走到另外一個大樓 里面 訪問的最終ip可能都不相同。 所以如果返回的是cname 則直接存儲cname 。 網絡環境發生變化, 會重新拉取, 不會使用緩存的cname 。
Q6、那cname的情況下,httpdns就起不到實際的作用了?
不會的, 一般劫持的都是 業務的主要域名, 而cname域名的劫持相對較少, 從我們公司的業務來看啊。 而且 dnspod 返回cname 的情況 我目前還沒看到。 都是解析倒ip 。 而我們自己做的 httpdns 服務器, 第一期目前會解析倒 cname 的節點。 跨域的ip解析 還沒做 會放到二期。
Q7、我們遇到的問題是主域名解析沒問題,cname的域名是amazon aws的域名,經常莫名其妙解析不通,懷疑是運營商搞鬼。當時也想自己做這個httpdns,但發現很麻煩,小廠沒人力搞這個事情。
有這個可能,我覺得可以把你們的domain放到dnspod里面試下解析出來的是不是cname如果是直接的ip應該沒問題。后期我們有計划加上udp直接發送dns協議包到公共的dns服務器節點來獲取數據,也支持設置自己家的權威dns服務器。
http://www.tuicool.com/articles/7nAJBb