Skip to content

balanceador dedicado

mobius1qwe edited this page Nov 4, 2026 · 1 revision

Home > Colocar no ar > Balanceador dedicado (TRALBalancer) 🚧

Balanceador dedicado (TRALBalancer)

Important

Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.

Vale para a 2.0 · Delphi XE em diante e Lazarus/FPC 3.2 em diante · Nível: intermediário

Em uma frase: um componente que só balanceia: escuta numa porta com as suas próprias threads (sem motor HTTP), lê apenas a linha de pedido e os cabeçalhos, escolhe um servidor de trás pelo TRALUpstream e repassa o corpo em fluxo nos dois sentidos - ou, no modo TCP, repassa bytes de qualquer protocolo.

Você vai precisar: os pacotes RALNetPackage e RALModuleProxyPackage (componente TRALBalancer, paleta RAL - Server; TRALUpstream em RAL - Modules). Para HTTPS, a OpenSSL 1.1.1 ou 3.x ao lado do programa (dependências).

Qual usar: TRALProxyModule ou TRALBalancer

TRALProxyModule TRALBalancer
onde roda dentro de um servidor RAL, ao lado das suas rotas sozinho, numa porta própria
motor o do servidor (Indy, mORMot2, fpHTTP...) nenhum: rede própria do RAL
plugins do servidor todos os de validação (ppValidate: listas de IP, limite de pedidos, inundação, encaminhado...)
corpo do pedido o motor recebe inteiro antes em fluxo, com memória constante
WebSocket não sim, como túnel
TCP puro (banco, MQTT, TLS sem abrir) não sim (Mode := bmTCP)
vazão a do motor perto da de um NGINX

O básico

Grupo := TRALUpstream.Create(Self);
Grupo.Servers.AddServer('http://10.0.0.11:8080');
Grupo.Servers.AddServer('http://10.0.0.12:8080');

Balanceador := TRALBalancer.Create(Self);
Balanceador.Port := 80;
Balanceador.Upstream := Grupo;                       // o que nenhuma rota pega
Balanceador.Routes.AddRoute('', '/api/', Api).StripPrefix := True;   // /api/x -> /x
Balanceador.Routes.AddRoute('admin.exemplo.com', '', Admin);         // por Host
Balanceador.Active := True;

As rotas são lidas em ordem e a primeira que casa vence: HostName (vazio é qualquer; *.exemplo.com cobre um nível) e PathPrefix (segmentos inteiros: /api não pega /apis). Nenhuma rota e nenhum Upstream: 404. O evento OnRoute pode trocar a escolha. Tudo o que o TRALUpstream faz vale aqui: políticas, pesos, reserva, saúde passiva e ativa, sessão grudada, estatística.

HTTPS

Balanceador.SSL.Enabled := True;
Balanceador.SSL.CertFile := 'certs\padrao.pem';          // para quem não pede um nome conhecido
Balanceador.SSL.KeyFile := 'certs\padrao.key';
Balanceador.SSL.Certificates.AddCertificate('www.exemplo.com', 'certs\www.pem', 'certs\www.key');
Balanceador.SSL.Certificates.AddCertificate('*.api.exemplo.com', 'certs\api.pem', 'certs\api.key');
  • Um certificado por nome na mesma porta, escolhido pelo SNI. StrictServerName recusa quem pede um nome que nenhum certificado cobre.
  • O certificado automático funciona: ligue o TRALSelfSignedPlugin ao balanceador.
  • Servidores de trás com https:// são chamados com TLS e retomam a sessão (a segunda conexão não paga o handshake inteiro). VerifyBackend confere o certificado deles contra as autoridades do sistema (desligado por padrão: servidores internos costumam ter certificado próprio).
  • Os servidores de trás recebem X-Forwarded-Proto: https.

O que passa e o que não passa

  • O corpo vai em fluxo nos dois sentidos, inclusive chunked (passado como veio, com trailer): um upload de 1 GB passa com a mesma memória de um GET. Quando um lado não esvazia, o outro espera (contrapressão).
  • Cabeçalhos: saem os de salto a salto (Connection e os que ele nomeia, Keep-Alive, Proxy-*...); AddForwarded (padrão) põe X-Forwarded-For, -Proto e -Host (o X-Forwarded-For que chegou é trocado, ou acrescentado com KeepForwardedChain); Via põe o cabeçalho Via. PreserveHost passa o Host do cliente em vez do do servidor de trás.
  • O Location de um redirecionamento do servidor de trás volta relativo (com o prefixo da rota, quando foi tirado).
  • Upgrade: websocket: depois do 101 os dois lados viram um túnel.
  • Conexões com os servidores de trás são mantidas e reaproveitadas entre pedidos.
  • Um pedido que falhou antes de qualquer resposta vai ao próximo servidor (MaxAttempts) só se é idempotente e sem corpo. Nenhum disponível: 503; nenhum respondeu: 502; o servidor ficou mudo além de ServerTimeout (ou do RequestTimeout do upstream): 504.

