Escalando o MySQL com balanceamento de carga TCP e Galera Cluster

NGINX | 26 de julho de 2021

[Editor – Publicado originalmente em 2016, este artigo foi atualizado para usar recursos do NGINX que foram revisados desde então. Para obter mais detalhes, consulte Registro avançado com o módulo JavaScript do NGINX e Painel do NGINX Plus.]

Introduzimos o balanceamento de carga TCP no NGINX Plus R5, e continuamos adicionando recursos nas versões subsequentes, além de oferecer suporte ao balanceamento de carga UDP. Neste artigo, exploramos os principais requisitos do balanceamento de carga TCP e como o NGINX Plus os atende.

Para explorar os recursos do NGINX Plus, usaremos um ambiente de teste simples que representa os principais componentes de uma aplicação com um backend de banco de dados escalonado. Para obter instruções completas sobre como criar o ambiente de teste, consulte o Apêndice.

O ambiente de teste para balanceamento de carga de servidores MySQL posiciona o NGINX Plus entre os clientes MySQL e o cluster Galera
O ambiente de teste para balanceamento de carga dos nós MySQL

Nesse ambiente, o NGINX Plus atua como proxy reverso para o servidor de banco de dados, escutando na porta padrão 3306 do MySQL. Isso fornece uma interface simples para o cliente, enquanto os nós MySQL de backend podem ser ampliados (e até mesmo colocados offline) sem afetar o cliente de forma alguma. Usamos a ferramenta de linha de comando do MySQL como cliente, que representa a aplicação de frontend no ambiente de teste.

Muitos dos recursos descritos neste artigo se aplicam tanto ao NGINX Open Source quanto ao NGINX Plus. Para simplificar, usaremos NGINX Plus ao longo de todo o artigo e indicaremos explicitamente os recursos que não estão disponíveis no NGINX Open Source.

Exploraremos os seguintes casos de uso:

Balanceamento de carga TCP

Antes de configurar o balanceamento de carga para qualquer aplicação, é importante entender como ela se conecta ao banco de dados. Na maioria dos nossos testes, usamos a ferramenta de linha de comando do MySQL, mysql(1), para nos conectar ao cluster Galera, executar uma consulta e, em seguida, encerrar a conexão. No entanto, muitos frameworks de aplicações usam um pool de conexões para minimizar a latência e utilizar com eficiência os recursos do servidor de banco de dados.

O balanceamento de carga TCP é configurado no contexto de configuração stream, portanto, criamos nossa configuração básica de balanceamento de carga do MySQL adicionando um bloco stream ao arquivo principal nginx.conf.

Carregando exemplo de código…

Isso separa nossa configuração de balanceamento de carga TCP do arquivo de configuração principal. Em seguida, criamos o arquivo stream.conf no mesmo diretório que o arquivo nginx.conf. Observe que, por padrão, o diretório conf.d é reservado para o contexto de configuração http, portanto, adicionar arquivos de configuração de stream a esse diretório não funciona.

Carregando exemplo de código…

Primeiro, definimos um grupo upstream chamado galera_cluster, que contém os três nós MySQL do nosso cluster Galera. Em nosso ambiente de teste, cada um deles pode ser acessado no localhost por um número de porta exclusivo. A diretiva zone define uma quantidade de memória compartilhada entre todos os processos de trabalho do NGINX Plus para manter o estado do balanceamento de carga. O bloco server{} configura como o NGINX Plus lida com os clientes. O NGINX Plus escuta na porta padrão do MySQL, 3306, e encaminha todo o tráfego para o cluster Galera definido no bloco upstream.

Para testar se essa configuração básica está funcionando, podemos usar o cliente MySQL para retornar o nome do host do nó no cluster Galera ao qual nos conectamos.

$ echo "SHOW VARIABLES WHERE Variable_name = 'hostname'" | mysql --protocol=tcp --user=nginx --password=plus -N 2> /dev/nullhostname	node1

Para verificar se o balanceamento de carga está funcionando, podemos repetir o mesmo comando.

$ !!;!!;!!hostname	node2
hostname	node3
hostname	node1

