詳講虎牙直播在全球 DNS 秒級生效上的實踐

2019-07-13   hello架構

這次分享的是全球 DNS 秒級生效在虎牙的實踐,以及由此產生的一些思考,整體上,分為以下 5 各部分:

  • 背景介紹;
  • 方案設計和對比;
  • 高可用;
  • 具體實踐和落地;

背景介紹

虎牙用到的基礎技術很多,DNS 是其中比較重要的一個環節。

DNS 的解析過程很關鍵,例如上圖中的 DNS 解析器通過一個定位解析追蹤到我們的 DNS,再到本地域名伺服器疊代解析,經過根域再到.com 名,最後到 huya.com 的根域名,獲取最終的解析結果。

在這個過程中, DNS 解析是天然的分布式架構,每一層都會有緩存,上一層出現問題掛掉,下一層都會有緩存進行容災。另外,整個 DNS 協議支持面廣,包括手機和 PC,我們用的編程框架里也有 DNS 解析器,伺服器也會配 DNS 解析引擎,因此,DNS 在虎牙的基礎設施中是很重要的部分。

虎牙的 DNS 的應用現狀

虎牙當前主要是依賴於公共的 DNS,相信在座的小夥伴們或多或少都會遇到過下面這些問題:

  • 依賴公共 localDNS,解析不穩定,延遲大。
  • 記錄變更生效時間長,無法及時屏蔽線路和節點異常對業務的影響。例如,權威 DNS 全球各節點數據同步時間不可控,全局生效時間超過 10 分鐘;localDNS 緩存過期時間不可控,部分 localDNS 不遵循 TTL 時間,緩存時間超過 48 小時。
  • 內部 DNS 功能缺失,無法解決內部服務調用面臨挑戰。例如,時延大、解析不准、支持多種調度策略。
  • 無法滿足國外業務的快速發展,雖然一些海外雲廠商提供了基於 DNS 的快速擴容方案,以及基於 DNS 的資料庫切換方案。

方案設計和對比

基於以上的問題,我們開始重新規劃 DNS 的設計。

名字服務架構

整個規劃會分三個方面,核心是我們做了「名字服務」的中心點,基於此,可以滿足我們的需求。

一方面通過 Nacos Sync,將現有多個註冊中心的服務, 同步到「名字服務」中, 通過 DNS 實現不同框架之間的 Rest 服務方式的調用, 實現例如 Eureka,Consul,Taf 等框架之間的服務調用。

另一方面,在全球負載均衡的場景下,由於虎牙是以音視頻業務為主,而音視頻業務對節點的延遲是非常敏感的,所以我們希望一旦出現節點延遲的情況,能立馬做切換。

第三個是傳統 DNS 的場景, 可以滿足容器和物理機的 DNS 需求, 提供本機 Agent 和集群兩種方案, 通過緩存和 prefect 大大提高 DNS 解析的可用性和加快生效時間。

對於名字服務的總體設計主要分 3 部分,接入層需要提供 API,消息通知和 DNS 接入的能力。核心功能需要能在基於現有網絡數據,CMDB 和 IP 庫的數據基礎上,提供靈活的負載均衡能力,全球數據的秒級同步,多個數據源的同步,能對全網服務的健康狀態進行監控,及時感知到大範圍的節點異常,並且能夠及時將節點的屏蔽的信息推送到端上。

最終,我們選擇 Nacos 作為名字服務的核心,提供統一的 API ,包括名字註冊、變化推送、負載均衡等;Nacos Sync 作為全球集群間數據同步的組件;DNS - F 是客戶端組件,用於攔截 DNS 請求,實現基於 DNS 的名字服務。

改造前後 DNS 變更生效流程的不同

接下來,我們通過對比看下改造前後 DNS 變更生效流程的差異。

原有 DNS 變更生效流程中,對 DNS 生效時間有影響的是:

Auth DNS

  • 跨區域、跨國數據同步慢,不穩定。
  • bind 在數據量比較大的時候,同步比較慢。

Local DNS:

  • 根據 TTL 緩存,過期後才會刷新數據。
  • 部分廠商不遵循 TTL 時間緩存,超過 24 小時的緩存時間。

伺服器:

伺服器開啟 nscd 做 DNS 緩存。

業務進程:

應用的 DNS 緩存,比如 Java 虛擬機、框架層的 DNS 緩存。

以上四種情況會比較影響 DNS 的變更生效流程,下圖是我們現有的 DNS 變更生效流程:

