使用go的io.Pipe優雅的優化中間緩存

BEFORE

今天發現,go的優點除了它的輕量線程(goroutine)提供了更方便靈活的併發編程模式以外,它的I/O機制也設計的很是給力。編程

以前,我在向其餘服務器發送json數據時,都須要先聲明一個bytes緩存,而後經過json庫把結構體中的內容mashal成字節流,再經過Post函數發送。
代碼以下:json

package main

import (
        "bytes"
        "encoding/json"
        "io/ioutil"
        "log"
        "net/http"
)

func init() {
        log.SetFlags(log.Lshortfile)
}

func main() {
        cli := http.Client{}

        msg := struct {
            Name, Addr string
            Price      float64
        }{
            Name:  "hello",
            Addr:  "beijing",
            Price: 123.56,
        }
        buf := bytes.NewBuffer(nil)
        json.NewEncoder(buf).Encode(msg)
        resp, err := cli.Post("http://localhost:9999/json", "application/json", buf)
        
        if err != nil {
            log.Fatalln(err)
        }

        body := resp.Body
        defer body.Close()

        if body_bytes, err := ioutil.ReadAll(body); err == nil {
            log.Println("response:", string(body_bytes))
        } else {
            log.Fatalln(err)
        }
}

這樣就老是須要咱們在內存中預先分配一個緩存,大部分狀況下其實還好,BUT 若是須要發送的數據很大,就會嚴重影響性能了。緩存

AFTER

go設計了一個Pipe函數幫咱們解決這個問題,因而個人代碼能夠改成:服務器

package main

import (
        "encoding/json"
        "io"
        "io/ioutil"
        "log"
        "net/http"
        "time"
)

func init() {
        log.SetFlags(log.Lshortfile)
}
func main() {
        cli := http.Client{}

        msg := struct {
            Name, Addr string
            Price      float64
        }{
            Name:  "hello",
            Addr:  "beijing",
            Price: 123.56,
        }
        r, w := io.Pipe()
        // 注意這邊的邏輯!!
        go func() {
            defer func() {
                time.Sleep(time.Second * 2)
                log.Println("encode完成")
                // 只有這裏關閉了,Post方法纔會返回
                w.Close()
            }()
            log.Println("管道準備輸出")
            // 只有Post開始讀取數據,這裏纔開始encode,並傳輸
            err := json.NewEncoder(w).Encode(msg)
            log.Println("管道輸出數據完畢")
            if err != nil {
                log.Fatalln("encode json failed:", err)
            }
        }()
        time.Sleep(time.Second * 1)
        log.Println("開始從管道讀取數據")
        resp, err := cli.Post("http://localhost:9999/json", "application/json", r)

        if err != nil {
            log.Fatalln(err)
        }
        log.Println("POST傳輸完成")

        body := resp.Body
        defer body.Close()

        if body_bytes, err := ioutil.ReadAll(body); err == nil {
            log.Println("response:", string(body_bytes))
        } else {
            log.Fatalln(err)
        }
}

輸出以下:併發

main.go:35: 管道準備輸出
main.go:44: 開始從管道讀取數據
main.go:38: 管道輸出數據完畢
main.go:31: encode完成
main.go:50: POST傳輸完成
main.go:56: response: {"Name":"hello","Addr":"beijing","Price":123.56}

能夠看到,經過Pipe,咱們能夠方便的連接輸入和輸出,只要咱們能正確理解整個過程的邏輯。有了它,咱們終於能夠甩去討厭的中間緩存了,同時也提升了系統的穩定性。app

服務器端代碼

爲了方便你們調試,如下是服務器端代碼:函數

package main

import (
           "encoding/json"
           "io/ioutil"
           "log"
           "net/http"
)

func init() {
           log.SetFlags(log.Lshortfile)
}

func main() {
           http.HandleFunc("/json", handleJson)

           http.ListenAndServe(":9999", nil)
}

func handleJson(resp http.ResponseWriter, req *http.Request) {
           if req.Method == "POST" {
               body := req.Body
               defer body.Close()
               body_bytes, err := ioutil.ReadAll(body)
               if err != nil {
                   log.Println(err)
                   resp.Write([]byte(err.Error()))
                   return
               }
               j := map[string]interface{}{}
               if err := json.Unmarshal(body_bytes, &j); err != nil {
                   log.Println(err)
                   resp.Write([]byte(err.Error()))
                   return
               }
               resp.Write(body_bytes)
           } else {
               resp.Write([]byte("請使用post方法!"))
           }
}
相關文章
相關標籤/搜索