Isso mostra que o algoritmo padrão de balanceamento de carga round-robin está funcionando corretamente. No entanto, se nosso aplicativo usar um pool de conexões para acessar o banco de dados (conforme sugerido acima), abrir conexões com o cluster de forma round-robin provavelmente resultará em um número desigual de conexões em cada nó. Além disso, não podemos associar uma conexão a uma determinada carga de trabalho, pois as conexões podem estar ociosas (aguardando uma consulta do aplicativo) ou ocupadas processando uma consulta. Um algoritmo de balanceamento de carga mais adequado para conexões TCP de longa duração é o Menor Número de Conexões, configurado com a diretiva least_conn:

Carregando exemplo de código…

Agora, quando um cliente abre uma nova conexão com o banco de dados, o NGINX Plus escolhe o nó do cluster com o menor número de conexões ativas.

Alta disponibilidade e verificações de integridade

A grande vantagem de distribuir a carga de trabalho do banco de dados entre os nós de um cluster é que isso também proporciona alta disponibilidade. Com a configuração discutida acima, o NGINX Plus marca um servidor como “inativo” e deixa de enviar pacotes TCP a ele caso uma nova conexão TCP não possa ser estabelecida.

Além de lidar dessa forma com servidores inativos, o NGINX Plus também pode ser configurado para realizar verificações de integridade automáticas e proativas, de modo que os servidores indisponíveis sejam detectados antes que as solicitações dos clientes sejam enviadas a eles (esse recurso é exclusivo do NGINX Plus). Além disso, a disponibilidade dos servidores pode ser testada com uma verificação de integridade no nível da aplicação, o que significa que podemos enviar uma solicitação a cada servidor e verificar se recebemos uma resposta que indique que ele está funcionando corretamente. Isso amplia nossa configuração da seguinte forma.

Carregando exemplo de código…

Neste exemplo, o bloco match define os dados de solicitação e resposta necessários para iniciar um handshake do protocolo MySQL versão 10. A diretiva health_check no bloco server{} aplica esse padrão e garante que o NGINX Plus encaminhe conexões MySQL somente para servidores que realmente sejam capazes de aceitar novas conexões. Nesse caso, realizamos a verificação de integridade a cada 20 segundos, excluímos um servidor do pool de balanceamento de carga TCP após uma única falha e retomamos o balanceamento de carga para ele após 2 verificações de integridade consecutivas bem-sucedidas.

Registro em log e diagnóstico

O NGINX Plus oferece registro flexível para que todo o processamento de TCP/UDP possa ser registrado para depuração ou análise offline. Para protocolos TCP, como o MySQL, o NGINX Plus grava uma entrada de log quando a conexão é encerrada. A diretiva log_format define quais valores aparecem nos logs. Podemos escolher entre quaisquer das variáveis disponíveis para os módulos Stream. Definimos o formato de log no contexto stream, no início do nosso arquivo stream.conf.

Carregando exemplo de código…

O registro de logs é habilitado adicionando a diretiva access_log ao bloco server{}, especificando o caminho do arquivo de log e o nome do formato de log definido no trecho de código anterior.

Carregando exemplo de código…

Isso gera entradas de log como a do exemplo abaixo.

$ tail -3 /var/log/nginx/galera_access.log192.168.91.1 [23/Jul/2021:17:42:18 +0100] TCP 200 369 1611 127.0.0.1:33063 0.000 0.003 12.614 12.614
192.168.91.1 [23/Jul/2021:17:42:18 +0100] TCP 200 369 8337 127.0.0.1:33061 0.001 0.001 11.181 11.181
192.168.91.1 [23/Jul/2021:17:42:19 +0100] TCP 200 369 1611 127.0.0.1:33062 0.001 0.001 10.460 10.460

Registro avançado com o módulo JavaScript do NGINX

O NGINX JavaScript é a linguagem de configuração programática “nativa do NGINX”. Trata-se de uma implementação exclusiva de JavaScript para NGINX e NGINX Plus, desenvolvida especificamente para casos de uso no lado do servidor e processamento por solicitação.

