背景分享
遇到過這么一個(gè)問題,有童鞋的 Go 程序用 DNS 解析做服務(wù)發(fā)現(xiàn)(內(nèi)網(wǎng)用的 CoreDNS 做的域名解析服務(wù)器)。比如,內(nèi)網(wǎng)有個(gè)服務(wù)域名,對應(yīng) 7 個(gè)后端節(jié)點(diǎn)。為了做服務(wù)發(fā)現(xiàn),故障的剔除等服務(wù),在 Client 端對一個(gè)給定的域名調(diào)用 Go 標(biāo)準(zhǔn)庫的 Resolver.LookupHost 方法來解析 ip 列表。如果解析得到的 ip 列表有變化,那么在 Client 內(nèi)對相應(yīng)的對后端節(jié)點(diǎn)的鏈接做創(chuàng)建和銷毀。
addrs,err:=resolver.LookupHost(ctx,/*某服務(wù)域名A*/) //addrs的結(jié)果會(huì)變化,一會(huì)返回6個(gè)ip,一會(huì)返回7個(gè)ip
就是這么一個(gè)典型的服務(wù)發(fā)現(xiàn)的應(yīng)用場景,還是精準(zhǔn)踩坑。那什么坑?
坑就是:解析得到的 ip 列表反復(fù)變化,導(dǎo)致反復(fù)創(chuàng)、刪連接和對應(yīng)的結(jié)構(gòu)體。讓人誤以為 DNS 的后端節(jié)點(diǎn)一直在故障,從而導(dǎo)致一系列的問題。
還遇到另一個(gè)有趣的問題:同一份業(yè)務(wù)代碼,Go 1.15 編譯的版本總會(huì)頻繁截?cái)喑?6 個(gè) ip ,Go 1.16 以上的版本則非常穩(wěn)定,一直返回 7 個(gè) ip ? 這又是為啥呢。
這個(gè)問題很簡單,但其實(shí)也很隱蔽。因?yàn)楹苌偃藭?huì)這么用,也很少人會(huì)注意到這個(gè)問題。
Go 的 DNS Lookup 的接口語義
先看下 Go 標(biāo)準(zhǔn)庫的接口語義,看下 Resolver.LookupHost 在 Go 的注釋怎么說的。文件在 Go 的標(biāo)準(zhǔn)庫 net/lookup.go :
//LookupHostlooksupthegivenhostusingthelocalresolver. //Itreturnsasliceofthathost'saddresses. func(r*Resolver)LookupHost(ctxcontext.Context,hoststring)(addrs[]string,errerror){ //... }
LookupHost 查詢一個(gè)給定的域名,返回值是一個(gè)地址列表。注意:它并沒有保證,要返回該域名的所有 ip 列表。 所以啊,這本來就是用法不對,Go 的接口沒聲明說要返回全部的 ip 。哪怕有域名對應(yīng)有 100 個(gè) ip ,這個(gè)接口只返回 1 個(gè)也是對的。
Go 1.15 和 Go 1.16之上的區(qū)別 ?
域名對應(yīng) 7 個(gè) ip ,同一份解析代碼, Go 1.15 編譯的程序時(shí)而返回 6 個(gè)?但 Go 1.16 之上的版本編譯則總是 7 個(gè),感覺非常穩(wěn)定。為什么呢?
筆者還真翻了一下 Go 1.15 和 Go 1.16 的區(qū)別,DNS 解析的代碼幾乎一致,只在 dnsPacketRoundTrip 函數(shù)中,改了一個(gè) buffer 的大小。
Go 1.15 是這樣的( 文件:src/net/dnsclient_unix.go ):
funcdnsPacketRoundTrip(cConn,iduint16,querydnsmessage.Question,b[]byte)(dnsmessage.Parser,dnsmessage.Header,error){ //發(fā)送請求 if_,err:=c.Write(b);err!=nil{ } //創(chuàng)建一個(gè)裝響應(yīng)包的buffer b=make([]byte,512)//seeRFC1035 for{ //讀取dns響應(yīng) n,err:=c.Read(b) //... returnp,h,nil } }
Go 1.16 是這樣的( 文件:src/net/dnsclient_unix.go ):
const( //MaximumDNSpacketsize. //Valuetakenfromhttps://dnsflagday.net/2020/. maxDNSPacketSize=1232 ) funcdnsPacketRoundTrip(cConn,iduint16,querydnsmessage.Question,b[]byte)(dnsmessage.Parser,dnsmessage.Header,error){ //發(fā)送請求 if_,err:=c.Write(b);err!=nil{ } //創(chuàng)建一個(gè)裝響應(yīng)包的buffer b=make([]byte,maxDNSPacketSize) for{ //讀取dns響應(yīng) n,err:=c.Read(b) //... returnp,h,nil } }
函數(shù)邏輯是發(fā)送請求給 DNS Server ,并等待它的響應(yīng)。兩個(gè)版本完全一致,只有 buffer 的大小不一樣,Go 1.16 之上用了 1232 這個(gè)大小。請注意,這個(gè)大小其實(shí)是有講究的,這個(gè)值是在盡量避免 IP 包分片又能盡量多裝數(shù)據(jù)而拍的一個(gè)值。詳細(xì)看 DNS FLAG DAY 2020[1] 。
這就是 Go 1.15 ,Go 1.16 版本在內(nèi)網(wǎng)域名解析中的差異。DNS 服務(wù)端雖然發(fā)了 7 個(gè) ip 過來,但是 Go 1.15 編譯的版本用 512 個(gè)字節(jié) buffer 裝不下,只解析到 6 個(gè)有效 ip,Go 1.16 版本則好點(diǎn),客戶端用的 1232 個(gè)字節(jié)的 buffer 大一點(diǎn),差別就在這個(gè)地方。
這里有個(gè)細(xì)節(jié)提一下:
DNS 的協(xié)議,Message 的 Header 有四個(gè)字段 QDCOUNT,ANCOUNT,NSCOUNT,ARCOUNT,是指明了數(shù)據(jù)包里各個(gè) Record 有多少個(gè),Answer 有多少個(gè)的。但是在協(xié)議實(shí)現(xiàn)的時(shí)候,往往不依賴于這幾個(gè)字段,因?yàn)樗鼈兛赡鼙粋卧旃簟K越馕龅?ip 列表都是按照實(shí)際解析結(jié)果來的,解析到多少個(gè)就多少個(gè),而不是 Header 里聲明了多少個(gè)。
//Qdcount,Ancount,Nscount,Arcountcan'tbetrusted,astheyare //attackercontrolled.
簡單說下 DNS
DNS 協(xié)議默認(rèn)使用 UDP 協(xié)議作為其傳輸層協(xié)議。UDP 的數(shù)據(jù)包是有限制的,DNS 的消息大小也是有限制的,基本大小限制為 512 字節(jié),長消息會(huì)被截?cái)嗖⑶以O(shè)置標(biāo)記位。
所以,DNS 協(xié)議本身就從來沒承諾過,給你解析完整的 IP 列表。它這個(gè)名字對應(yīng)的 IP 而已,至于全不全,它從沒承諾過。
本文并不詳細(xì)介紹 DNS 協(xié)議的原理。詳細(xì)見 RFC 1035[2] 和相關(guān)的文檔。為了突破數(shù)據(jù)包大小,或者其他的限制,也有擴(kuò)展協(xié)議 EDNS ,可以參考 RFC 6891[3] 。
總之,用 DNS 解析 ip 這種方式來替代 consul 這種服務(wù)發(fā)現(xiàn)。感覺還是有欠缺的,或者說它的使用場景是不一樣的。
總結(jié)
Go 默認(rèn)的解析方式其實(shí)有兩種,一種是 cgo 的方式調(diào)用 libc 庫的函數(shù)去解析,一種是 Go 自己的實(shí)現(xiàn)。本文討論的是默認(rèn)的 Go 的方式。
DNS 做服務(wù)發(fā)現(xiàn)好像并不合適,和 consul 等組件不同。它有自己特定的協(xié)議約束著,如果一定要用 DNS 來做服務(wù)發(fā)現(xiàn),那么請千萬要注意本文提到的知識點(diǎn)。
DNS 解析的 ip 列表,并不承諾它是全的。如果業(yè)務(wù)想用來做服務(wù)發(fā)現(xiàn)和剔除的功能,請千萬牢記。
DNS 服務(wù)端和客戶端的行為配合缺一不可,服務(wù)端會(huì)不會(huì)發(fā)全部?客戶端能不能收全部?各種差異都會(huì)導(dǎo)致解析出來的 ip 可能不一樣。
-
接口
+關(guān)注
關(guān)注
33文章
8844瀏覽量
152786 -
服務(wù)器
+關(guān)注
關(guān)注
12文章
9542瀏覽量
86822 -
編譯
+關(guān)注
關(guān)注
0文章
672瀏覽量
33438
原文標(biāo)題:DNS 做服務(wù)發(fā)現(xiàn),是坑嗎 ?
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論