Go語言·Web調優詳解

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

前言

很早以前crypto/tls(TLS長串連庫)和net/http的效能不敢恭維,因此我們都使用Nginx做反向 Proxy,但是Go1.8將要來了,這種格局即將被打破了!

我們最近嘗試性的將Go1.8編譯的服務暴漏到了外網,結果發現crypto/tls 和net/http都得到了極大的提升:穩定性、效能以及服務的延展性!

crypto/tls

現在已經是2016年了,我們不可能再去裸奔在互連網了,因此基於TLS是必然的選擇,所以我們需要crypto/tls這個庫。好訊息就是在1.8下,該庫的效能得到了很大的提升,效能表現堪稱十分優秀,而且安全性也非常出色。

預設推薦的配置類似
[Mozilla標準] (https://wiki.mozilla.org/Security/Server_Side_TLS),然而我們應該要設定PreferServerCipherSuits為true,這樣可以使用更安全更快速的密文族;設定CurvePreferences避免未最佳化的Curve;選擇CurveP256而不是CurveP384,因為後者可能會為每個用戶端消耗將近1秒的cpu時間!!

&tls.Config{      PreferServerCipherSuites: true,      // 僅僅使用擁有彙編實現的Curve      CurvePreferences: []tls.CurveID{          tls.CurveP256,          tls.X25519, // Go 1.8 only      },  } 

如果可以接受TLS相容性上可能存在的問題(例如版本問題,下面的配置建議更現代化,因此對老版本可能不夠相容),還可以設定MinVersion和CipherSuites

MinVersion: tls.VersionTLS12,      CipherSuites: []uint16{          tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,          tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,          tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // Go 1.8 only          tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,   // Go 1.8 only          tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,          tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,            // 最好禁用下面的參數,因為沒有提供向前的安全性,但是對於部分用戶端可能需要開啟          // tls.TLS_RSA_WITH_AES_256_GCM_SHA384,          // tls.TLS_RSA_WITH_AES_128_GCM_SHA256,      }

Go的CBC密碼族實現在Lucky13攻擊下,不夠穩定,因此我們在上面的配置中禁用了CBC密碼族,雖然go1.8已經進行了改善。

注意!上述的最佳化只針對amd64架構,在此架構下,我們甚至可以考慮cloudflare公司的開源的效能極高的加密版本(AES-GCM,Chacha20-Poly2305,P256)。

當然,我們還需要認證,這裡我們可以使用golang.org/x/crypto/acme/autocert和Letss Encrypt,同時別忘了將http請求重新導向到https,如果你的用戶端是瀏覽器,還可以考慮HSTS.

srv := &http.Server{      ReadTimeout:  5 * time.Second,      WriteTimeout: 5 * time.Second,      Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {          w.Header().Set("Connection", "close")          url := "https://" + req.Host + req.URL.String()          http.Redirect(w, req, url, http.StatusMovedPermanently)      }),  }  go func() { log.Fatal(srv.ListenAndServe()) }()  

配置完後,我們可以使用SSL labs test來檢查我們的TLS是否正確

net/http

net/http是一個成熟的HTTP1.1和HTTP2協議棧,具體怎麼用,這裡就不贅述了,我們來講講伺服器端背後的故事。

timeouts

在外網環境中,這個參數是最重要的也是最容易被忽視的之一!你的後端服務如果不設定逾時,在內網環境可能還Ok,但是到了外網環境,那就是災難,特別是在遇到攻擊時。

Timeouts的應用是一種資源控制,就算goroutine很廉價,但是檔案描述符fd很昂貴的,一個不再工作或者長閑置的串連是不該去佔用寶貴的fd的。

當伺服器的fd不夠用時,在accept新串連時就會失敗,報錯如下:

  http: Accept error: accept tcp [::]:80: accept: too many open files; retrying in 1s  

預設的net/http的http.Server,可以通過http.ListenAndServe和http.ListenAndServeTLS建立,是沒有timeouts的,這完全不是我們想要的。


如所示,http.Server主要有三種timeouts,ReadTimeout,WriteTimeout,IdleTimeout,我們可以這樣設定:

srv := &http.Server{      ReadTimeout:  5 * time.Second,      WriteTimeout: 10 * time.Second,      IdleTimeout:  120 * time.Second,      TLSConfig:    tlsConfig,      Handler:      serveMux,  }  log.Println(srv.ListenAndServeTLS("", ""))  

ReadTimeout是從串連accept一直到所有請求的body被完全讀取(如果不讀body,那就是所有header被讀取)。該逾時是net/http包在串連accept之後直接設定SetReadDeadline的。

ReadTimeout存在一個問題,伺服器沒有給更多的時間來串流來自用戶端的資料。因此Go1.8引入了ReadHeaderTimeout,這裡的逾時僅僅針對Header的讀取逾時,當然這個沒有解決根本問題,因此新的解決方案在issue#16100有進一步的討論,關於怎麼在Handler中處理ReadTimeout。

WriteTimeout是從包頭讀取成功開始,一直到回複(response)的寫入,是在readRequest的末尾調用SetWriteDeadline函數實現的。

當串連是HTTPS時,SetWriteDeadline會在串連accept後立刻調用一次,這裡是處理TLS的握手逾時。因此,這次逾時是在HTTP包頭讀取或者等待第一個位元組傳輸之前結束。

和ReadTimeout一樣,WriteTimeout也無法從Handler中進行相對控制:issue#16100

最後是IdleTimeout,這個是在Go1.8引入的一個很有用的參數,用來控制伺服器端KeepAlive的串連允許閒置最大時間。在go1.8之前,ReadTimeout有一個很大的問題,對於Keepalive的串連是不友好的(儘管可以在應用程式層來解決Idle的逾時問題):因為在上一個請求的讀取完畢後,下一個請求的ReadTimeout會立即開始重新計時,這樣串連閒置時間也算在ReadTimeout內,造成了串連的過早斷開。

綜上所述,當我們在Go1.8中處理外部不受信任的串連時,我們要設定上這三個逾時,這樣用戶端就不會因為各種過慢的寫或者讀,一直霸佔串連了。

http2

在Go1.6版本及之後,HTTP2會自動開啟,若且唯若:

  • 請求是基於TLS/HTTPS的
  • Server.TLSNextProto設定為nil(注意,如果設定為空白map,那會禁用HTTP2)
  • Server.TLSConfig被設定並且ListenAndServerTLS被使用;或者,使用Serve,同時tls.Config.NextProtos包含了"h2",例如[]string{"h2","http/1.1"}

同時在Go1.8版本修複了一個關於HTTP2的ReadTimeout的Bug,再結合1.8的其它特性,我的建議是儘快升級1.8。

tcp keepalive

如果你在用ListenAndServe(與此相對的是給Serve傳一個net.Listener參數,但是這種方式沒有做任何防護),那麼三分鐘長的TCP keepalive時間將自動被設定

如果你用的是TCP長串連服務,那麼你該使用net.ListenTCP,同時設定keepalive時間,根據我的經驗,如果不設定這個,那麼長串連存在泄漏的風險,後面我會詳細寫一篇文章分析TCP串連泄漏的問題。

metrics

我們可以用Server.ConnState來擷取串連的狀態,注意,我們要自己維護map[net.Conn]ConnState。

總結

以後再也不用在Go的Web服務前再前置一個Nginx了,節省了伺服器同時也降低了請求的延遲,前提是,我們使用了Go1.8。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.