整體上相對簡單,只要業務進程這邊將自己內部的 DNS 緩存關掉, 通過 DNS-F 進行查詢的時候, 會直接到最近的 Nacos 集群拉取最新的服務節點信息, 而且後續節點的變化也會推送到 DNS-F 中, 後續可以直接在緩存中獲取最新信息。

國內 Nacos 集群

集群內通過 raft 協議同步數據,毫秒級別完成同步。

Nacos Sync:

  • Nacos 推送變化到 Nacos Sync,跨區域、跨國網絡差的情況下可能會導致推送結果丟失,或者延遲加大。
  • Nacos Sync 會主動拉取實例變更,拉取周期和監聽的服務數量會影響到變更時效。

DNS - F:

  • Nacos 會將變更推送到 DNS - F,網絡差的情況可能會導致推送結果丟失,或者延遲加大。
  • DNS - F 會主動拉取實例變更,拉取周期和監聽的服務數量會影響到變更時效。

業務進程:

通過應用禁用 DNS 緩存來解決。

核心設計 Nacos

Nacos 有兩套推送機制。

一種是通過客戶端來選擇一個可獲節點,比如它第一次拉取的是一個正常節點,這個正常節點就會跟它維護一個訂閱關係,後面有變化就會有一個相應的實地變化推送給我。如果當前節點掛掉, 他會通過重連, 在節點列表上,連上一個正常的節點。這時候會有新的 DNS 關係出現。

另一種是通過 SDK 的方式,在服務端尋找可獲節點。服務端每個節點之間, 會進行一個可活的探測, 選擇其中一個可活節點用戶維護這個訂閱關係。 當這個節點出現問題, 連接斷開後, SDK 重新發送訂閱請求,服務端會再次選擇另外一個可活的節點來維護這個訂閱關係。這就保證整了推送過程不會因為某個節點掛掉而沒有推送。

推送的效率方面,主要是用 UDP 的方式,這個效率不像 TCP 消耗那麼高。

以上兩個方案都比較適合我們目前的場景。

核心組件設計 Nacos Sync

我們選擇 Nacos Sync 作為多集群數據同步的組件,主要是從以下 4 方面進行考慮的。

  • 同步粒度:

Nacos Sync 同步數據的時候是以服務為維度, 比較容易做最終一致性處理, 同時可以提供保活的機制,滿足節點維持的場景。 資料庫通過 Binlog 同步的方式只能局限於事務粒度, 而文件同步只能通過單個文件的粒度, 在服務同步這個維度並不是很合適。

  • 可用性方面:

Nacos Sync 作為一個中間件,是以集群方式進行的,傳統的資料庫和文件方式基本是單進程進行的,可用性方面可能不太滿足要求。

  • 同步方式方面:

Nacos Sync 通過在服務粒度的全量寫入,滿足服務註冊和 DNS 這兩種場景, 不需要額外的事務消耗, 能保證最終一致即可。

  • 環形同步:

我們國內有多個可獲的節點,希望它們之間的數據可以進行環形同步,每個節點之間是相互備份的,這時候用 Nacos Sync 的話,是支持的。雖然資料庫方面,比較經典的是主主同步,但如果同時對一個主件進行更新的話,每一個點進行協助是會有問題的,而且文件方面是不支持的。

Nacos Sync 和開源版本的不同

我們對 Nacos Sync 開源方案上做了幾處修改,以更好的適用於現在的場景:

第一,通過配置方式對任務進行分拆。因為在實際應用場景裡面,因為 Nacos Sync 的任務達一兩萬,單機很容易到達瓶頸,所以我們通過配置的方式將這些分片到多台 Nacos Sync 機器上。

第二,通過事件合併和隊列控制的方式控制 Nacos 集群的寫入量,以保證後端的穩定性。雖然下發事件一秒鐘只有一個,但在很多場景中,例如需要 K8s 或者 Taf 進行數據同步的時候,變化的頻率是非常高的,這時候通過事件合併,每個服務單獨進行一個寫入進程。這樣通過隊列控制的方式可以控制整個 Nacos 集群的寫入量。

第三,添加了能支持從 K8s 和 Taf 同步數據的功能。後期我們會將這個特性提交給 Nacos,讓更多的開發者使用。

核心組件設計 DNS - F

DNS - F 是基於 CoreDNS 上開發的,我們擴展了以下 4 個組件:

Nacos 插件:查詢 Nacos 服務信息,監聽 Nacos 服務變化,並將服務轉化為域名,實現以 DNS 協議為基礎的服務發現;
Cache 插件:提供域名緩存服務 ;
Log 插件:將 DNS 解析日誌上報到日誌服務;
Proxy 插件:代理解析外部域名;