[Editor – O caso de uso a seguir é apenas um dos muitos disponíveis para o módulo JavaScript do NGINX. Para ver a lista completa de casos de uso, consulte Casos de uso do módulo JavaScript do NGINX.

Esta publicação foi atualizada para usar o objeto de sessão refatorado (s) do módulo Stream, introduzido no NGINX JavaScript 0.2.4, e a diretiva js_import, introduzida no NGINX JavaScript 0.4.0.]

No módulo Stream para balanceamento de carga TCP/UDP, o NGINX JavaScript fornece acesso ao conteúdo dos pacotes de solicitação e resposta. Isso significa que podemos examinar a solicitação do cliente correspondente à consulta SQL e extrair elementos úteis, como o método SQL, por exemplo, SELECT ou UPDATE. O NGINX JavaScript pode então disponibilizar esses valores como variáveis comuns do NGINX. Neste exemplo, colocamos nosso código JavaScript em /etc/nginx/sql_method.js.

Carregando exemplo de código…

A função getSqlMethod() recebe um objeto JavaScript (s) que representa o pacote atual. As propriedades desse objeto, como fromUpstream e buffer, fornecem as informações necessárias sobre o pacote e seu contexto.

Primeiro, verificamos se o pacote TCP vem do cliente, pois não precisamos examinar os pacotes provenientes do servidor MySQL upstream. Aqui, estamos interessados no terceiro pacote do cliente, pois os dois primeiros contêm informações de handshake e autenticação. O terceiro pacote do cliente contém a consulta SQL. Em seguida, o início dessa string é comparado com um dos métodos SQL definidos no array methods. Quando encontramos uma correspondência, armazenamos o resultado na variável global $method e gravamos uma entrada no log de erros. Os registros do NGINX JavaScript são gravados no log de erros com o nível de severidade info e, portanto, não aparecem por padrão.

A função setSqlMethod() é chamada quando uma variável do NGINX com o mesmo nome é avaliada. Quando isso acontece, a variável recebe o valor da variável global do NGINX JavaScript $method, obtido por meio de chamadas à função getSqlMethod().

Observe que este código NGINX JavaScript foi desenvolvido para o cliente de linha de comando do MySQL, no qual uma única consulta é executada. Ele não captura com precisão consultas complexas nem várias consultas em uma conexão de longa duração, embora possa ser adaptado para esses casos de uso. Para obter instruções sobre como instalar e habilitar o módulo NGINX JavaScript, consulte o Guia de Administração do NGINX Plus.

Para incluir o método SQL em nossos logs, incluímos a variável $sql_method na diretiva log_format.

Carregando exemplo de código…

Também precisamos ampliar nossa configuração para informar ao NGINX Plus como e quando executar o código JavaScript do NGINX.

Carregando exemplo de código…

Primeiro, especificamos o local do código JavaScript do NGINX com a diretiva js_import e usamos a diretiva js_set para instruir o NGINX Plus a chamar a função setSqlMethod() quando precisar avaliar a variável $sql_method. Em seguida, dentro do bloco server{} usamos a diretiva js_filter para especificar a função que será chamada sempre que um pacote for processado. Opcionalmente, podemos adicionar a diretiva error_log com a opção info para habilitar o registro em log do JavaScript do NGINX.

Com essa configuração adicional, nosso log de acesso agora fica assim.

$ tail -3 /var/log/nginx/galera_access.log192.168.91.1 [23/Jul/2021:17:42:18 +0100] TCP 200 369 1611 127.0.0.1:33063 0.000 0.003 12.614 12.614 UPDATE
192.168.91.1 [23/Jul/2021:17:42:18 +0100] TCP 200 369 8337 127.0.0.1:33061 0.001 0.001 11.181 11.181 SELECT
192.168.91.1 [23/Jul/2021:17:42:19 +0100] TCP 200 369 1611 127.0.0.1:33062 0.001 0.001 10.460 10.460 UPDATE

Painel do NGINX Plus

[Editor – Esta seção foi atualizada para fazer referência à API do NGINX Plus, que substitui e torna obsoleto o módulo Status estendido separado, abordado originalmente aqui.]

Além de registrar detalhadamente a atividade do MySQL, podemos observar métricas em tempo real e a integridade dos nossos servidores MySQL upstream no painel de monitoramento de atividade em tempo real do NGINX Plus (o NGINX Open Source fornece um conjunto menor de métricas, disponível somente por meio da API de status básico apenas).

