踩了個DNS解析的坑,但我還是沒想通

来源:https://www.cnblogs.com/zhuochongdashi/archive/2022/04/12/16134578.html
-Advertisement-
Play Games

hello大家好,我是小樓。 最近踩了個DNS解析的小坑,雖然問題解決了,但排查過程比較曲折,最後還是有一點沒有想通,整個過程分享給大家。 背景 最近負責的服務要置換機器。置換機器可能很多小伙伴不知道是幹啥,因為大家平時接觸不到,我簡單解釋一下什麼是機器置換以及為什麼需要機器置換。 機器置換通俗地講 ...


hello大家好,我是小樓。

最近踩了個DNS解析的小坑,雖然問題解決了,但排查過程比較曲折,最後還是有一點沒有想通,整個過程分享給大家。

背景

最近負責的服務要置換機器。置換機器可能很多小伙伴不知道是幹啥,因為大家平時接觸不到,我簡單解釋一下什麼是機器置換以及為什麼需要機器置換。

機器置換通俗地講就是更換機器,把服務從一臺機器遷移到另一臺上去。

為什麼要機器置換呢? 錶面原因可能是機器硬體故障、或者機器過了保修期。

有些小伙伴可能就想問,我在公司也負責了很多服務,為啥從來沒有置換過機器呢?原因可能是用了容器,沒有直接部署在物理機上,置換機器的任務被轉移給了雲平臺的運維人員;還可能是你們有專門的運維幫忙做了這件事,對開發人員來說幾乎是透明的。

我負責的服務為啥要置換呢?因為機器過保了。服務為啥部署在物理機上呢?因為它是個基礎服務,和一般服務不太一樣,有一些限制,只能在物理機上部署。為啥沒有運維人員幫忙呢?因為公司很多基礎服務是自運維,開發者既做開發又是運維。

image

說完機器置換,再來聊聊這個基礎服務,它是一個Go寫的服務,不停地發送HTTP請求,記住這點就好,其他不重要。

這個服務在置換機器後,HTTP請求的耗時慢了不少,如下圖,黃色為老機器,藍色為新機器,指標的值就是HTTP請求的耗時(毫秒),大概1.5倍的差距。這就是今天要分享的問題,接下來說說我的排查過程。

image

問題排查

這種情況,先去看了機器的各項指標,如CPU、網路情況等等,看看是否有異常,確認是否被其他指標影響了。但看了一圈下來,發現新機器的各項指標甚至還優於老機器。

接著去詢問了提供機器的同學,看看機器是否有異常,結果也是沒有。

既然HTTP請求變慢,就想到看看是請求的哪個環節變慢了,用如下的命令來測試下,功能變數名稱我用百度的功能變數名稱來代替:

curl -o /dev/null -s -w %{time_namelookup}::%{time_connect}::%{time_total}"\n" http://www.baidu.com

這裡的各個參數代表含義(還有一些其他參數也可用):

  • time_total 總時間,按秒計。精確到小數點後三位。
  • time_namelookup DNS解析時間,從請求開始到DNS解析完畢所用時間。
  • time_connect 連接時間,從開始到建立TCP連接完成所用時間,包括前邊DNS解析時間,如果需要單純的得到連接時間,用這個time_connect時間減去前邊time_namelookup時間。以下同理,不再贅述。
  • time_appconnect 連接建立完成時間,如SSL/SSH等建立連接或者完成三次握手時間。
  • time_pretransfer 從開始到準備傳輸的時間。
  • time_redirect 重定向時間,包括到最後一次傳輸前的幾次重定向的DNS解析,連接,預傳輸,傳輸時間。
  • time_starttransfer 開始傳輸時間。在client發出請求之後,Web 伺服器返回數據的第一個位元組所用的時間

這樣能看到功能變數名稱解析、連接、傳輸各個階段的耗時情況,新老機器對比,如果有一項特別高,那麼這項肯定有問題

  • 新機器:0.001484::0.001743::0.007489
  • 老機器:0.000681::0.000912::0.002475

簡單計算一下:

  • 新機器:DNS解析耗時0.001484秒,連接建立耗時0.000258秒,總耗時0.007489秒
  • 老機器:DNS解析耗時0.000681秒,連接建立耗時0.000231秒,總耗時0.002475秒

