前言

主要是因為這一份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 整個關閉.

所以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之中了,大家要稍微注意一下.


Evan

Attitude is everything