O painel do NGINX Plus foi introduzido no NGINX Plus R7 e fornece uma interface web para a API do NGINX Plus. Para habilitá-lo, adicionamos um novo bloco server{} no contexto http, em um arquivo separado, /etc/nginx/conf.d/dashboard.conf:

Carregando exemplo de código…

Também precisamos atualizar o bloco server{} em stream.conf com a diretiva status_zone para permitir a coleta de dados de monitoramento do nosso serviço MySQL.

Carregando exemplo de código…

Com essa configuração, o painel do NGINX Plus fica disponível na porta 8080. Na captura de tela a seguir, podemos ver nossos três servidores MySQL, cada um exibindo os detalhes de várias conexões em andamento e o status de integridade atual. Podemos ver que o nó que escuta na porta 33062 teve anteriormente uma breve indisponibilidade de 18,97 segundos (informada na coluna DT).

O painel de monitoramento de atividades em tempo real do NGINX Plus permite acompanhar a integridade dos servidores durante o balanceamento de carga dos nós MySQL
O painel de monitoramento de atividades em tempo real do NGINX Plus permite acompanhar a integridade dos seus servidores MySQL

Considerações sobre gravações simultâneas

O Galera Cluster apresenta cada nó de servidor MySQL como um banco de dados mestre que realiza operações de leitura e gravação. Para muitas aplicações, a proporção entre leituras e gravações é tão alta que o risco de a mesma linha de uma tabela ser atualizada simultaneamente por vários clientes é totalmente aceitável quando comparado à flexibilidade proporcionada por um cluster de banco de dados multimestre. Em situações em que há um risco maior de ocorrerem gravações simultâneas, temos duas opções.

  1. Crie dois grupos upstream separados, um para leituras e outro para gravações, cada um escutando em uma porta diferente. Dedique um ou mais nós do cluster às gravações e inclua todos os nós no grupo de leituras. O código do cliente deve ser atualizado para selecionar a porta apropriada para as operações de leitura e gravação. Essa abordagem é discutida em Balanceamento de carga avançado do MySQL com o NGINX Plus em nosso blog e é mais adequada para um ambiente altamente escalável com muitos nós de servidor MySQL.
  2. Mantenha um único grupo upstream e modifique o código do cliente para detectar erros de gravação. Quando um erro de gravação é detectado, o código aumenta exponencialmente o intervalo antes de tentar novamente, após o término da concorrência. Essa abordagem é discutida em Alta disponibilidade do MySQL com NGINX Plus e Galera Cluster em nosso blog e é mais adequada para um cluster pequeno, no qual dedicar nós do cluster a gravações comprometeria a alta disponibilidade.

Resumo

Neste artigo, exploramos vários dos aspectos essenciais do balanceamento de carga de uma aplicação TCP (ou UDP), como o MySQL. O NGINX Plus oferece um balanceador de carga TCP/UDP completo para ajudar você a disponibilizar aplicações com alto desempenho, confiabilidade, segurança e escalabilidade, independentemente do tipo de tráfego.

Para experimentar o NGINX Plus, inicie sua avaliação gratuita de 30 dias hoje mesmo ou entre em contato conosco para conversar sobre seus casos de uso.

Apêndice: criação do ambiente de teste

O ambiente de teste é instalado em uma máquina virtual para que fique isolado e possa ser reproduzido. No entanto, nada impede que ele seja instalado em um servidor físico, ou seja, diretamente no hardware.

Instalando o NGINX Plus

Consulte o Guia do Administrador do NGINX Plus.

Instalação do Galera Cluster para MySQL

Neste exemplo, instalamos o Galera Cluster em um único host, usando contêineres Docker para cada nó. As instruções a seguir foram adaptadas de Introdução ao Galera com Docker e pressupõem que tanto o Docker Engine quanto a ferramenta de linha de comando do MySQL já estejam instalados.

  1. Crie um arquivo básico de configuração do MySQL (my.cnf) para que a imagem do Docker o copie para cada contêiner do Galera.

    Carregando exemplo de código…

  2. Baixe a imagem Docker básica do Galera.
