2016-11-15 193 views
0

我正在使用golang創建一個簡單的http服務器。我有兩個問題,一個更理論,另一個關於真正的計劃。golang併發http請求處理

  1. 併發請求處理

我創建了一個服務器,並使用s.ListenAndServe()來處理請求。我很瞭解同時服務的請求。我用一個簡單的處理程序,以檢查它:

func ServeHTTP(rw http.ResponseWriter, request *http.Request) { 
    fmt.Println("1") 
    time.Sleep(1 * time.Second)  //Phase 2 delete this line 
    fmt.Fprintln(rw, "Hello, world.") 
    fmt.Println("2") 
} 

我看到,如果我發送多個請求,我會看到所有的「1」的出現,只有經過第二所有的「2」出現。 但是,如果我刪除睡眠線,我看到該程序從未開始一個請求,在它完成前一個(輸出是1 2 1 2 1 2 ...)。 所以我不明白,如果他們是並行的或不真的。如果他們是我希望看到在打印一些亂七八糟......

  • 現實生活中的問題
  • 在實際處理程序,我請求發送到另一臺服務器並將答案返回給用戶(對請求和答案進行了一些更改,但在想法上它是一種代理)。所有這一切都需要時間,並且從看到的東西(通過向處理程序添加一些打印件)中,請求被逐個處理,它們之間沒有併發(我的打印件向我顯示請求開始,執行所有步驟,結束,只有我看到一個新的開始....)。 我能做些什麼來使它們真正併發?
    把處理函數作爲goroutine給出錯誤,請求的主體已經關閉。另外,如果它已經同時增加更多goroutines將使事情變得更糟。

    謝謝!

    +1

    這些請求默認是併發的。除非您使用goroutines,否則處理程序內部的任何內容都是連續的。 –

    +0

    您檢查請求是否被併發處理的方式是有缺陷的。所有的請求總是以並行的方式進行處理,無論您的打印報告如何讓您思考。 – Volker

    +1

    我建議你發送併發請求到你的服務器,看看會發生什麼! –

    回答

    3

    你的例子很難說出發生了什麼。

    下面的例子將清楚地說明請求是並行運行的。

    package main 
    
    import (
        "fmt" 
        "log" 
        "net/http" 
        "time" 
    ) 
    
    func main() { 
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 
         if len(r.FormValue("case-two")) > 0 { 
          fmt.Println("case two") 
         } else { 
          fmt.Println("case one start") 
          time.Sleep(time.Second * 5) 
          fmt.Println("case one end") 
         } 
        }) 
    
        if err := http.ListenAndServe(":8000", nil); err != nil { 
         log.Fatal(err) 
        } 
    } 
    

    使一個請求http://localhost:8000

    5秒

    內發出另一個請求到 http://localhost:8000?case-two=true

    控制檯輸出將是

    case one start 
    case two 
    case one end 
    
    +0

    在@ jmaloney的代碼提供的透明度之上。使用像Siege這樣的工具發送併發請求也是最好的,https://www.joedog.org/siege-home/ – reticentroot

    0

    它用作在並行的方式請求作爲可在這裏看到源https://golang.org/src/net/http/server.go#L2293

    這裏是一個人爲的例子

    package main 
    
    import (
        "fmt" 
        "log" 
        "net/http" 
        "sync" 
        "time" 
    ) 
    
    func main() { 
        go startServer() 
        sendRequest := func() { 
         resp, _ := http.Get("http://localhost:8000/") 
         defer resp.Body.Close() 
        } 
        start := time.Now() 
        var wg sync.WaitGroup 
        ch := make(chan int, 10) 
        for i := 0; i < 10; i++ { 
         wg.Add(1) 
         go func(n int) { 
          defer wg.Done() 
          sendRequest() 
          ch <- n 
         }(i) 
        } 
        go func() { 
         wg.Wait() 
         close(ch) 
        }() 
    
        fmt.Printf("completion sequence :") 
        for routineNumber := range ch { 
         fmt.Printf("%d ", routineNumber) 
        } 
        fmt.Println() 
        fmt.Println("time:", time.Since(start)) 
    } 
    
    func startServer() { 
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 
         time.Sleep(1 * time.Second) 
        }) 
        if err := http.ListenAndServe(":8000", nil); err != nil { 
         log.Fatal(err) 
        } 
    } 
    

    在過去的幾運行很容易想象的是,走程序,其發送請求的完成順序是完全隨機的,鑑於通道FIFO ,我們可以總結出服務器以併發方式處理請求,而不管HandleFunc是否睡覺。 (假設所有請求大約在同一時間開始)。

    除上述內容外,如果您在HandleFunc中睡了一秒鐘,完成所有10個例程所需的時間始終爲1.xxx秒,這進一步表明服務器同時處理了這些請求,因爲總時間完成所有請求應該已經有10多秒了。

    實施例:

    completion sequence :3 0 6 2 9 4 5 1 7 8 
    time: 1.002279359s 
    
    completion sequence :7 2 3 0 6 4 1 9 5 8 
    time: 1.001573873s 
    
    completion sequence :6 1 0 8 5 4 2 7 9 3 
    time: 1.002026465s 
    

    由不同步打印判斷併發幾乎總是不確定的。

    0

    雖然Go同時爲請求提供服務,但客戶端實際上可能會阻塞(在發送第二個請求之前等待第一個請求完成),然後人們會看到最初報告的行爲。

    我遇到了同樣的問題,我的客戶端代碼通過XMLHttpRequest向緩慢的處理程序發送「GET」請求(我使用了與上面提到的類似的處理程序代碼,10秒超時)。事實證明,這樣的請求相互阻塞。例如JavaScript客戶端代碼:

    for (var i = 0; i < 3; i++) { 
        var xhr = new XMLHttpRequest(); 
        xhr.open("GET", "/slowhandler"); 
        xhr.send(); 
    } 
    

    注意xhr.send()將立即返回,因爲這是一個異步調用,但這並不能保證瀏覽器會發送實際的「GET」請求的時候了。

    GET請求可能會緩存,如果嘗試獲取相同的URL,緩存可能(實際上會影響)對服務器的請求的製作方式。 POST請求沒有被緩存,所以如果在上面的例子中"GET"更改爲"POST",Go服務器代碼將顯示/slowhandler將同時觸發(您會看到「1 1 1 [... pause ...] 2 2 2 2「印刷)。