Go con Ejemplos: Limitación de transferencia

La limitación de tasa de transferencia es un mecanísmo importante para controlar la utilización de un recurso y mantener la calidad del servicio. Go lo soporta elegantemente usando gorutinas, canales y tickers.

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

Primero veamos una limitación básica. Supongamos que queremos limirar el número de peticiones entrantes que podemos manejar. Serviremos estas peticiones desde un canal con el 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 limitador de transferencia.

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

Al bloquear durante la recepción del canal limiter antes de servir cada petición, nos autolimitamos a una petición cada 200 milisegundos

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

Podriamos permitir pequeños picos de peticiones en nuestro esquema de limitación y seguir conservando el limite general. Para lograrlo podemos bufferear nuestro canal limiter. Este canal burstyLimiter nos permitirá tener picos de hasta 3 eventos.

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

Llenamos el canal para representar los picos.

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

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

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

Ahora simularemos 5 peticiones más. La primera de estas 3 se beneficiará de la capacidad de soportar picos del canal burstyLimiter.

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

Al ejecutar nuestro programa podemos ver que el primer bloque de peticiones es atendido cada ~200 milisegundos apróximadamente como lo deseamos.

$ go run limitacion-de-transferencia.go
peticiones 1 2014-07-16 17:58:36.733961487 +0000 UTC
peticiones 2 2014-07-16 17:58:36.933979229 +0000 UTC
peticiones 3 2014-07-16 17:58:37.133983308 +0000 UTC
peticiones 4 2014-07-16 17:58:37.333995394 +0000 UTC
peticiones 5 2014-07-16 17:58:37.534003928 +0000 UTC

For the second batch of requests we serve the first 3 immediately because of the burstable rate limiting, then serve the remaining 2 with ~200ms delays each. Para el segundo bloque de peticiones, servimos los primeros 3 inmediatamente usando el soporte de picos, y luego servimos los 2 restantes con un retraso de ~200 milisegundos

peticiones 1 2014-07-16 17:58:37.534072367 +0000 UTC
peticiones 2 2014-07-16 17:58:37.534085589 +0000 UTC
peticiones 3 2014-07-16 17:58:37.534094082 +0000 UTC
peticiones 4 2014-07-16 17:58:37.734343287 +0000 UTC
peticiones 5 2014-07-16 17:58:37.935017031 +0000 UTC

Siguiente ejemplo: Contadores atómicos.