$ sudo docker pull erkules/galera:basic
  1. Crie o primeiro nó do Galera (node1), expondo a porta padrão do MySQL como 33061.
$ sudo docker run -p 33061:3306 --detach=true --name node1 -h node1 erkules/galera:basic --wsrep-cluster-name=local-test --wsrep-cluster-address=gcomm://
  1. Crie o segundo nó do Galera (node2). A porta do MySQL é exposta como 33062 e vinculada ao node1 para comunicação entre clusters.
$ sudo docker run -p 33062:3306 --detach=true --name node2 -h node2 --link node1:node1 erkules/galera:basic --wsrep-cluster-name=local-test --wsrep-cluster-address=gcomm://node1
  1. Crie o terceiro e último nó do Galera (node3) da mesma forma que o node2. A porta do MySQL é exposta como 33063.
$ sudo docker run -p 33063:3306 --detach=true --name node3 -h node3 --link node1:node1 erkules/galera:basic --wsrep-cluster-name=local-test --wsrep-cluster-address=gcomm://node1
  1. Crie uma conta de usuário chamada nginx que possa ser usada para acessar remotamente o cluster a partir do host. Para isso, execute o comando mysql(1) dentro do próprio contêiner Docker.
$ sudo docker exec -ti node1 mysql -e"GRANT ALL PRIVILEGES ON *.* TO 'nginx'@'172.17.0.1' IDENTIFIED BY 'plus'"
  1. Verifique se é possível se conectar ao cluster Galera a partir do host usando o protocolo TCP.
$ mysql --protocol=tcp -P 33061 --user=nginx --password=plus -e "SHOW DATABASES"mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
  1. Por fim, execute o mesmo comando em outro nó do cluster para mostrar que a conta de usuário nginx foi replicada e que o cluster está funcionando corretamente.
$ mysql --protocol=tcp -P 33062 --user=nginx --password=plus -e "SHOW DATABASES"mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+


Compartilhar

Sobre o autor

Liam Crilly
Liam CrillyDiretor Sênior de Gestão de Produtos

Mais publicações de Liam Crilly

Publicações relacionadas no blog

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade
NGINX | 10/10/2024

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade

Descubra como o F5 NGINX One aprimora o gerenciamento e a observabilidade de ambientes híbridos e multicloud ao oferecer um console web centralizado baseado em SaaS, com insights em tempo real, conformidade automatizada e gerenciamento simplificado de configurações.

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa
NGINX | 17/06/2024

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa

Descubra como transformar uma definição de esquema OpenAPI em uma configuração NGINX totalmente funcional, operando como um API Gateway com a segurança de um Web Application Firewall e um Portal do Desenvolvedor, usando uma abordagem declarativa de API.

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor
NGINX | 14/07/2021

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor

Picos de tráfego e sobrecarga de servidores são preocupações sérias para varejistas online em qualquer época, mas especialmente durante a “Cyber Season” de compras de fim de ano. Detalhamos como você pode aproveitar as soluções de entrega de aplicações de nível de produção da NGINX para se preparar para aumentos repentinos de tráfego.

Distribuição segura de chaves privadas SSL com o NGINX
NGINX | 02/04/2019

Distribuição segura de chaves privadas SSL com o NGINX

Descrevemos três maneiras progressivamente mais seguras de proteger chaves privadas SSL ao configurar o NGINX para processar tráfego HTTPS: permitir acesso de leitura apenas ao usuário root, criptografar as chaves com senhas armazenadas separadamente e distribuir as senhas a partir de um repositório central.

Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX
NGINX | 24/05/2018

Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX

O módulo JavaScript do NGINX lançado com o NGINX Plus R15 oferece suporte a subsolicitações, possibilitando uma classe totalmente nova de casos de uso para o NGINX Plus. Este exemplo completo também usa a API do NGINX Plus e os módulos de chave-valor para converter solicitações de API dos clientes em solicitações separadas para as APIs dos microsserviços que compõem um aplicativo.

Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus
NGINX | 19/04/2016

Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus

Confira dicas para evitar o esgotamento de portas efêmeras com ajustes no kernel do Linux, conexões keepalive e conexões vinculadas dinamicamente a endereços IP locais.