I think I just made go-github 4x faster in 4 lines of code (and 4hrs). Always make sure you are reusing connections! https://t.co/ORCv1a4prv
— Filippo Valsorda (@FiloSottile) March 27, 2016
前言
主要是因為這一份twiiter讓我注意到,Filippo Valsorda(@FiloSottile) 提到說他只花了四行程式碼就讓go-github (Google 出的直接操控github 的package) 連線速度可以增加四倍
原因?
根據這次的PR,可以知道主要的原因是在於json decoder對於io.reader讀取資料的時候. 不會一次把所有的直抓完,而會剩下一個 \n
由於這個因素 response.Body
(io.Readcloser)呼叫close的時候會把整個TLS的connect關閉.造成每次的connection 都會重新啟動,浪費了許多無謂的時間.
解法
針對Reader的行為而言,如果是讀到了最後一個位元,接下來的讀取都會讀到EOF
. 這裡有一個範例可以參考.
(When Read encounters an error or end-of-file condition after successfully reading n > 0 bytes, it returns the number of bytes read. It may return the (non-nil) error from the same call or return the error (and n == 0) from a subsequent call. An instance of this general case is that a Reader returning a non-zero number of bytes at the end of the input stream may return either err == EOF or err == nil. The next Read should return 0, EOF.)
所以如果不是使用json decoder,而是使用其他方式來讀資料.就會連\n
都讀完,使得response.Body
清空後close不會直接把connect 整個關閉.
I wrote new #golang package that can make your app 4x faster. #joke / “GitHub - mattn/go-drainclose” https://t.co/HwANVx40L5
— mattn (@mattn_jp) March 29, 2016
所以mattn也提供了一個小套件,其實就是把這件事情實現:
resp, err := http.Get(blah)
if err != nil {
return err
}
json.NewDecoder(resp.Body).Decode(&data)
// 如果reader 已經被清掉,直接結束
if resp.Body == nil {
return nil
}
// 如果body reader有存在,先把它清空 (drain)
// 透過io.Copy的方式把 resp.Body清空
if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
return err
}
// 這樣一來這樣的Close就不會將TLS connection關閉.
return resp.Body.Close()
此外,透過mattn的這篇文章,也有提到.json.Unmarlshal
並不會有這個問題.
2016/03/31 更新
Bradfitz 也就是Go net/http的作者就跳出來說要把這個問題修回去Golang裡面,避免以後其他部分的影響.不過這部分的修改,已經趕不上Go 1.6之中了,大家要稍微注意一下.