Can Your Laptop Handle a Million Concurrent HTTP Requests?
Imagine sending 1,000,000 simultaneous HTTP requests to a REST API service, pushing your computer's limits to maximize concurrency. While tools exist for this task, let's dive into how to do it in Go using goroutines.
package main import ( "fmt" "net/http" "runtime" "time" ) func main() { runtime.GOMAXPROCS(runtime.NumCPU()) // Use all available CPU cores transport := &http.Transport{} // Create an HTTP transport for i := 0; i < 1000000; i++ { go func() { // Start a goroutine for each HTTP request req, _ := http.NewRequest("GET", "http://myapi.com", nil) req.Header.Set("User-Agent", "custom-agent") req.SetBasicAuth("xxx", "xxx") resp, err := transport.RoundTrip(req) if err != nil { panic("HTTP request failed.") } defer resp.Body.Close() if resp.StatusCode != 302 { panic("Unexpected response returned.") } location := resp.Header.Get("Location") if location == "" { panic("No location header returned.") } fmt.Println("Location Header Value:", location) }() } time.Sleep(60 * time.Second) // Sleep for 60 seconds }
But Wait, There's a Problem!
Running this script results in errors due to file descriptor limits. The system simply can't handle so many simultaneous connections.
An Improved Solution
To overcome these limitations, we need to use a more sophisticated approach.
Using a Dispatcher and Worker Pool
This solution involves creating a dispatcher goroutine that pushes requests onto a channel. A worker pool of goroutines pulls requests from the channel, processes them, and sends them to a response channel. A consumer goroutine then processes the responses.
// Dispatcher function func dispatcher(reqChan chan *http.Request, reqs int) { defer close(reqChan) for i := 0; i < reqs; i++ { req, err := http.NewRequest("GET", "http://localhost/", nil) if err != nil { log.Println(err) } reqChan <- req } } // Worker function func worker(t *http.Transport, reqChan chan *http.Request, respChan chan Response) { for req := range reqChan { resp, err := t.RoundTrip(req) r := Response{resp, err} respChan <- r } } // Consumer function func consumer(respChan chan Response, reqs int) (int64, int64) { var ( conns int64 size int64 ) for conns < int64(reqs) { select { case r, ok := <-respChan: if ok { if r.err != nil { log.Println(r.err) } else { size += r.ContentLength if err := r.Body.Close(); err != nil { log.Println(r.err) } } conns++ } } } return conns, size }
Results
Running this improved script generates impressive results, such as:
Connections: 1000000
Concurrent: 200
Total size: 15000000 bytes
Total time: 38m20.3012317s
Average time: 2.280131ms
Optimizing for Performance
However, tweaking the number of concurrent requests and total requests will help you push your system's limits and stress-test its capabilities. Remember, this is an extreme test and can rapidly consume system resources.
The above is the detailed content of Can My Laptop Handle One Million Concurrent HTTP Requests Using Go?. For more information, please follow other related articles on the PHP Chinese website!