雖然從這次的測試數據來看,新機器DNS解析似乎慢了一點,但你仔細看這個數值,幾乎對請求的總體耗時沒啥影響,而且多測試幾次,發現這兩台機器的DNS解析其實差不多。

但還是不放心,驗證DNS是否存在問題,再用dig命令去試一下

dig www.baidu.com

執行時,明顯感覺到了卡頓,確定是DNS有問題了。

image

問題解決

一開始,我去網上搜索了一下DNS慢的相關文章,找到了一篇文章《記一次Go net庫DNS問題排查》,但稍微驗證了下,和我的case沒啥關係,文章是好文章,所以也貼個鏈接,感興趣可以讀讀。

《記一次Go net庫DNS問題排查》https://juejin.cn/post/6948469896007122974

接著就去找了網路組的同學,網路組的同學稍微看了一眼就知道原因了,說新機器沒有安裝DNSmasq,這又是個啥?不要慌,先去網上查下再接話。

image

DNSmasq 提供 DNS 緩存和 DHCP 服務功能。作為功能變數名稱解析伺服器(DNS),DNSmasq可以通過緩存 DNS 請求來提高對訪問過的網址的連接速度。作為DHCP 伺服器,DNSmasq 可以用於為區域網電腦分配內網ip地址和提供路由。DNS和DHCP兩個功能可以同時或分別單獨實現。DNSmasq輕量且易配置,適用於個人用戶或少於50台主機的網路。此外它還自帶了一個 PXE 伺服器。

簡單來說,這裡它扮演的是一個DNS緩存的角色,提高DNS的查詢速度。

說到這裡,插播一個小知識,我一直以為DNS會被操作系統緩存,不知道你們有沒有這樣的錯覺,但實際上,Linux下如果沒有特殊處理,每一次DNS解析都要查詢DNS伺服器。很好證明,可以用tcpdump抓DNS的包試試,我當時也試了下,每次都會去遠程拿DNS解析結果。這個結論在《TCP/IP詳解捲1》中也能找到相關的描述:

image

只有Windows和比較新的Linux系統可以在客戶端緩存DNS,而且Linux系統是需要手動開啟的,所以預設情況下都要去遠程獲取DNS緩存。

言歸正傳,網路組同學說要麼裝一個DNSmasq,要麼改下DNS伺服器的配置,也就是/etc/resolv.conf文件,由於機器上已經有服務了,所以選擇了改配置這種比較安全的方式。

沒改之前,/etc/resolv.conf 的第一行是127.0.0.1,也就是將本地也作為DNS伺服器,但實際上本地沒有開啟DNS服務,網路組同學說,去掉第一行配置或者安裝DNSmasq都可以。

先是去掉了127.0.0.1的配置,結果耗時不變!

image

隨後加上127.0.0.1的配置,又安裝了DNSmasq後,耗時就降下去了。

image

整個解決的過程,程式沒有重啟,唯一的變數是安裝了DNSmasq,所以這一定是DNS的鍋了。

問題反思

雖然問題解決了,但我還有幾個疑問:

  1. 為什麼配置了127.0.0.1的DNS server,但沒有開啟DNSmasq呢?
  2. 為什麼去掉127.0.0.1配置會無效呢?

第1個問題比較好搞清楚,問了下系統部的同學,他說本來是應該開啟DNSmasq的,但出了一點點小差錯,結果只配置了127.0.0.1。

image

再看第2個問題,DNS本地緩存和遠程查詢差距這麼大嗎?據網路組同學說DNS server是公司內自建的,內網傳輸,實際並不慢,用dig也好測試,使用第2、3行的DNS server測試下,發現dig的速度都很快。

dig www.baidu.com @host

為什麼有了127.0.0.1的配置就變得很慢呢?下麵就從我的幾個猜測入手,一個個證明,但在猜測之前,我們先瞭解一下Go程式解析DNS的流程。

Go的DNS解析流程

Go的DNS解析分為兩種:

  • cgo方式,調用c語言標準庫的實現
  • 純Go代碼實現