DNS - F 和開源版本的不同

第一,在日誌組件裡面將日誌上傳到自己的日誌服務。

第二,對緩存功能做了一個增強。一般的緩存功能可能根據 TTL 時間會過期,我們把這個過期時間給去掉了,直接令到緩存永遠不會過期,然後通過異步將這個緩存進行刷新。比如 TTL 可能快到到時間了,我們就會主動做一個查詢或者推送查詢,這樣,服務端或者公共 DNS 出現問題的時候,就不會影響到整體服務。

第三,增強了高可用的保障能力。包括進程監控、內部運營和外部運營的探測。另外,原來的開源版本用的是本機部署的方式,我們做成了集群化的部署,解決了服務推送、服務負載均衡方面的問題。

高可用

全球化部署方案

這是虎牙的一個全球化的部署方案,我們在全球部署了兩個大區,分別是國內和國外。這兩個大區是指定服務同步的,走的是專線,這樣可以保障同步的穩定性。在一個大區內我們又部署了多個接入點,例如在國內大區,我們部署了深圳和無錫兩個接入點,這兩個節點的數據是互相同步、互為備份,保證在一個集群掛掉下可以切換到另外一個集群。

多個接入點的情況下,我們通過 HttpDNS 實現客戶端的就近接入。客戶端定期請求 HttpDNS,HttpDNS 能根據地域尋找就近接入點。如果接入點出現故障,我們就直接在 HttpDNS 把這個節點給摘除,這樣客戶端就能快速地切換到另外一個接入點。

接下來講一下單個集群下的部署方案。

單個集群部署了多個 Nacos 節點,並通過 7 層負載均衡的方式暴露給外面使用,並且提供了多個 VIP,滿足不同線路和區域的接入要求。同時,Nacos Sync 做了分片處理,將同步壓力分散到各個分片上,一個分片下我們又部署了多個 Nacos Sync 的節點,以保障多活和高可用。

線上演練

演練的場景是模擬一個單個集群掛了和兩個集群都掛了。

從圖中可以看到,把深圳的流量切走之後,無錫的流量就漲上去了,然後再把無錫的流量切走,再關閉服務,這樣就可以看到兩邊的流量已經沒了。之後,再去恢復兩個集群的流量,看一下整個切換過程中對服務的影響。

首先看一下對寫入的影響,在單個集群掛了的情況下,是沒有任何影響的。如果是兩個集群都掛了,寫入就會失敗。可以看到,這個圖有一個波峰,這個波峰就是我們兩個集群都掛了的情況下,寫入失敗延遲加大。

但是切換的整個過程對 DNS-F 是沒有任何影響的,延遲保持平穩。此外,在集群重新上線前,我們需要做數據校驗,保證集群之間元數據和實例數據的最終一致。

可用性級別方面,我們可以保障:

  • 單集群掛掉後不會有影響;
  • 雙集群掛掉後只會影響域名變更,不影響域名解析;

線上演練數據校驗機制

運行過程中,我們也要保證集群間數據的一致性。我們通過全量校驗和增量校驗兩種手段去保證,全量校驗方式如下:

  • 大區內部做 10 分鐘的全量校驗,保證大區內各個集群數據的一致;
  • 大區之間做 2 分鐘做一次全量校驗,保證大區之間被同步的服務的數據一致性。

增量校驗方式如下:

  • 從其他數據源同步的數據,通過數據源的時間戳,做增量校驗;
  • 基於 API 的寫入日誌,定期校驗寫入的內容是否已經全部同步。

DNF - S 高可用

關於 DNS - F 的高可用,我們主要做了以下 5 個點:

  • Agent 的健康狀態監測,包括進程存活和是否能正常解析;
  • 緩存內部域名,並做持久化處理,保證 Nacos 集群出現問題時不會影響內部域名的解析;
  • 提供備用節點,保證在 DNS-F 掛了,或者是 DNS-F 需要升級的情況下,也不會影響到內部域名解析;
  • resolv.conf 配置檢查,發現 127.0.0.1 不在配置中會自動添加;
  • 限制 Agent 的 CPU 的使用,避免對業務進程造成影響。

具體的實踐和落地

實踐一:資料庫域名改造

之前的資料庫是用 IP 方式接入的,在資料庫切換的時候,需要通知每個業務方修改配置,重啟服務,這樣就帶來一個問題:整個過程是不可控的,取決於業務方的響應速度,生效時間通常超過十分鐘。

