Reputation: 6561
Many times when developing an http server in Go I have this dilemma.
Assuming I want to respond to the client with http statuscode 200
as fast as possible (and then perform work at the back), this is why I usually do:
I have my main http handler receive the request, I write http 200
to the response, and I send a message over a channel
(if I have N
workers listening on that channel
, I am using a buffered channel
of N
):
func myHttpHandler(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(200)
log(req)
}
func log(req *http.Request) {
msg := createLog(req)
if msg != nil {
channel <- msg
}
}
I have my listeners (fired off on init) listening forever on that channel:
func init() {
for i := 0; i < workerCount; i++ {
go worker(i, maxEntrySize, maxBufferSize, maxBufferTime)
}
}
func worker(workerID int, maxEntrySize int, maxBufferSize int, maxBufferTime time.Duration) {
for {
entry := <-channel
...
...
...
Now, my main question is: should I fire off the log(req)
function inside a go routine? i.e.
func myHttpHandler(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(200)
go func() { log(req) } ()
}
As far as I can gather, there's no point in opening up a goroutine for every http request in this case.
Since the current operation of the log(req)
function is mostly sending some data over a channel
- that operation is super quick. The only time when it's not quick - is if the channel
blocks. Now, if the channel
blocks, it has to mean that the worker is blocked. And since the worker listens for messages on the channel forever - if the worker is blocked, it means my machine is truly not capable to produce faster output (the worker does some I/O
as you can imagine, but that's also very quick, because the I/O only happens once per minute).
Furthermore, since I have N
workers, the channel
I am using to send the messages on from the handler is buffered with N
, so it only blocks if all N
workers are blocked.
Is this correct? What are the pros and cons of using a goroutine
for the log(req)
call? This handler receives upto 10K requests per second, I am guessing it's not a good idea to open a goroutine
for each request.
Upvotes: 2
Views: 3920
Reputation: 24818
There's no point in opening up a goroutine for every http request in this case.
That already happens when you use net/http
's Server. Your handler is invoked in a goroutine of its own.
I am guessing it's not a good idea to open a goroutine for each request.
It's not a bad idea either. Go's runtime can easily handle hundreds of thousands of goroutines.
However, if log
blocks, you risk timing out on your client, who is waiting to receive a full HTTP response and only doing rw.WriteHeader(200)
doesn't constitute one yet.
To remedy this, you can do something like:
if cr, ok := rw.(io.Closer) {
cr.Close()
}
And it's also probably a good idea to set the Content-Length
header of your response to 0
.
Upvotes: 5