由於要適配各個平臺,所以又有了各個平臺的實現。

這部分代碼位於net包下,想要跟蹤也很簡單,寫個建立連接的代碼,一步步debug,找到功能變數名稱解析的地方。

我直接告訴你從lookup_unix.go文件的lookupIP方法看起,當然這隻是Unix系統,包括Mac和Linux,不過Mac不走純Go的代碼,它被強制走到cgo了,在Linux上沒有特殊配置是走純Go實現的DNS解析,以下代碼以Linux為例:

func (r *Resolver) lookupIP(ctx context.Context, network, host string) (addrs []IPAddr, err error) {
	// ①強制走純Go的DNS解析器
	if r.preferGo() {
		return r.goLookupIP(ctx, host)
	}
	// ②根據解析順序解析
	order := systemConf().hostLookupOrder(r, host)
	if order == hostLookupCgo {
		if addrs, err, ok := cgoLookupIP(ctx, network, host); ok {
			return addrs, err
		}
		// cgo not available (or netgo); fall back to Go's DNS resolver
		// ③如果cgo搞不定,降級到先文件再DNS
		order = hostLookupFilesDNS
	}
	ips, _, err := r.goLookupIPCNAMEOrder(ctx, host, order)
	return ips, err
}

這裡order有如下幾種

hostLookupCgo      hostLookupOrder = iota // cgo
hostLookupFilesDNS                 // 文件優先
hostLookupDNSFiles                 // DNS優先
hostLookupFiles                    // 只查文件
hostLookupDNS                      // 只查DNS

這裡的文件也就是/etc/hosts,goLookupIP 最終也調用了 goLookupIPCNAMEOrder,但goLookupIPCNAMEOrder這個方法的代碼太長,所以我這裡只講一下大致的流程:

  1. 如果需要先查詢hosts文件,則先查,查到直接返回
  2. 讀取/etc/resolv.conf文件,拿出DNS server的配置,並且每5秒更新一次
  3. 構造DNS請求並向伺服器發送,UDP讀取的超時時間預設為5秒,可在/etc/resolv.conf文件中配置,同一個功能變數名稱的不同類型(如ipv4和ipv6)的查詢可配置為並行或串列
  4. 向DNS server發送請求採用的是輪詢機制,如果其中一個server請求出錯,則順延至下一個,重試次數預設為2,可在/etc/resolv.conf文件中配置
  5. 最後解析查詢結果並返回,如果結果為空,且配置了hosts文件兜底,則查詢一次文件

好了,流程簡單介紹到這裡,接下來驗證我的幾個猜想。

猜想一:Go是否只在程式啟動時讀取一次/etc/resolv.conf文件

這個猜想的依據是,如果查詢DNS時拿到了127.0.0.1的DNS server,且本地未開啟DNS服務時,可能會慢,且配置文件如果修改了,Go程式如果只在初始化時讀一次文件,那自然改配置文件無效。

但事實並非如此,上面也說了,Go在讀取DNS配置文件時是惰性地每隔5秒更新一次

func (conf *resolverConfig) tryUpdate(name string) {
	// 初始化,只做一次
  conf.initOnce.Do(conf.init)
  // ...
	now := time.Now()
	if conf.lastChecked.After(now.Add(-5 * time.Second)) {
		return
	}
	conf.lastChecked = now
  // ... 
	dnsConf := dnsReadConfig(name)
	conf.mu.Lock()
	conf.dnsConfig = dnsConf
	conf.mu.Unlock()
}

而且我做了個實驗,寫了個DNS解析的測試代碼,放在有127.0.0.1配置但未開啟DNSmasq的伺服器上跑,抓127.0.0.1 53埠(DNS預設埠)的包,發現是有流量的,然後修改/etc/resolv.conf配置,去掉127.0.0.1,發現抓不到127.0.0.1 53埠的流量了,這證明和代碼邏輯一致,本猜想不成立。

猜想二:DNS查詢遠程比本地慢很多

這個很好證明,還是用上面的程式

  1. 放在無127.0.0.1配置的伺服器上跑
  2. 放在有127.0.0.1配置且開啟DNSmasq的伺服器上跑

