Introdução
No ano passado, ajudei um colega que estava concluindo um mestrado, cuja tese envolvia diretamente a eficiência de mecanismos de balanceamento de carga e suas abordagens. E, uma vez absorvido algum conhecimento a mais a respeito, acho que podemos dar um mergulho no assunto, e aproveitar para conhecer mais de perto o balanceamento de conexões nativo Application Server para conexões do SmartClient.
Balanceamento de Carga
Também conhecido por “Load balancing”, é um mecanismo que distribui requisições de processamento para múltiplos recursos computacionais, como computadores, “Cluster” de computadores, links de rede, unidades de processamento ou unidades de disco. O objetivo é otimizar o uso de recursos, maximizando disponibilidade e resultados, minimizando tempos de resposta, e evitar sobrecarga de uso de recursos. A utilização de múltiplos componentes com balanceamento de carga ao invés de um único componente deve aumentar a escalabilidade e disponibilidade através da redundância. Normalmente este recurso envolve software e/ou hardware específicos.
Entre as técnicas mais comuns, podemos citar o Round-Robin de DNS (onde uma URL de um WebSite retorna um IP diferente para cada consulta ao DNS, direcionando as requisições para outros servidores), algoritmos de distribuição (normalmente round-robin ou escolha randômica) são usados para decidir qual servidor irá processar uma chamada qualquer. Algoritmos mais sofisticados de balanceamento podem usar como fatores determinantes informações como tempo médio de resposta, estado do serviço, tráfego atual, etc.). Existe também um outro recurso, chamado “Proxy reverso” (Reverse Proxy), que além de outras funcionalidades, também pode ser utilizado para distribuição de requisições e balanceamento.
Pontos comuns
Cada mecanismo atua sob princípios diferentes, e visando suprir as necessidades dos ambientes onde estão inseridos, mas de uma forma geral, todos aderem aos mesmos princípios de desempenho, escalabilidade e resiliência. Como eu já disse, dentre os vários mecanismos disponíveis, não existe um “melhor de todos”, existem aqueles que melhor atendem as suas necessidades. Um sistema complexo pode usar vários níveis de balanceamento usando abordagens diferentes, para partes diferentes do sistema.
Balanceamento de carga no Protheus
O Protheus Server (ou Application Server) do AdvPL possui um mecanismo de balanceamento de carga nativo para as conexões recebidas das aplicações Smartclient. Basicamente, você provisiona dois ou mais serviços do Application Server, com as mesmas configurações de ambiente, distribuídos em quaisquer máquinas, e configura um novo serviço, chamado de “Master” ou “Balance”, com as configurações de balanceamento de carga das conexões especificados na seção [servernetwork] do arquivo de configuração do servidor (appserver.ini).
Quando iniciamos uma aplicação AdvPL a partir de um SmartClient, normalmente o programa executado é responsável pela montagem das telas de interface e pela execução do processamento. Por exemplo, a inclusão de um pedido de venda pelo SmartClient, quando você finaliza a operação e confirma a inclusão, a função de inclusão de pedido é executada pelo mesmo processo de interface, mantendo a interface em stand-by até a conclusão do processo e o programa devolver uma mensagem para a interface e permitir você prosseguir com a operação.
Neste modelo, como o processamento é realizado pelo mesmo processo que atende a conexão, toda a “carga” do processo está atrelada a esta conexão, e a aplicação foi projetada sob o conceito de persistência de conexão. Logo, em se tratando das conexões de SmartClient, ao balancear uma conexão, automaticamente estamos balanceando a “carga”.
Requisitos e comportamentos
Ao configurar no “Master” todos os serviços “Slave” de balanceamento, devemos enumerar host/ip e porta de conexão TCP de cada serviço. Este IP e porta deve ser visível na rede usada pelos SmartClients para estabelecer a conexão. Se houver um firewall entre os smartclients e os application servers, os IPs e portas dos “Slave’s” também precisam ser “abertos”, não basta abrir o IP e porta do “Master”.
O serviço de balanceamento não atua como um “Proxy Reverso”, as conexões estabelecidas com ele são indiretamente redirecionadas ao “Slave” escolhido no momento que a conexão foi estabelecida. Se ele atuasse como um proxy reverso, ele se tornaria um gargalo de comunicação, e caso ele fosse derrubado, todas as conexões atualmente em uso de todo o ambiente cairiam junto.
Quando o Balance recebe a conexão de um SmartClient, ele decide na hora qual dos serviços cadastrados para balanceamento é o mais adequado a receber esta conexão, e retorna ao SmartClient uma instrução de reconexão, indicando ao SmartClient qual é o Serviço que ele deve se conectar. Logo, o Smartclient conecta com o “Master”, recebe a informação de qual o serviço adequado para conextar-se, desconecta do “Master”, e conecta-se diretamente com este serviço.
Toda a regra e controle de distribuição é realizado pelo “Master”. Como ele já possui pré-configurados os serviços que podem receber conexões, ele próprio mantém com cada serviço “Slave” uma conexão dedicada de monitoramento, através da qual ele “sabe” quantos processos estão em execução em cada um dos serviços, e se cada serviço está apto a receber novas conexões. Como cada conexão pode exercer uma carga variável durante seu uso, a uma métrica utilizada é direcionar a nova conexão ao serviço que têm o menor número de processos em execução no momento.
Cada serviço do Protheus, após a Build 7.00.090818, possui um processo interno dedicado ao monitoramento de memória de sua instância de serviço. Caso um limite de ocupação de memória da instância seja atingido, este serviço torna-se automaticamente bloqueado para aceitar novas conexões de SmartClient e subir novos Jobs. O Serviço “Master” se mantém atualizado sobre este status, e não direciona novas conexões para este “Slave” enquanto seu status for “bloqueado”. Veja as referências no final do artigo para as documentações da TDN que abordam em maior profundidade estes assuntos.
Não seria melhor usar outra regra ?
Muitas pessoas já me perguntaram isso, e a resposta é “neste modelo de processamento, não têm opção melhor”. Como a carga e consumo de memória e CPU depende da rotina que será chamada, e a rotina está atrelada a conexão, qualquer cenário de distribuição está sujeito a desequilíbrios, afinal as rotinas possuem consumo de memória, CPU e rede variáveis durante a sua execução, e mesmo se fosse possível transferir um processo para outro serviço “a quente”, se este processo também apresenta a mesma variabilidade de consumo de recursos, a transferência da conexão seria freqüente, tornando-se ineficiente e pesada.
Assumindo que cada conexão persistente possui características variáveis de consumo, o mais elegante é dividir pelo número de processos por serviço. Mesmo havendo um eventual desequilíbrio no consumo de recursos, uma vez atingida uma quantidade de memória acima do normal em um serviço, ele automaticamente “bloqueia-se” para não aceitar mais conexões. Quando ao consumo de CPU, quando sabe-se que determinadas rotinas vão demandar muito tempo e consumo de recursos, pode-se montar um ambiente de balanceamento secundário, com outros “Slave’s” dedicados, onde os usuários de rotinas mais pesadas pode alocar outros serviços, e alguns deles podem ser colocados em scheduler para serem executados após o expediente.
Configuração de Balanceamento no AdvPL
Basicamente, você deve criar uma nova seção no appserver.ini para cada “Slave” que você quer relacionar no balanceamento. Recomenda-se criar um identificador cujo nome esteja relacionado de alguma forma com a máquina servidora e o número do serviço utilizado. Vamos dar um exemplo de balanceamento de serviços em apenas uma máquina, subindo o servidor “Master” na porta 6000, e quatro outros serviços “Slave” nas portas seguintes ( 6001 a 6004 )
[servernetwork]
servers=SL_1,SL_2,SL_3,SL_4
[SL_1]
Server=172.16.10.201
Port=6001
Connections=20
[SL_2]
Server=172.16.10.201
Port=6002
Connections=20
[SL_3]
Server=172.16.10.201
Port=6003
Connections=20
[SL_4]
Server=172.16.10.201
Port=6004
Connections=20
A configuração acima deve ser inserida no appserver.ini do serviço eleito como “Master”, que neste exemplo está na mesma máquina que os “Slave’s””, mas na porta 6000. Quando o serviço é iniciado, ele verifica se existe a seção [servernetwork], e verifica quais são os serviços enumerados para balanceamento. O “Master” sobe um processo interno dedicado para monitorar cada um dos “Slave’s”, e saber em tempo real quantos processos estão sendo executados em cada um, e se eles não estão bloqueados para aceitar novas conexões. Caso um processo não consiga encontrar um “Slave”, ele fica tentando estabelecer uma conexão, e enquanto ele não sabe se o “Slave” está no ar ou não, ele não redireciona nenhuma conexão para aquele “Slave”.
Cada serviço que nós chamamos de “Slave”, não precisa de nenhuma chave especial, ele precisa apenas refletir as mesmas configurações de ambiente entre os servidores, e o mesmo ambiente tem que apontar para o mesmo RootPath, para o mesmo License Server, e todos os ambientes devem estar usando a mesma cópia do repositório.
Como os IPs e Portas de cada “Slave” precisam estar visíveis para o SmartClient conseguir conectar, todos os arquivos de configuração dos SmartClients do parque de máquinas deve estar configurado para apontar diretamente para o IP e porta do “Master”. Caso algum Smartclient não esteja configurado desta forma, e estiver apontando para um determinado “Slave”, isto não atrapalha o balanceamento, pois mesmo que a conexão não tenha sido redirecionada pelo “Master”, ele vai decidir o balanceamento baseado no número de processos em execução em cada “Slave”. Porém, ao apontar diretamente para um “Slave”, se ele estiver bloqueado ou indisponível, a conexão não será direcionada para nenhum outro “Slave”.
Limitação de Balanceamento
Para cada “Slave”, devemos especificar um número na chave “connections”. Este número por padrão não é um número “absoluto” de conexões, mas sim um número de “peso” de distribuição. Por exemplo, ao configurarmos o número 20 na chave “connections” de todos os “Slave’s” para balanceamento, o “Master” vai entender que a carga deve ser distribuída igualmente entre todos os “Slave’s”. Quando configuramos um dos “Slave’s” com connections=40 e os outros três com connections=20, haverá um percentual de diferença nesta distribuição.
A fórmula é calculada primeiro somando todos os números de conexão:
40+20+20+20 = 100
Agora, calculamos o percentual de conexões a serem desviadas para cada “Slave” dividindo a quantidade de conexões pelo numero total, e multiplicando por 100:
40/100 * 100 = 40 %
20/100 * 100 = 20 %
20/100 * 100 = 20 %
20/100 * 100 = 20 %
Logo, se neste ambiente forem feitas 10 conexões, 4 vão para o “Slave”1, e 2 para cada um dos outros “Slave’s”.
Porém, como a distribuição é feita igualmente, a primeira vai pro “Slave” 1 , a segunda também, a terceira vai pra um dos demais “Slave’s”, a quarta também, a quinta também. Quando o balanceamento atinge uma posição de equilíbrio, a sexta conexão deve ir para o balance 1, a sétima também, e as três últimas serão distribuídas para os demais “Slave’s”.
Normalmente em um ambiente equilibrado, o número é o mesmo. Você pode determinar um comportamento de exceção e priorizar mais os serviços de uma determinada máquina, caso ela tenha mais memória e CPU por exemplo. Porém, o mais saudável neste caso é criar mais de um serviço “Slave” na mesma máquina, para haver uma melhor utilização de recursos. Mesmo que este número originalmente não seja um fator limitante, você deve preenchê-lo com um valor aceitável do número máximo de conexões que realmente é saudável fazer em apenas um serviço.
Existe uma configuração na seção ServerNetwork, chamada BALANCELIMIT. Uma vez habilitada, ela considera que o somatório de conexões ( no nosso exemplo, 100 ) é um fator limitante. Por default, se o ambiente passar de 100 conexões, o balanceamento vai continuar distribuindo as conexões usando a mesma regra para os “Slave’s”, até que os “Slave’s” que atingirem um pico de memória não aceitarem mais conexões. Quando habilitamos esta configuração, o Balance vai parar de distribuir conexões para todos os “Slave’s”, caso a soma do numero de processos rodando em todos os “Slave’s” mapeados atingir ou superar a soma de conexões estipuladas nas chaves “connections” de cada “Slave” mapeado no balanceamento. Quando isso acontece, o Balance não redireciona mais conexões, retornando erros de indisponibilidade de serviços para o Smartclient que tentou se conectar.
Desequilíbrio de balanceamento
Enquanto todas as pessoas estão entrando no sistema, a distribuição de conexões é sempre uniforme. Porém, quando os usuários começam a sair do sistema, os serviços podem ficar desbalanceados, onde coincidentemente os usuários que saíram estavam conectados em alguns serviços específicos. Em condições normais de uso, este desbalanceamento não interfere no poder de processamento dos usuários que ainda estão conectados — exceto se coincidirem vários processos consumindo em excesso um determinado recurso de uma máquina, pois neste caso os demais usuários conectados em serviços naquela máquina podem ser penalizados pelo consumo destes processos. De qualquer modo, mesmo temporariamente desbalanceada a quantidade de conexões, as novas conexões feitas no ambiente vão ser priorizadas pelo “Master”, para entrar justamente nos “Slave’s” com o menor número de processos.
Configurações adicionais
Por default, um servidor “Master” pode aceitar conexões vindas do SmartClient, caso não haja nenhum outro “Slave” disponível para redirecionar a conexão. Este comportamento nem sempre é desejável em um ambiente, e pode ser desligado usando a configuração “Master” Connection=0 na seção [servernetwork].
E, existe uma configuração de diagnóstico, para colocar o mecanismo de balanceamento do “Master” em modo “Verbose”, onde cada conexão recebida por um SmartClient é registrada no log de console do Application Server (console.log), informando sua origem, e para qual “Slave” ela foi encaminhada. Basta colocar na seção ServerNetwork a chave Verbose=1
Balanceamento com SSL
Quando utilizamos SSL entre os SmartClients e o Application Server, cada serviço do Protheus, inclusive o “Master”, deve ser configurados com uma conexão SSL, em uma porta distinta. No momento de configurar cada seção de “Slave” no appserver.ini do serviço “Master”, devemos especificar a porta TCP original de cada serviço, e a porta SSL usando a configuração “SecurePort=nnnn”, onde nnnn é o número da porta reservada para conexão segura (SSL) com aquele “Slave”.
Internet e Intranet
Quando disponibilizamos um balanceamento de carga para SmartClients em um ambiente de Intranet, e queremos expandí-lo para a internet ou outra rede externa, a utilização do balanceamento de carga nativo do Application Server exige que os IPs e portas usadas pelos serviços estejam públicos, e também seja visíveis pelo mecanismo de balanceamento (Serviço “Master”), e criar dois serviços de balanceamento. Um IP e porta do “Master” de “Intranet” para serem usados pelos SmartClients dentro da sua rede interna, e um IP e Porta do “Master” para configurar os Smartclients vindos “de fora”.
Internamente, você pode fazer cada balance apontar para um grupo distinto de servidores dedicados ao acesso interno ou externo, ou colocar 2 IPs nas máquinas onde estão todos os seus serviços “Slave”, e pelo balanceamento apontar para todos os serviços disponíves na sua rede, lembrando que o “Master” de acesso interno precisa enxergar os IPs locais, e o “Master” para acesso externo tem que ser configurado com os IPs externos, e precisam acessar cada um dos serviços por estes IPs.
Posso usar “localhost” ?
Não, não pode. Se o “Master” retorna o IP e porta configurados para um “Slave”, na seção correspondente ao “Slave” no appserver.ini do “Master”, no ini do smartclient você apontou um IP e porta do “Master”, mas ao estabelecer a conexão, o “Master” verificou que o “localhost:6001” é a melhor opção, e vai devolver isso pro SmartClient … Logo, o smartclient vai tentar conectar com um serviço Protheus na própria máquina onde ele está instalado. Você pode usar uma máquina grande e colocar muitos serviços de Protheus nela, e fazer um balanceamento “local”, MAS os IPs e Portas usados na configuração devem ser visíveis aos SmartClients, ok ? Se você usar localhost, seu balanceamento somente vai funcionar para os SmartClients que você iniciar de dentro da própria máquina onde estão os serviços do Protheus.
Posso usar outro mecanismo de balanceamento ?
Sim, pode. Você pode usar um NLB, um Proxy reverso, qualquer outro mecanismo que não interfira no conteúdo dos pacotes trafegados e que mantenha a persistência da conexão com o “Slave” escolhido, sendo assim totalmente transparente para as aplicações envolvidas. Normalmente a única desvantagem deste mecanismo é saber se o “Slave” escolhido está realmente disponível para executar o programa. Quando um “Slave” está bloqueado, a porta TCP de conexão permanece no ar e aceita novas conexões, mas responde a elas com uma mensagem de erro e desconectam. Com este balanceamento, caso seja usada uma métrica qualquer, onde naquele momento este serviço esteja atendendo aquela métrica, uma nova conexão vai ser direcionada para ele, mas ele vai atender e retornar erro. Sem persistir a conexão, e a métrica sendo favorável para este “Slave”, você corre o risco de ninguém mais conseguir entrar no sistema, pois todas as conexões serão direcionadas para um “Slave” que está no ar, mas não está preparado para atender as conexões.
O mundo ideal
Um modelo muito interessante de distribuição de processos seria um MVC com o processamento realizado por um pool de processos dedicados, separados do processo de interface. Deste modo, o processo de interface teria um consumo de CPU e memória relativamente mais baixos, em razão apenas da quantidade de componentes de interface, e as requisições efetivas de processamento e iteração com os dados fossem distribuídos em um pool de serviços distribuídos (SOA – Service Oriented Architecture). Se as implementações de processamento mantivessem uma constante média de consumo de recursos por requisição, o mecanismo de balanceamento das requisições de processamento poderia utilizar métricas mais eficientes, direcionando um processo que consome mais CPU para um serviço onde a CPU não esteja “no gargalo”, e sabendo que um processo vai consumir mais memória que os demais, o mecanismo poderia verificar antes de distribuir quais os serviços disponíveis estão com a melhor ocupação, ou que agüentariam aceitar a requisição sem comprometer os processos em execução.
Isto atenderia tanto o cenário de desempenho quando o de resiliência. Com mais de uma máquina física configurada para atender mais de um tipo de requisição, caso um serviço ou uma máquina inteira apresentasse um problema ou indisponibilidade, até mesmo uma manutenção programada, cada máquina teria o seu próprio “orquestrador” de eventos, que ao perceber que uma máquina saiu fora do circuito, poderia direcionar para outro serviço em outra máquina. A sua alta disponibilidade teria um grau de tolerância de acordo com quantos porcento de máquina você tem de “sobra”.
Ao invés de configurar um cluster ativo-passivo, você determina qual é o mínimo de máquinas que precisam estar no ar para dar conta do recado, e coloca uma ou mais máquinas adicionais. Se o seu dia a dia ocuparia duas máquinas inteiras, com serviços espalhados nelas, você pode colocar uma terceira máquina. Logo, você vai ter uma máquina “de sobra”, mas utilizaria as três juntas para não bater 100% do seu uso de recursos. No pior cenário, se uma máquina inteira sair do ar por uma pane física ou uma janela de manutenção de hardware, o software redirecionaria toda a carga para os dois hardwares disponíveis, que em tese deveriam dar conta do recado enquanto essa terceira máquina não “volta”.
Se você coloca o dobro de poder computacional do que você realmente precisa, você pode “perder” momentaneamente até duas máquinas inteiras, que as duas restantes vão bater “na tampa” mas vão segurar o sistema no ar. Se neste cenário a terceira máquina ir pro vinagre, você tem ainda têm duas saídas de emergência: Priorizar os serviços importantes e tirar tudo que é supérfluo do ar naquele momento, limitando a quantidade de conexões e processos, pra a primeira máquina não sobrecarregar — é melhor alguns trabalhando do que tudo parado — e dependendo da granularidade dos serviços, você pode pegar algum equipamento de “emergência” e colocar alguns “Slave’s” neles para operação de “contingência”, distribuindo os serviços essenciais que a maquina 1 não iria dar conta.
Uma outra vertente no balanceamento de carga seria a utilização dos “Slave’s” encadeados, onde os processos seriam distribuídos somente para um slave, até que um “threshold” (limite) seja atingido, então os novos processos passam a alocar o segundo slave, e assim por diante. Este cenário é muito atrativo quando pensamos em escalabilidade para ambientes “Cloud”, onde seria possível por exemplo determinar um número inicial ou mínimo de máquinas virtuais, e criar uma regra para, quando o último slave começar a receber conexões, uma nova VM é disponibilizada no ambiente, e automaticamente inserida no balanceamento. Estas máquinas virtuais adicionais, assim que todas as conexões forem encerradas, poderia ‘fechar-se’ e ser descartada.
Em um ambiente de processamento de múltiplos hardwares, isto também seria útil para fazer uma janela de manutenção de um Hardware. Por exemplo, alterando a priorização dos slaves, deixamos por último na fila todos os slaves de um determinado equipamento, e bloqueamos as conexões para estes slaves. Conforme os usuários fossem desconectando da interface, no final do dia a máquina não estaria com nenhuma conexão, podendo ser tirada inteira do ar para manutenção, se que nenhum usuário desse falta disso.
Conclusão
A busca por eficiência de processos deve ser sempre uma constante em qualquer sistema com previsão de crescimento. E já existem muitos recursos voltados para isso. Entender como a aplicação funciona é um passo a mais para abrir caminhos para mudanças, com mais certeza de sucesso nas mudanças planejadas. Espero que este post tenha dado alguma luz “a mais” neste mecanismo, e que vocês tenham TERABYTES de sucesso na utilização deles 😀
Até o próximo post, pessoal 😉
Documentação TDN
Balanceamento de carga entre serviços
Seção [ServerNetwork]
Processo de monitoramento e controle de memória
Configuração BALANCELIMIT
Referências
Load balancing (computing). (2015, October 16). In Wikipedia, The Free Encyclopedia. Retrieved 00:49, October 31, 2015, from https://en.wikipedia.org/w/index.php?title=Load_balancing_(computing)&oldid=686092130
Reverse proxy. (2015, October 23). In Wikipedia, The Free Encyclopedia. Retrieved 13:47, October 31, 2015, from https://en.wikipedia.org/w/index.php?title=Reverse_proxy&oldid=687157637