這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
今天給大家介紹3個我覺得比較有啟發的Golang小技巧,分別是以下幾個程式碼片段
- nsq裡的select寫檔案和socket
- io模組裡的sendfile
- fasthttp裡對header的處理
nsq裡的select讀
在nsq中,需要讀取之前磁碟上的,或者是從記憶體中直接讀取,一般人都是先判斷記憶體中有沒有資料,然而,nsq另闢蹊徑使用了select語句,把CSP模式用到了極致。
源檔案連結:channel.go
select { case msg = <-c.memoryMsgChan: //嘗試從記憶體中讀取 case buf = <-c.backend.ReadChan(): //如果記憶體中沒有,直接從磁碟上讀取 msg, err = decodeMessage(buf) if err != nil { c.ctx.nsqd.logf("ERROR: failed to decode message - %s", err) continue }
io模組中的sendfile
經過精巧的io.ReadFrom interface設計,sendfile對上層的http handler完全透明,具體調用
+----------------+ | http.ServeFile | +--------+-------+ | +--------+--------+ +----------------+ +---------------------------------+ | os.File +------> io.Copy | | http.Response | +--------+--------+ +--------+-------+ | +-----------------------------+ | | | | | net.TCPConn | | | +--------v-------+ 2. has? | | +-------------------------+ | | | | io.CopyBuffer +---------> | | | io.ReadFrom | | +-----+ | +--------+-------+ | | | +---------------------+ | | | | | | | | | | sednfile (syscall) | | | | | | | | | | +---------------------+ | | | | | | | | +-------------------------+ | | | | | | +-----------------------------+ | | | | +---------------------------------+ | | 4. do it! +--------v------+ 3. YES! | +---------------> syscall <-----------------------------------------------------+ +----------------
- http模組對於檔案只是簡單地直接開啟,擷取檔案描述符(file descriptor)
- http模組調用io.Copy函數,io.Copy函數開始檢查Reader Writer是否特殊的ReadFrom,WriteTo介面
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) { // bla.... // Similarly, if the writer has a ReadFrom method, use it to do the copy. if rt, ok := dst.(ReaderFrom); ok { return rt.ReadFrom(src) }
- 完成判斷,並直接調用net.TCPConn模組下的ReadFrom介面,裡面寫上了sendfile
func (c *TCPConn) ReadFrom(r io.Reader) (int64, error) { if n, err, handled := sendFile(c.fd, r); handled { if err != nil && err != io.EOF { err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} } return n, err } // skipped....
這樣io.Copy的使用者其實不知道自己默默地用了sendfile,同時又保持了介面一致性和很低的耦合度。
更深入的可以移步謝大的教程- interface
fasthttp對於header的處理
fasthttp為什麼會比net.http快,其中一個原因就是fasthttp並沒有完全地解析所有的http請求header資料。這種做法也稱作lazyloading。首先我們從header的struct開始看起吧。
type RequestHeader struct { //bla..... contentLength int contentLengthBytes []byte method []byte requestURI []byte host []byte contentType []byte userAgent []byte h []argsKV bufKV argsKV cookies []argsKV rawHeaders []byte}
可能大家都很奇怪,Request裡沒有string,明明method、host都可以用string啊,這是由於string是不可變類型,而從Reader中讀出的[]byte是可變類型,因此,從[]byte轉化為string時是有copy和alloc行為的。雖然資料量小時,沒有什麼影響,但是構建高並發系統時,這些小的資料就會變大變多,讓gc不堪重負。
request中還有一個特殊的argsKV
type argsKV struct { key []byte value []byte}
其實和上面的理由是一樣的,net.http中使用了map[string]string來儲存各種其他參數,這就需要alloc,為了達成zeroalloc,fasthttp採用了遍曆所有header參數來返回,其實也有一定的考慮,那就是大部分的header數量其實都不多,而每次對於短key對比只需要若干個CPU周期,因此算是合理的tradeoff(詳見bytes.Equal彙編實現)
對於[]byte alloc的最佳化,可以參考Dave Cheney的《Five things that make gofast》