<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          你真的懂 timeout 嗎?

          共 5122字,需瀏覽 11分鐘

           ·

          2021-09-30 20:40

          服務(wù)為什么需要 timeout 呢?提前釋放資源

          記得在上家公司時,一個 python 服務(wù)與公網(wǎng)交互,request 庫發(fā)出去的請求沒有設(shè)置 timeout ... 而且還是個定時任務(wù),占用了超多 fd

          同時微服務(wù)場景下某下游的服務(wù)阻塞卡頓,這樣會造成他的級聯(lián)上下游都雪崩了。

          語言層面:對于使用線程池的語言,會消耗所有線程,work 不夠用。其實對于 go 來說,創(chuàng)建大量 goroutine 也會有 runtime 開銷的, 只是慢性死亡罷了

          內(nèi)核層面:還有一點超時配置的必要性,如果某服務(wù)掛了,那么內(nèi)核會幫忙收尾,根據(jù)情況或走 RST 或走 FIN,訪問者就知道鏈接關(guān)了。但如果主機掛了,或者中間網(wǎng)絡(luò)設(shè)備掛了,客戶端沒有超時配置,就只能 tcp keepalive 來判斷死鏈接,按照默認內(nèi)核配置語言兩個多小時。文末提到 redis 就是例子

          Latency

          業(yè)界都用 P99 分位來衡量服務(wù)的 latency, 即使這樣如果 QPS 非常高,另外 1% 的請求也會出現(xiàn) long tail. 再來看幾個不同側(cè)重點的概念:

          Server Side P99 統(tǒng)計的只是 server handler 處理時間

          Client P99 =  client framework 時間 + client 內(nèi)核處理時間 + 網(wǎng)絡(luò)傳輸時間 + server 處理時間

          當你發(fā)現(xiàn) latency 比較高,想去 challenge 下游時,請對好口徑。通常 client p99 > server p99

          這還是普通的 server/client 模式,如果中間涉及了 lb, 或是 mesh 排查問題很要命

          可觀測性

          現(xiàn)在都是微服務(wù)場景,一個訂單全鏈路涉及幾十個服務(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]

          底層實現(xiàn)

          定時器這塊業(yè)務(wù)早有標準實現(xiàn):小頂堆, 紅黑樹時間輪. 感興趣的同學(xué)可以搜索相關(guān)文章

          原理不難,但是有公司面試都要求手寫紅黑樹?。。∵@就過份了吧

          Linux 內(nèi)核和 Nginx 的定時器采用了 紅黑樹 實現(xiàn),好多長連接系統(tǒng)多采用 時間輪

          Go 使用 小頂堆, 四叉堆,比較矮胖,不是最樸素的二叉堆。

          最早版本只有一個 timer 堆,所以性能非常差,精度也有問題。一般都用戶實現(xiàn)多堆,或是用時間輪實現(xiàn)。這方面的輪子比寫公眾號的碼農(nóng)都多 ^_^

          后來經(jīng)過優(yōu)化 Go 內(nèi)置多堆實現(xiàn),每個 P 一個 timer 堆,性能好了很多。注意,Go 的 conn timeout 是通過用戶層 timer 實現(xiàn)的,而不是內(nèi)核的 setsockopt

          HTTP

          這里要區(qū)分 http1 和 http2, 以前寫過一篇 HOL blocking 的文章,感興趣可以翻下歷史

          Http1 如果超時到了,那么底層庫是要關(guān)閉 tcp connection 的,強制丟棄未讀到的數(shù)據(jù),這時會產(chǎn)生大量的 timewait, 要注意

          但是對于 Http2 來說,虛擬出來了 stream, 做到了多路復(fù)用,只要關(guān)閉 stream 即可,底層 socket 還可以正常使用

          對于 go http 還有一個坑,可以參考 i/o timeout , 希望你不要踩到這個net/http包的坑[4]

          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è)置建立連接超時
                      if err != nil {
                          return nil, err
                      }
                      err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //設(shè)置發(fā)送接受數(shù)據(jù)超時
                      if err != nil {
                          return nil, err
                      }
                      return conn, nil
                  },
              }
          }

          上面代碼是錯誤使用,這個導(dǎo)致每次 conn 連接后只設(shè)置一次超時時間

              client := &http.Client{
                  Transport: tr,
                  Timeout: 3*time.Second,  // 超時加在這里,是每次調(diào)用的超時
              }

          正確的應(yīng)該在 http.Client 結(jié)構(gòu)體里設(shè)置,感興趣的去參考全文吧

          另外服務(wù)端也要設(shè)置 timeout, 以防把服務(wù)端壓跨,請參考 So you want to expose Go on the Internet[5]

            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è)置與坑

          Redis 服務(wù)端要注意兩個參數(shù):timeouttcp-keepalive

          其中 timeout 用于關(guān)閉 idle client conn, 默認是 0 不關(guān)閉,為了減少服務(wù)端 fd 占用,建議設(shè)置一個合理的值

          tcp-keepalive 在很早的 redis 版本是不開啟的,這樣經(jīng)常會遇到因為網(wǎng)格抖動等原因,socket conn 一直存在,但實際上 client 早己經(jīng)不存在的情況

          Redis Client 實現(xiàn)有一個重大問題,對于集群環(huán)境下,有些請求會做 Redirect 跳轉(zhuǎn),默認是 16 次,如果 tcp read timeout 設(shè)置了 100ms, 那總時間很可能超過了 1s

          這就是一直強調(diào)的問題,tcp timeout 設(shè)置不代表實際的調(diào)用時間,因為業(yè)務(wù)層會多次調(diào)用 socket 讀寫。最好外面包一層 context 或是 circuit breaker

          MySQL 也同樣服務(wù)端可以設(shè)置 MAX_EXECUTION_TIME 來控制 sql 執(zhí)行時間。不同發(fā)行版本還不一樣,有的只支持 select, 有的同時支持 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)過一個 backend, 減去相應(yīng)的耗時

          Q: 依賴的下游出現(xiàn)大量超時,應(yīng)該如何處理?

          A: 要做到 fast fail, 一定得有降級 (circuit breaker 熔斷)措施,否則會拖垮整條鏈路。

          小結(jié)

          這次分享就這些,以后面還會分享更多的內(nèi)容,如果感興趣,可以關(guā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]

          i/o timeout , 希望你不要踩到這個net/http包的坑: https://mp.weixin.qq.com/s/UBiZp2Bfs7z1_mJ-JnOT1Q,

          [5]

          So you want to expose Go on the Internet: https://blog.cloudflare.com/exposing-go-on-the-internet/,


          瀏覽 37
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91在线视频观看 | 北条麻妃影音先锋 | 天天拍天天日 | 欧美大码一区二区免费看 | 免费无码又爽又高潮视频 |