提升資料庫切換的關鍵點,第一個就是切換時不需要業務方參與,能在業務方無感知的情況下進行切換;第二個是實例變化能秒級推送到我們的應用,將應用快速切換到一個新的實例上。

大家可以看一下??? 這個圖,這是我們現在做的一個改造,圖中的 DMX 是虎牙內部的一個資料庫管理系統,思路就是把 DMX 和名字服務打通。DMX 會把資料庫實例信息以服務的形式註冊到名字服務,服務名就是域名。

實際應用過程中,通過這個域名去訪問資料庫,應用在訪問前首先會經過 DNS - F 去做域名的解析,解析的時候是從名字服務查詢實例信息,然後把實例的 IP 返回給應用。這樣,應用就能通過 IP 和我們的資料庫實例進行連接。

切換的時候,在 DMX 平台修改域名對應的實例信息,並把變更推送到名字服務,名字服務再推送給 DNS-F,應用在下一次解析的時候就能拿到新的實例 IP,達到切換資料庫實例的目的。

這套方案落地後,虎牙的資料庫切換基本上在 10 秒鐘之內能夠完成。

實踐二:內部調用使用內部域名

虎牙部分內部系統之間調用是通過 7 層負載均衡,但是由於沒有內部 DNS,需要通過的公共的 LocalDNS 來解析,這就帶來一些問題:

問題一:擴縮容的時候要去修改 DNS 記錄,整個過程生效時間可能會超過 10 分鐘,故障的節點會影響業務較長的時間。

問題二:公共的 LocalDNS 智能解析不準確,比如無錫的機器可能會解析到深圳的一個接入點,影響接入質量。

問題三:不支持定製化的負載均衡策略,例如同機房、同大區優先的策略,通過公共 LocalDNS 是實現不了的。

如果想要提升內部服務調用質量,一是 DNS 記錄變更繞過 LocalDNS,把 DNS 的記錄變更直接推到 DNS-F。二是與內部系統打通,從 CMDB 等內部系統獲取機器信息,支持多種負載均衡策略。

大家可以看一下上面??? 的圖,這個改造和資料庫域名的改造思路是一樣的,最右上角有一個 7 層負載管理系統,我們把這個系統和名字服務打通,7 層負載管理系統會把域名信息以服務形式註冊到名字服務,變更域名記錄時直接從 7 層負載管理系統推送到名字服務,名字服務再推送到 DNS-F,達到快速切換的目的。

如果域名配置了負載均衡策略,名字服務會從 CMDB 獲取機器、機房等信息,打標到域名的實例信息。然後,DNS-F 查詢名字服務時,會攜帶 ClientIp,名字服務根據 ClientIp 的 CMDB 信息過濾實例列表,返回同機房的實例給 DNS-F,達到同機房優先的目的。

由此帶來的效果是:

第一,服務擴縮容能夠秒級完成,減少了故障時間。

第二,擴展了 DNS 的負載均衡策略,例如有些業務是需要在不同區域有不同的接入點的,而且不能跨區域調用,之前的 DNS 負載均衡策略是不能滿足這個需求的,但在改造之後,我們能根據 CMDB 信息去做同區域調度的負載均衡策略。

第三,業務在接入內部域名之後,延遲會有明顯的下降。上圖顯示的就是某個服務在接入到內部域名之後,延遲出現明顯的下降。

另一個落地的效果就是我們對主機上的域名解析的優化。因為我們的 DNS - F 是部署在每台主機上的,然後提供一個緩存的功能。帶來的效果就是:

  • 平均解析延遲會從之前的 200 毫秒下降到現在的 1 毫秒;
  • 緩存命中率會從之前的 90% 上升到 99.8%,90% 是用 CoreDNS 原生的那個 Cache,99.8% 是在這個 Cache 的組件下做了優化之後的效果;
  • 解析失敗率是從之前的 0.1% 下降到 0%;

這裡再總結一下項目落地的技術價值:

第一,提供了基於 DNS 服務發現的能力,消除異構系統之間互相調用的障礙。

第二,填補了沒有內部域名解析能力的空白。

第三,解決我們上面說的內部服務調用面臨的挑戰:延時大、解析不准、不支持多種負載均衡策略、故障牽引慢。

第四,優化外部域名的解析,屏蔽 LocalDNS 的故障。

落地規模是:DNS - F 覆蓋率 100%,完成 Taf 和 Eureka 註冊中心的數據同步。