O que é throttling e como implementar em Go

Em uma das minhas sessões de mentoria, me perguntaram sobre throttling.

Throttling é uma técnica utilizada em diversas áreas da tecnologia para controlar o uso excessivo de recursos, limitando a taxa de execução de certa ação em um determinado período de tempo, podendo adicionar os eventos extras à uma fila de processamento ou descartá-los por completo.

Entendendo o throttling

Imagine um restaurante muito popular que recentemente viralizou nas redes sociais. Suponha que a cozinha do restaurante só consiga preparar até 10 pratos por hora, devido à capacidade dos cozinheiros e dos equipamentos.

Se o restaurante simplesmente tentar receber todo mundo que chega, ele terá problemas:

  • Os cozinheiros serão inundados com mais pedidos do que podem lidar
  • A qualidade dos pratos provavelmente cairá, pois os cozinheiros tentariam preparar os pratos com pressa
  • O tempo de espera dos clientes aumentará significativamente, e muitos iriam embora insatisfeitos
  • Faltarão mesas, e clientes ficarão em pé

Agora, considere que o restaurante implementou um limite ao número de clientes que atende simultaneamente.

  • Cada cliente que chega ao restaurante recebe um número de acordo com a ordem de chegada
  • Se já houver 10 clientes sendo atendidos, qualquer novo cliente precisa esperar sua vez na fila antes de fazer o pedido
  • À medida que a cozinha termina um prato e o entrega ao cliente, o próximo da fila pode fazer seu pedido

A fila garante que todos os clientes sejam atendidos de forma justa e em ordem de chegada. Isso mantém um fluxo contínuo e controlado de pedidos sendo processados.

A cozinha do restaurante é equivalente ao servidor ou à capacidade de processamento de uma API

Os clientes chegando ao restaurante representam usuários ou serviços que fazem requisições ao servidor

A fila de espera é o mecanismo de fila (queue) usado para gerenciar requisições que excedem a capacidade do sistema

O limite de 10 pratos por hora é o throttling, limitando o número de requisições que o servidor pode processar em um dado período de tempo.

Implementando throttling em Go

Vamos implementar um exemplo simples de throttling em Go.

Neste exemplo, vamos limitar a execução de uma função a 5 vezes por segundo.

Channels

Os channels são uma das principais ferramentas para comunicação entre goroutines em Go. Eles são usados para enviar e receber dados entre goroutines.

É possível verificar se um channel é capaz de receber um evento imediatamente ou se ele está bloqueado. Esse será o princípio básico para implementar nosso throttling.

Exemplo


package main

import (
    "fmt"
    "time"
)

var interval = 5 * time.Second

func monitor(ch <-chan string) {
    ticker := time.NewTicker(interval)

    for {
        select {
        case <-ticker.C:
            fmt.Println(<-ch)
        }
    }
}

func main() {
    var ch = make(chan string, 1)

    go monitor(ch)

    for i := 0; i < 100; i++ {
        select {
        case ch <- fmt.Sprintf("Message %d", i):
        default:
            fmt.Println("Channel is full!")
        }
        time.Sleep(1 * time.Second)
    }
}

Vamos entender o que está acontecendo no código acima:

1. Pacote principal e importação de bibliotecas


package main

import (
    "fmt"
    "time"
)
  • package main: Define o pacote principal do programa Go, necessário para a execução.
  • import "fmt" e import "time": Importa os pacotes para formatação de strings e manipulação de tempo, respectivamente.

2. Variáveis globais


var interval = 5 * time.Second
  • interval: Essa variável global define o intervalo de tempo de 5 segundos para o throttling. Ou seja, a função monitor somente aceitará mensagens a cada 5 segundos.

3. Função monitor


func monitor(ch <-chan string) {
    ticker := time.NewTicker(interval)

    for {
        select {
        case <-ticker.C:
            fmt.Println(<-ch)
        }
    }
}
  • monitor(ch <-chan string): Uma função que recebe um channel somente de leitura do tipo string.
  • ticker := time.NewTicker(interval): Cria um ticker que envia um evento a cada intervalo de tempo definido (5 segundos neste exemplo).
  • for { select { ... } }: Inicia um loop infinito que usa uma construção select para lidar com operações de recebimento no channel.
  • case <-ticker.C: fmt.Println(<-ch): Quando o ticker envia um evento (a cada 5 segundos), um valor é recebido do channel ch e impresso.

4. Função main


func main() {
    var ch = make(chan string, 1)

    go monitor(ch)

    for i := 0; i < 100; i++ {
        select {
        case ch <- fmt.Sprintf("Message %d", i):
        default:
            fmt.Println("Channel is full!")
        }
        time.Sleep(1 * time.Second)
    }
}
  • var ch = make(chan string, 1): Cria um channel de string com buffer de tamanho 1.
  • go monitor(ch): Inicia a função monitor em uma goroutine, permitindo que ela funcione de forma assíncrona.
  • O laço for i := 0; i < 100; i++ { ... } tenta enviar 100 mensagens para o channel ch:
    • select { case ch <- fmt.Sprintf("Message %d", i): default: fmt.Println("Channel is full!") }: Tenta enviar a mensagem formatada para o channel. Se o channel estiver cheio (o que pode acontecer, já que o buffer é de tamanho 1 e as mensagens são processadas apenas a cada 5 segundos), o default é executado e a mensagem “Channel is full!” é impressa.
    • time.Sleep(1 * time.Second): O loop espera 1 segundo antes de tentar enviar a próxima mensagem.

Resumo

  • Este código implementa throttling ao combinar channels e tickers para processar mensagens a cada 5 segundos.
  • A função monitor é usada para consumir as mensagens do channel de forma controlada (a cada 5 segundos).
  • A função main tenta enviar 100 mensagens para o channel com uma espera de 1 segundo entre cada tentativa. Se o channel estiver cheio, uma mensagem de aviso é impressa.

Testando o código

Para testar o código, basta salvá-lo em um arquivo Go (por exemplo, throttling.go) e executá-lo.

Você verá a saída das mensagens sendo impressas a cada 5 segundos, devido ao throttling implementado,


go run throttling.go
Channel is full!
Channel is full!
Channel is full!
Channel is full!
Message 0
Channel is full!
Channel is full!
Channel is full!
Channel is full!
Message 5
Channel is full!
Channel is full!
Channel is full!
Channel is full!
Message 10
...

Este é um exemplo simples de como implementar throttling em Go. Você pode ajustar o intervalo de tempo, o tamanho do buffer do channel e outras configurações para atender às necessidades específicas do seu aplicativo.

Ao invés de imprimir Channel is full, você poderia adicionar os eventos extras à uma fila de processamento ou descartá-los por completo, dependendo dos requisitos do seu aplicativo.

Espero que este exemplo tenha sido útil para entender o conceito de throttling e como implementá-lo em Go. Se tiver alguma dúvida ou sugestão, fique à vontade para compartilhar nos comentários.

Até a próxima!

comments powered by Disqus