Go by Example: Rate Limiting

Limitación de frecuencia es un mecanismo importante para controlar la utilización de recursos y mantener la calidad del servicio. Go soporta elegantemente la limitación de frecuencia con goroutines, canales y tickers.

package main
import (
    "fmt"
    "time"
)
func main() {

Primero veremos la limitación de frecuencia básica. Supongamos que queremos limitar nuestro manejo de solicitudes entrantes. Atenderemos estas solicitudes desde un canal del mismo nombre.

    requests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        requests <- i
    }
    close(requests)

Este canal limiter recibirá un valor cada 200 milisegundos. Este es el regulador en nuestro esquema de limitación de frecuencia.

    limiter := time.Tick(200 * time.Millisecond)

Al bloquear en una recepción del canal limiter antes de atender cada solicitud, nos limitamos a 1 solicitud cada 200 milisegundos.

    for req := range requests {
        <-limiter
        fmt.Println("request", req, time.Now())
    }

Podemos querer permitir ráfagas cortas de solicitudes en nuestro esquema de limitación de frecuencia mientras preservamos la limitación de frecuencia general. Podemos lograr esto mediante la amortiguación de nuestro canal limitador. Este canal burstyLimiter permitirá ráfagas de hasta 3 eventos.

    burstyLimiter := make(chan time.Time, 3)

Llenar el canal para representar la ráfaga permitida.

    for i := 0; i < 3; i++ {
        burstyLimiter <- time.Now()
    }

Cada 200 milisegundos intentaremos agregar un nuevo valor a burstyLimiter, hasta su límite de 3.

    go func() {
        for t := range time.Tick(200 * time.Millisecond) {
            burstyLimiter <- t
        }
    }()

Ahora simulemos 5 solicitudes entrantes más. Las primeras 3 de estas se beneficiarán de la capacidad de ráfaga de burstyLimiter.

    burstyRequests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        burstyRequests <- i
    }
    close(burstyRequests)
    for req := range burstyRequests {
        <-burstyLimiter
        fmt.Println("request", req, time.Now())
    }
}

Al ejecutar nuestro programa vemos que el primer lote de solicitudes se maneja una vez cada ~200 milisegundos como se desea.

$ go run rate-limiting.go
request 1 2012-10-19 00:38:18.687438 +0000 UTC
request 2 2012-10-19 00:38:18.887471 +0000 UTC
request 3 2012-10-19 00:38:19.087238 +0000 UTC
request 4 2012-10-19 00:38:19.287338 +0000 UTC
request 5 2012-10-19 00:38:19.487331 +0000 UTC

Para el segundo lote de solicitudes atendemos las primeras 3 inmediatamente debido a la limitación de frecuencia ráfaga, luego atendemos las 2 restantes con retrasos de ~200ms cada una.

request 1 2012-10-19 00:38:20.487578 +0000 UTC
request 2 2012-10-19 00:38:20.487645 +0000 UTC
request 3 2012-10-19 00:38:20.487676 +0000 UTC
request 4 2012-10-19 00:38:20.687483 +0000 UTC
request 5 2012-10-19 00:38:20.887542 +0000 UTC

Siguiente ejemplo: Atomic Counters.