你真的了解 timeout 吗?
本文來自董神,一個(gè)和曹大談笑風(fēng)生的男人:
服務(wù)為什么需要 timeout 呢?提前釋放資源。
記得在上家公司時(shí),一個(gè) python 服務(wù)與公網(wǎng)交互,request 庫發(fā)出去的請求沒有設(shè)置 timeout... 而且還是個(gè)定時(shí)任務(wù),占用了超多 fd。
同時(shí)微服務(wù)場景下某下游的服務(wù)阻塞卡頓,這樣會造成他的級聯(lián)上下游都雪崩了。
語言層面:對于使用線程池的語言,會消耗所有線程,worker 不夠用。其實(shí)對于 Go 來說,創(chuàng)建大量 goroutine 也會有 runtime 開銷的, 只是慢性死亡罷了。
內(nèi)核層面:還有一點(diǎn)超時(shí)配置的必要性,如果某服務(wù)掛了,那么內(nèi)核會幫忙收尾,根據(jù)情況或走 RST 或走 FIN,訪問者就知道連接關(guān)了。但如果主機(jī)掛了,或者中間網(wǎng)絡(luò)設(shè)備掛了,客戶端沒有超時(shí)配置,就只能通過 tcp keepalive 來判斷死鏈接,按照默認(rèn)內(nèi)核配置語言兩個(gè)多小時(shí),文末提到 redis 就是例子。
Latency
業(yè)界都用 P99 分位來衡量服務(wù)的 latency,即使這樣如果 QPS 非常高,另外 1% 的請求也會出現(xiàn) long tail。再來看幾個(gè)不同側(cè)重點(diǎn)的概念:
Server Side P99 統(tǒng)計(jì)的只是 server handler 處理時(shí)間。
Client P99 = ?client framework 時(shí)間 + client 內(nèi)核處理時(shí)間 + 網(wǎng)絡(luò)傳輸時(shí)間 + server 處理時(shí)間。
當(dāng)你發(fā)現(xiàn) latency 比較高,想去 challenge 下游時(shí),請對好口徑。通常 client p99 > server p99。
這還是普通的 server/client 模式,如果中間涉及了 lb, 或是 mesh 排查問題更要命。
可觀測性
現(xiàn)在都是微服務(wù)場景,一個(gè)訂單全鏈路涉及幾十個(gè)服務(wù),查起問題非常困難,所以分布式的 tracing 系統(tǒng)非常重要。
另外現(xiàn)在也都擁抱云原生環(huán)境,如果引入 service mesh 的話更難以排查問題。
一般 tracing 系統(tǒng)都是根據(jù) google 論文 Dapper, a Large-Scale Distributed Systems Tracing Infrastructure[1]發(fā)展而來的。
除了自己造輪子,主流的有zipkin[2], opentelemetry[3]
底層實(shí)現(xiàn)
定時(shí)器這塊業(yè)務(wù)早有標(biāo)準(zhǔn)實(shí)現(xiàn):小頂堆, 紅黑樹 和 時(shí)間輪。感興趣的同學(xué)可以搜索相關(guān)文章
原理不難,但是有公司面試都要求手寫紅黑樹,這就過份了吧。
Linux 內(nèi)核和 Nginx 的定時(shí)器采用了 紅黑樹 實(shí)現(xiàn),好多長連接系統(tǒng)多采用 時(shí)間輪。
Go 使用 小頂堆,四叉堆,比較矮胖,不是最樸素的二叉堆。
最早版本只有一個(gè) timer 堆,所以性能非常差,精度也有問題。一般都用戶實(shí)現(xiàn)多堆,或是用時(shí)間輪實(shí)現(xiàn)。這方面的輪子比寫公眾號的碼農(nóng)都多 ^_^
后來經(jīng)過優(yōu)化 Go 內(nèi)置多堆實(shí)現(xiàn),每個(gè) P 一個(gè) timer 堆,性能好了很多。注意,Go 的 conn timeout 是通過用戶層 timer 實(shí)現(xiàn)的,而不是內(nèi)核的 setsockopt。
HTTP
這里要區(qū)分 http1 和 http2,以前寫過一篇 HOL blocking 的文章,感興趣可以翻下歷史
Http1 如果超時(shí)到了,那么底層庫是要關(guān)閉 tcp connection 的,強(qiáng)制丟棄未讀到的數(shù)據(jù),這時(shí)會產(chǎn)生大量的 timewait,要注意。
但是對于 Http2 來說,虛擬出來了 stream,做到了多路復(fù)用,只要關(guān)閉 stream 即可,底層 socket 還可以正常使用。
對于 Go Http 還有一個(gè)坑,可以參考 i/o timeout,希望你不要踩到這個(gè)net/http包的坑。
func?init()?{tr?=?&http.Transport{MaxIdleConns:?100,Dial:?func(netw,?addr?string)?(net.Conn,?error)?{conn,?err?:=?net.DialTimeout(netw,?addr,?time.Second*2)?//設(shè)置建立連接超時(shí)if?err?!=?nil?{return?nil,?err}err?=?conn.SetDeadline(time.Now().Add(time.Second?*?3))?//設(shè)置發(fā)送接受數(shù)據(jù)超時(shí)if?err?!=?nil?{return?nil,?err}return?conn,?nil},} }上面代碼是錯(cuò)誤使用,這個(gè)導(dǎo)致每次 conn 連接后只設(shè)置一次超時(shí)時(shí)間:
????client?:=?&http.Client{Transport:?tr,Timeout:?3*time.Second,??//?超時(shí)加在這里,是每次調(diào)用的超時(shí)}正確的應(yīng)該在 http.Client 結(jié)構(gòu)體里設(shè)置,感興趣的去參考全文吧。
另外服務(wù)端也要設(shè)置 timeout,以防把服務(wù)端壓跨,請參考So you want to expose Go on the Internet[4]
??srv?:=?&http.Server{ReadTimeout:??5?*?time.Second,WriteTimeout:?10?*?time.Second,IdleTimeout:??120?*?time.Second,TLSConfig:????tlsConfig,Handler:??????serveMux, } log.Println(srv.ListenAndServeTLS("",?""))數(shù)據(jù)庫相關(guān)
做為 CRUD Boy,經(jīng)常和 DB 打交道,讓我們來看下常見的超時(shí)設(shè)置與坑。
Redis 服務(wù)端要注意兩個(gè)參數(shù):timeout 和 tcp-keepalive。
其中 timeout 用于關(guān)閉 idle client conn,默認(rèn)是 0 不關(guān)閉,為了減少服務(wù)端 fd 占用,建議設(shè)置一個(gè)合理的值。
tcp-keepalive 在很早的 redis 版本是不開啟的,這樣經(jīng)常會遇到因?yàn)榫W(wǎng)格抖動等原因,socket conn 一直存在,但實(shí)際上 client 早已經(jīng)不存在的情況。
Redis Client 實(shí)現(xiàn)有一個(gè)重大問題,對于集群環(huán)境下,有些請求會做 Redirect 跳轉(zhuǎn),默認(rèn)是 16 次,如果 tcp read timeout 設(shè)置了 100ms,那總時(shí)間很可能超過了 1s。
這就是一直強(qiáng)調(diào)的問題,tcp timeout 設(shè)置不代表實(shí)際的調(diào)用時(shí)間,因?yàn)闃I(yè)務(wù)層會多次調(diào)用 socket 讀寫。最好外面包一層 context 或是 circuit breaker。
MySQL 也同樣服務(wù)端可以設(shè)置 MAX_EXECUTION_TIME 來控制 sql 執(zhí)行時(shí)間。不同發(fā)行版本還不一樣,有的只支持 select,有的同時(shí)支持 dml ddl...
其它
Q: timeout 與 sla 什么關(guān)系?
A: 要大于 sla。沒有經(jīng)過 toB 業(yè)務(wù)的重錘,感觸不深,有朋友了解的可以留言講講 toB 業(yè)務(wù)的玩法。
Q: 如何傳遞 timeout?
A: 一般都是框架層傳遞的,比如 grpc 會在 header 里傳遞服務(wù)的 timeout, 每經(jīng)過一個(gè) backend,減去相應(yīng)的耗時(shí)。
Q: 依賴的下游出現(xiàn)大量超時(shí),應(yīng)該如何處理?
A: 要做到 fast fail,一定得有降級 (circuit breaker 熔斷)措施,否則會拖垮整條鏈路。
這次分享就這些,以后面還會分享更多的內(nèi)容,如果感興趣,可以關(guān)注并點(diǎn)擊左下角的分享轉(zhuǎn)發(fā)哦~
參考資料
[1]
Dapper, a Large-Scale Distributed Systems Tracing Infrastructure:?https://research.google/pubs/pub36356/
[2]zipkin:?https://zipkin.io/
[3]opentelemetry:?https://opentelemetry.io/docs/concepts/distributions/
[4]So you want to expose Go on the Internet:?https://blog.cloudflare.com/exposing-go-on-the-internet/
最后,歡迎關(guān)注我的公眾號~
總結(jié)
以上是生活随笔為你收集整理的你真的了解 timeout 吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 曹大带我学 Go(5)—— 哪里来的 g
- 下一篇: 跟着邓神 3 天掌握 Go 语言基础(免