Daaang Amy
open main menu
Part of series: Learning the Backend

Let's Go Further Ch 10-13

/ 3 min read

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 b available 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/r seconds per token
  • tokens can only be added to the bucket up to a maximum of b tokens
  • if a HTTP request is received when there are no tokens left, a 429 too many requests response 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)
}()

Other Let’s Go Posts