Aula 39 – Tutorial Golang – Rate-Limiting
Aula 39 – Tutorial Golang – Rate-Limiting
Página principal do blog
Todas as aulas desse curso
Aula 38 Aula 40
Redes Sociais:
Meus links de afiliados:
Hostinger
Digital Ocean
One.com
Melhore seu NETWORKING
https://digitalinnovation.one/
Participe de comunidades de desenvolvedores:
Fiquem a vontade para me adicionar ao linkedin.
E também para me seguir no https://github.com/toticavalcanti.
Código final da aula:
https://github.com/toticavalcanti
Canais do Youtube
Toti
Lofi Music Zone Beats
Backing Track / Play-Along
Código Fluente
Putz!
Vocal Techniques and Exercises
PIX para doações
Aula 39 – Tutorial Golang – Rate-Limiting
Rate-limiting
O que é rate-limiting?
Rate-limiting é uma técnica que limita a taxa de solicitações que um sistema pode receber.
É uma ferramenta importante para proteger sistemas de sobrecarga e garantir a qualidade de serviço (QoS).
Por que usar rate-limiting?
Existem várias razões para usar rate-limiting, incluindo:
- Proteção contra sobrecarga: Rate-limiting pode ajudar a proteger sistemas de sobrecarga, evitando que sejam inundados por solicitações. Isso pode ajudar a evitar falhas do sistema e garantir que os usuários tenham uma experiência consistente.
- Garantia de QoS: Rate-limiting pode ajudar a garantir a qualidade de serviço (QoS) para todos os usuários. Isso pode ser feito, por exemplo, garantindo que todos os usuários recebam uma resposta dentro de um tempo limite especificado.
- Prevenção de abuso: Rate-limiting pode ajudar a prevenir o abuso de sistemas, como ataques de negação de serviço (DoS).
Implementando rate-limiting em Golang
Go fornece suporte para rate limiting usando funcionalidades do pacote time, que faz parte de sua biblioteca padrão.
Neste exemplo específico, o rate limiting é implementado utilizando as ferramentas de temporização fornecidas pelo pacote time, como tickers e canais.
Em Go, um “ticker” é uma ferramenta do pacote time utilizada para executar ações em intervalos de tempo regulares, é como um cronômetro que emite eventos periodicamente e você pode usar para controlar a frequência de determinadas operações.
OBS: Quem quiser relembrar Tickers é só clicar.
Exemplo de rate-limiting
O seguinte código mostra um exemplo de rate-limiting:
package main
import (
"fmt"
"time"
)
func main() {
// Primeiro, vamos olhar para o rate limiting básico. Suponhamos
// que queremos limitar o processamento de requisições recebidas.
// Vamos atender essas requisições a partir de um canal
// com o mesmo nome.
requests := make(chan int, 5)
for i := 1; i <= 5; i++ {
requests <- i
}
// Fechando o canal para indicar que não haverá mais
// envio de dados, evitando deadlock
close(requests)
// Este canal `limiter` receberá um valor a cada 200 milissegundos.
// Este será o regulador no nosso esquema de rate limiting.
limiter := time.Tick(200 * time.Millisecond)
// Ao bloquear na recepção do canal `limiter` antes de atender
// cada requisição, limitamos a taxa para 1 requisição a cada 200 milissegundos.
for req := range requests {
<-limiter
fmt.Println("request", req, time.Now())
}
// Podemos querer permitir picos curtos de requisições no
// nosso esquema de rate limiting, preservando o
// limite geral de taxa. Isso pode ser alcançado por
// meio do buffer do nosso canal de limitação. Este canal `burstyLimiter`
// permitirá picos de até 3 eventos.
burstyLimiter := make(chan time.Time, 3)
// Preenchemos o canal para representar a capacidade de pico permitida.
for i := 0; i < 3; i++ {
burstyLimiter <- time.Now()
}
// A cada 200 milissegundos, tentaremos adicionar um novo
// valor ao `burstyLimiter`, até o seu limite de 3.
go func() {
for t := range time.Tick(200 * time.Millisecond) {
burstyLimiter <- t
}
}()
// Agora simulamos mais 5 requisições recebidas. As primeiras
// 3 dessas requisições se beneficiarão da capacidade de pico
// do `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())
}
}
Fechando o Canal requests (close(requests)) e close(burstyRequests)
Fechar um canal é uma prática importante em Go por várias razões, entre elas, evitar Deadlocks, além de sinalizar que não haverá mais envio de dados para esse canal.
O que é Deadlock?
Um deadlock em programação ocorre quando dois ou mais processos (goroutines, no caso de Go) estão esperando uns pelos outros para liberar recursos ou concluir operações, mas, nenhum deles pode prosseguir.
Isso leva a um impasse, onde todos os processos envolvidos ficam bloqueados indefinidamente, uns esperando pelos outros.
Explicação do Código
Rate Limiting
O código cria um canal de requisições(requests) e o preenche com 5 números, representando as requisições.
Um limitador(limiter) é configurado para emitir um sinal a cada 200 milissegundos, controlando a taxa de processamento das requisições.
Rate Limiting com Capacidade de Pico
Um canal burstyLimiter com buffer para 3 eventos é criado, permitindo um processamento inicial mais rápido (pico) de até 3 requisições.
Uma goroutine adiciona novos valores ao burstyLimiter a cada 200 milissegundos, respeitando o limite de buffer.
As primeiras 3 requisições são processadas imediatamente, aproveitando a capacidade de pico, enquanto as seguintes aguardam conforme a taxa regular estabelecida.
Explicação Metafórica
- Loja (Capacidade do Serviço): A loja representa o serviço web. A capacidade da loja de atender clientes de forma eficiente é análoga à capacidade do serviço de processar requisições. Quando a loja está operando em sua capacidade máxima, isso significa que está atendendo o maior número possível de clientes ao mesmo tempo.
- Clientes (Requisições ao Serviço): Cada cliente entrando na loja representa uma requisição ao serviço. Em condições normais, a loja pode lidar com um fluxo constante de clientes entrando e sendo atendidos.
- Atendentes de Caixa e o Processo de Atendimento:
- Atendentes Regulares (
limiter
): Imagine que a loja tem atendentes de caixa que atendem um cliente a cada 200 milissegundos. Isso garante que os clientes sejam atendidos de forma constante e ordenada, evitando filas longas e garantindo que a loja não fique sobrecarregada. - Atendentes Extras para Picos (
burstyLimiter
): Agora, imagine que a loja pode chamar atendentes extras para lidar com picos de clientes. Inicialmente, três atendentes extras estão disponíveis para atender rapidamente três clientes. Após esses atendimentos, a loja volta a depender dos atendentes regulares que atendem a uma taxa constante.
- Atendentes Regulares (
- Gerenciamento de Fluxo de Clientes:
- Com o
limiter
, a loja nunca fica sobrecarregada, pois os atendentes regulares mantêm o fluxo de atendimento estável. - Com o
burstyLimiter
, a loja inicialmente lida com um pico de clientes, três atendimentos rápidos e depois, volta a um ritmo regular de atendimento.
- Com o
Esta metáfora ilustra como um serviço web gerencia o fluxo de requisições para evitar sobrecarga, mantendo um equilíbrio entre atender a picos de demanda e garantir um serviço contínuo e estável.
É uma maneira eficiente de assegurar que todos os “clientes” (requisições) sejam atendidos de forma justa e sem causar interrupções no “serviço da loja” (serviço web).
Em Termos de Requisições:
O código utiliza canais em Go para implementar rate limiting.
Sem Capacidade de Pico (Burst):
- Limitador (
limiter
): Este canal é usado para limitar o processamento de requisições a uma taxa constante. No código, olimiter
é um ticker que emite sinais a cada 200 milissegundos. - Processamento de Requisições: Cada requisição é processada quando um sinal é recebido do
limiter
. Isso efetivamente espaça as requisições para que ocorram a uma taxa de 1 a cada 200 milissegundos.
Com Capacidade de Pico:
- Limitador com Pico (
burstyLimiter
): Este canal possui um buffer inicial que permite o processamento rápido de um número limitado de requisições (até 3 neste caso). - Recarga do
burstyLimiter
: Uma goroutine adiciona novos sinais aoburstyLimiter
a cada 200 milissegundos, mas somente até o limite do seu buffer. - Processamento de Requisições com Pico: As primeiras requisições são processadas imediatamente, até esgotar o buffer do
burstyLimiter
. Após isso, as requisições subsequentes são processadas à medida que novos sinais são adicionados aoburstyLimiter
.
Resumo
O código usa tickers e canais com buffer para controlar a frequência de processamento de requisições.
As requisições são processadas baseadas na disponibilidade de sinais nos canais limiter
e burstyLimiter
, criando um sistema de rate limiting eficaz que pode lidar tanto com uma taxa constante de requisições quanto com picos de atividade.
Conclusão
Rate-limiting é uma ferramenta importante que pode ajudar a proteger sistemas de sobrecarga e garantir a qualidade de serviço.