Pedidos recusados

O balanceador lê os pedidos com o rigor da RFC 9112, para que ele e o servidor de trás nunca discordem de onde um pedido termina (contrabando de pedido):

pedido resposta
Content-Length junto com Transfer-Encoding 400
dois Content-Length diferentes 400
Transfer-Encoding que não termina em chunked, ou num HTTP/1.0 400
linha dobrada (começa com espaço), espaço antes dos dois pontos, \r ou \n solto no meio 400
linhas terminadas só em \n não é reconhecido: 408 depois de HeaderTimeout
HTTP/1.1 sem Host, ou com dois 400
cabeçalho maior que MaxHeaderSize (16 KB) 431
HTTP/2.0 em texto 505
CONNECT 405

Modo TCP

Banco := TRALUpstream.Create(Self);
Banco.Servers.AddServer('tcp://10.0.0.20:5432');

Tcp := TRALBalancer.Create(Self);
Tcp.Mode := bmTCP;
Tcp.Port := 5432;
Tcp.Upstream := Banco;
Tcp.Active := True;

Bytes nos dois sentidos, com meio fechamento (quem termina de mandar avisa o outro lado). Com TLS que passa sem abrir, uma rota com HostName escolhe o grupo pelo nome que o cliente pede no SNI - o balanceador lê o ClientHello e não tem certificado nenhum:

Tcp.Routes.AddRoute('a.exemplo.com', '', GrupoA);
Tcp.Routes.AddRoute('*.b.exemplo.com', '', GrupoB);

Plugins

Os plugins de validação ligados ao balanceador (Plugin.Server := Balanceador) julgam cada pedido antes do repasse: listas de IP, força bruta, inundação, limite de pedidos, TRALForwardedPlugin (para um balanceador atrás de outro). O que eles recusam é respondido ali. O TRALConcurrencyPlugin conta o pedido até a resposta terminar.

Log, estatística, teste

  • OnAccess é chamado no fim de cada pedido (no modo TCP, de cada conexão), numa thread do balanceador: grave ou enfileire ali, não mexa na tela. RALBalancerAccessLine(Entrada, 'combined') dá a linha no formato do NGINX/Apache; 'json', um objeto por linha.
  • StatsPath := '/status': o próprio balanceador responde a estatística (conexões, pedidos, e por servidor de cada upstream) em JSON, ou no formato do Prometheus com ?format=prometheus. Proteja com uma lista de IPs.
  • Na IDE, o verbo Testar porta diz se a porta abre e os certificados carregam; o Testar servidores do TRALUpstream pede o caminho de saúde a cada servidor agora (TestServers em código).

Trocar a configuração com ele rodando

Balanceador.Routes.AddRoute('', '/v2/', NovoGrupo);
Balanceador.Upstream := OutroGrupo;
Balanceador.Reload;   // novos pedidos seguem isto; os em curso terminam como estavam

Reload também recarrega os certificados de SSL. As conexões abertas não caem: o próximo pedido delas já segue as rotas novas. Um upstream que saiu de uso não é liberado pelo balanceador (pedidos em curso podem usá-lo): libere depois de Active := False. O RALProxy faz tudo isso a partir de um arquivo de configuração.

Ajustes

propriedade padrão para quê
ThreadCount 0 (um por processador) threads de poll
BufferSize 16384 bytes por buffer (um por sentido e conexão)
MaxHeaderSize 16384 maior cabeçalho de pedido e de resposta
HeaderTimeout 30000 ms tempo para o cliente mandar o cabeçalho inteiro
IdleTimeout 75000 ms conexão mantida quieta entre pedidos
ServerTimeout 0 (o RequestTimeout do upstream) servidor de trás mudo
TunnelTimeout 600000 ms TCP e WebSocket quietos nos dois sentidos
MaxConnections 0 (sem limite) conexões de clientes ao mesmo tempo

Erros comuns

Sintoma Causa Solução
404 em tudo nenhuma rota casou e Upstream vazio preencha Upstream ou confira HostName/PathPrefix
400 ou 408 para um cliente antigo ele manda o pedido fora da RFC (linha dobrada, linhas só com \n) corrija o cliente: o balanceador não adivinha onde o pedido termina
502 logo de cara o servidor de trás recusa a conexão confira o endereço e a porta da URL
504 o servidor de trás demora mais que ServerTimeout aumente ServerTimeout ou o RequestTimeout do upstream
o log trava a tela OnAccess mexe em componentes visuais enfileire a linha e leve à tela com um TTimer (veja o exemplo Balancer)
HTTPS não sobe a OpenSSL não está ao lado do programa dependências; o verbo Testar porta diz o motivo

Veja também

Clone this wiki locally