Let's Go Further Ch 10-13
Mutex Locks and Custom Loggers
When writing our own logger, remembe to use mutex locks so that our server
can write to the logger concurrently. Without this lock, it’s possible that the content
of multiple log entries would be written at exactly the same time and get mixed up
in the output.
We can replace the http.Server default logger with our own custom logger as
long as we satisfy the Write() method from the io.Writer interface.
Rate Limiting
token bucket rate limiter
How it works:
- there is a bucket with
bavailable tokens in it - the bucket starts out full and as requests come in, 1 token is taken out
- tokens are added back in at a rate of
1/rseconds per token - tokens can only be added to the bucket up to a maximum of
btokens - if a HTTP request is received when there are no tokens left, a
429 too many requestsresponse is sent back
Global vs IP Based Rate Limiting
Global = enforce a strict limit on the total rate of requests
IP = each client will have a max number of requests per second
Why IP? so that one client making requests doesn’t affect all the others
Distributed Applications
The way that the book describes you do rate-limiting is only recommended for single-mahcine servers. AKA your application will only ever run on machine. For applications that will run on multiple servers, it’s better to use the built-in rate limiter from the load balancer or proxy (HAProxy, Nginx, reverse proxy).
Graceful shutdown
We can shut down our server with ctrl+c but this causes some problems.
Clients will not be able to receive responses to their in-flight requests. And
any ongoing work may be left incomplete.
Instead, we can use shutdown signals in order to trigger a graceful shutdown.
Before starting the server, create a go routine.
go func() {
// Create a quit channel which carries os.Signal values.
quit := make(chan os.Signal, 1)
// Use signal.Notify() to listen for incoming SIGINT and SIGTERM signals and
// relay them to the quit channel. Any other signals will not be caught by
// signal.Notify() and will retain their default behavior.
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// Read the signal from the quit channel. This code will block until a signal is
// received.
s := <-quit
// Log a message to say that the signal has been caught. Notice that we also
// call the String() method on the signal to get the signal name and include it
// in the log entry properties.
app.logger.PrintInfo("caught signal", map[string]string{
"signal": s.String(),
})
// Exit the application with a 0 (success) status code.
os.Exit(0)
}()