結果兩者耗時差不多,甚至他們和在有127.0.0.1配置但未開啟DNSmasq的伺服器上的耗時也基本一致。

這說明無論怎樣查詢DNS都不慢。

猜想三:是否是併發太高導致

為什麼我會有這個猜想呢,一是線上的QPS大概是50左右,和上面測試的場景不太一樣,二是我在上面的代碼中看到了鎖,是不是併發高了之後,鎖帶來的開銷變大導致?

我寫了個100併發的代碼,去查詢DNS,結果發現這段代碼在如下三種場景,耗時都差不多

  1. 無127.0.0.1配置的伺服器
  2. 有127.0.0.1配置且開啟DNSmasq的伺服器
  3. 有127.0.0.1配置且未開啟DNSmasq的伺服器

同時我也去問了網路組的同學,他說DNS server能抗住百萬QPS,服務端沒有壓力。

image

最後

寫到最後,我emo了~雖然問題解決了,但為什麼當時DNS查詢慢還是不知道,如果你看了文章知道其中哪裡有問題,或者有什麼比較好的排查方法,歡迎來探討,反正我是查不下去了。

最後再說一句,寫文章很辛苦,需要點鼓勵,來個點贊在看關註吧,我們下期再見。

搜索關註微信公眾號"捉蟲大師",後端技術分享,架構設計、性能優化、源碼閱讀、問題排查、踩坑實踐。

image


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • fastposter v2.7.1 緊急發佈 電商海報編輯器 fastposter海報生成器,電商海報編輯器,電商海報設計器,fast快速生成海報 海報製作 海報開發。二維碼海報,圖片海報,分享海報,二維碼推廣海報,支持Java Python PHP Go JS 小程式。基於Vue 和Pillow ...
  • 相信在座各位應該沒有幾個不看小說的吧,嘿嘿~ 一般來說咱們書荒的時候怎麼辦?自然是去起某點排行榜先找到小說名字,然後再找度娘一搜,哎 ,筆趣閣就出來答案了,美滋滋~ 但是那多麻煩,咱們直接用python,直接全部下載下來慢慢看不就好了~ 小孩子才做選擇,成年人選擇都要… 好了,不啰嗦了,等下大家要罵 ...
  • google 出品的依賴註入庫 wire:https://github.com/google/wire 什麼是依賴註入 依賴註入 ,英文全名是 dependency injection,簡寫為 DI。 百科解釋: 依賴註入是指程式運行過程中,如果需要調用另一個對象協助時,無須在代碼中創建被調用者,而 ...
  • 目錄 一.簡介 二.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 OpenGL (ES) 學習路 ...
  • 前言 最近有人對自動上傳與發佈很感興趣,都私下找我說了好幾次了。今天,必須把他安排,必須實力寵粉。 “本篇依次介紹目前主流的短視頻平臺(抖音、快手、B站、小紅書、微視、百度好看視頻、西瓜視頻、微信視頻號、搜狐視 頻、一點號、大風號、趣頭條等)的短視頻自動發佈,希望幫助大家更方便、高效的來進行自媒體的 ...
  • LeetCode_141:https://leetcode-cn.com/problems/linked-list-cycle/ 給你一個鏈表的頭節點 head ,判斷鏈表中是否有環。 如果鏈表中有某個節點,可以通過連續跟蹤 next 指針再次到達,則鏈表中存在環。 如果鏈表中存在環 ,則返回 tr ...
  • 開門見山。 這篇文章,教大家用Python實現常用的假設檢驗! 服從什麼分佈,就用什麼區間估計方式,也就就用什麼檢驗! 比如:兩個樣本方差比服從F分佈,區間估計就採用F分佈計算臨界值(從而得出置信區間),最終採用F檢驗。 建設檢驗的基本步驟: 前言 假設檢驗用到的Python工具包 •Statsmo ...
  • 最近發現一個視頻網站,準備去爬取得時候,前面很順利 利用fiddler抓包獲取網站的post數據loads為python字典數據,分析數據就能發現每個視頻的連接地址就在其中 發現這些都是m3u8文件流的形式並且加密的 key 最後實現代碼如下: 下載下來後用暴風音影可以播放,其他播放器要用格式工廠轉 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...