Servidores de Aplicação
Resumo Continuando a nossa série de artigos sobre a caminhada da CELEPAR rumo às tecnologias de objetos e componentes distribuídos, hoje falaremos sobre servidores de aplicação. Além de explicarmos o que isso significa, descreveremos com um nível razoável de detalhes um tipo de componente de software comumente utilizado nesses servidores, chamado monitor de transação. Suas funcionalidades são indispensáveis em sistemas corporativos que possuem requisitos de escalabilidade e robustez. Apresentação Um servidor de aplicação é uma plataforma sobre a qual roda a porção servidora de um aplicativo. Isto inclui hardware e software. O hardware está fora do escopo deste artigo, mas o software pode ser dividido em dois grupos: funções do negócio, que são específicas para um domínio de problema; e serviços especializados, que são funções genéricas aplicáveis a diversas soluções. Assim sendo, do ponto de vista do software um servidor de aplicação consiste de um agrupamento de funções de negócio e de serviços que integrados satisfazem as necessidades dos usuários. Embora existam diversos serviços disponíveis atualmente em forma de componentes de software, nos concentraremos em dois deles que consideramos de suma importância para o desenvolvimento de aplicações robustas e escaláveis: monitoramento de transações e gerenciamento de filas de requisições. Esses serviços podem estar implementados em diversos tipos de produtos, como por exemplo monitores de transação [1,7,8,9] gerenciadores de transação entre objetos (OTM – Object Transaction Manager), serviço de transação dos ORBs [4,5,6], middleware orientado a mensagem (MOM – Message Oriented Middleware) [7], serviço de eventos dos ORBs [4,5,6] e extensões do sistema operacional. Um monitor de transação moderno suporta transações entre objetos e gerenciamento de filas também. Por essa razão, uma análise mais aprofundada desse tipo de produto nos permitirá compreender como é possível adicionar robustez e escalabilidade a uma solução. Comecemos pelo conceito: um monitor de transações é um produto de software utilizado para criar, executar e gerenciar aplicações de processamento transacional. Entre as suas principais funcionalidades podemos destacar:
Todas essas funcionalidades reunidas em um único produto proporcionam uma melhoria substancial de performance e contribuem significativamente para a produção de sistemas distribuídos robustos e escaláveis. Discutiremos um pouco mais a fundo esse componente arquitetural pré-fabricado sob cinco enfoques: a importância dos monitores de transação; a arquitetura dos monitores de transação; como um monitor de transação contribui para a escalabilidade; como um monitor de transação contribui para a robustez; e um comparativo entre sistemas gerenciadores de bancos de dados e monitores de transação. 1. A importância dos monitores de transação A performance é um aspecto crítico de sistemas transacionais. Ninguém está disposto a esperar mais do que poucos segundos para saber se existe poltrona disponível num vôo. É um grande desafio configurar um sistema para atender aos requisitos de tempo de resposta e de produtividade a um custo mínimo, pois existem muitas opções de produtos disponíveis. Por esse motivo, os vendedores que atuam no mercado de processamento transacional formaram um consórcio independente chamado Transaction Processing Performance Council (TPC) [1,8,9,10], o qual definiu os primeiros benchmarks de sistemas transacionais que poderiam ser usados para comparar de forma justa diferentes produtos de diferentes vendedores. Cada benchmark do TPC utiliza programas transacionais padronizados e define a performance do sistema como sendo sua capacidade de processamento (throughput) diante de condições predeterminadas de carga de trabalho (workload), de tamanho do banco de dados, de tempo de resposta e assim sucessivamente. Há duas medidas principais para a performance de um sistema: o throughput máximo, medido em transações por minuto (tpm), e o custo das transações, medido em dólares por tpm ($/tpm). O custo é calculado como o preço de tabela dos produtos de hardware e software necessários, mais cinco anos de manutenção desses produtos (chamadado custo de propriedade). Os dois primeiros benchmarks, chamados TPC-A e TPC-B, modelaram uma aplicação de automação de caixa eletrônico (automated teller machine), mas esses benchmarks foram aposentados e substituídos pelo TPC-C. TPC-A e TPC-B são muito simplistas comparados à maioria das aplicações transacionais atuais. Além disso, quase todos os fabricantes têm números de benchmark TPC-A/B tão bons que estes índices não representam mais um bom diferencial entre os produtos a serem selecionados. O TPC-C é baseado numa aplicação de entrada de pedidos para um fornecedor de produtos no atacado. Comparado ao TPC-A/B, ele inclui uma maior variedade de transações, algumas bastante pesadas, e um banco de dados mais complexo. O tamanho do banco de dados é proporcional ao número de depósitos de produtos. Há cinco tipos de transação:
As três primeiras transações têm requisitos rígidos de tempo de resposta. A transação de "entrega do pedido" executa como um batch periódico e a transação de "consulta ao nível de estoque" tem requisitos de tempo de resposta e consistência mais flexíveis. Com relação à carga de trabalho, exige-se um número igual de transações do tipo "registro de novo pedido" e "pagamento de pedido", e para cada 10 novos pedidos processados exige-se uma transação do tipo "consulta à situação de um pedido", "entrega de pedido" e "consulta ao nível de estoque". O TPC-C apresenta maior disputa pelos dados compartilhados e exercita uma variedade mais ampla de funções sensíveis à performance, tais como execução de transações a posteriori, acesso via chave secundária e cancelamento de transações (abort). Mas, por que o interesse nos testes TPC? Simplesmente para reforçar a importância dos monitores de transação. Todas as soluções de alta performance divulgadas pelo TPC [10], sem exceção, utilizam monitores de transação. A tabela abaixo mostra os 10 primeiros classificados em ordem de throughput (tpmC). A segunda tabela mostra os 10 primeiros classificados com base no custo da transação ($/tpmC). Os dados contidos nessas tabelas foram coletados em 25/11/1998.
O TPC-C é um benchmark para sistemas transacionais. Existe também o TPC-D, um benchmark que modela um ambiente de apoio a decisão no qual consultas ad hoc complexas são executadas sobre grandes bancos de dados. As consultas geralmente apresentam uma ou mais das seguintes características:
O propósito do TPC-D é avaliar custo e performance de sistemas que suportam esse tipo de aplicação. 2. A arquitetura dos monitores de transação Um monitor de transação implementa seu fluxo de controle utilizando o conjunto de componentes de software ilustrados na figura abaixo [8].
2.1. Presentation Server A estação de trabalho interage com o componente chamado presentation server (servidor de apresentação), o qual é responsável pela tradução das telas, das seleções de menus, e de outros tipos de entradas em uma mensagem de formato padrão (requests). Serviços de apresentação estão sendo fornecidos cada vez mais por linguagens de quarta geração, tais como Visual Basic, PowerBuilder e Delphi. A requisição proveniente do servidor de apresentação pode ser temporariamente armazenada em disco numa fila, ou pode ser redirecionada diretamente para ser processada pelo componente chamado workflow controller (controlador de fluxo de trabalho). Este componente roteia a requisição para o servidor de transação apropriado e invoca um programa desse servidor, o qual realiza o trabalho solicitado, isto é, o qual executa realmente a transação [8]. Os primeiros monitores de transação tinham um servidor de apresentação nativo para executar a interface com o usuáro. Nos anos 80, produtos independentes para gerenciamento de interface tornaram-se populares, os quais ofereciam ambientes de construção de formulários do tipo wysiwig (what you see is what you get, ou o que você vê é o que você obtém) e chamavam funções de uma linguagem de programação padrão. Quando utilizamos um gerenciador de formulários que não está totalmente integrado com o servidor de apresentação, o programador da aplicação precisa fazer chamadas para este servidor que construirá a requisição. Isto é comum quando se utiliza linguagens de quarta geração cliente/servidor e gerenciadores de formulários projetados para interagir com dispositivos especializados, tais como leitoras de códigos de barras, terminais de autorização de cartões de crédito, caixas registradoras, bombas de combustível, robôs, ATMs e assim por diante. 2.2. Workflow Controller O propósito principal do controlador de fluxo de trabalho é mapear as requisições recebidas para servidores de transação capazes de executá-las e enviar essas requisições aos respectivos servidores. Se a transação produz algum tipo de saída, o controlador de fluxo é responsável por rotear essa resposta novamente para o servidor de apresentação que enviou a requisição. Normalmente, a camada de controle de fluxo executa as operações Start, Commit e Abort. Dentro da transação poderá haver chamadas para um ou mais servidores de transação. Outras responsabilidades importantes do controlador de fluxo são as seguintes: interação com o serviço de diretório; roteamento de requisições baseada em parâmetros; gerenciamento de sessões e segurança; minimização das sessões de comunicação; coordenação de transações aninhadas; tratamento de erros; finalmente, gerenciamento de pontos de salvamento de trabalhos parcialmente realizados (savepoints). 2.3. Transaction Server Um servidor de transação é um programa da aplicação que realmente executa a requisição. Pode ser um programa interno, ou pode chamar outros programas para realizar seu trabalho. Esses outros programas podem executar no mesmo ambiente do servidor de transação ou em um sistema remoto, caso em que uma mensagem de comunicação é necessária entre o módulo chamador e o módulo chamado. Os servidores de transação não podem ser considerados programas comuns de acesso a dados por dois motivos: eles necessitam atender altas taxas de requisição e precisam também suportar comunicações baseadas em transações. Realizar comunicação dentro do contexto de uma transação não é uma tarefa fácil, por duas razões. Primeiro, se você inicia uma transação em um processo, tal como o controlador de fluxo, e então envia uma mensagem para outro processo, como um servidor de transação, a este último precisa ser dito que ele está operando na mesma transação que aquele que o chamou. Segundo, todos os processos que foram envolvidos na transação devem ser informados quando há o comprometimento (commit) da transação ou seu cancelamento (abort). Uma grande parte do trabalho de um monitor de transações está dedicada a essas atividades. 3. Como um monitor de transação contribui para a escalabilidade Ao explicar a arquitetura de um monitor de transações, dissemos que o workflow controller é responsável, entre outras coisas, por minimizar o número de sessões de comunicação. Como isso acontece? Suponha que existissem 300 servidores de apresentação e 50 servidores de transação em um determinado sistema, conforme mostra a figura a seguir [8]. Quantas sessões seriam necessárias para permitir que qualquer servidor de apresentação acessasse qualquer servidor de transação? 300 x 50 = 15.000 sessões.
Isto significa que o modelo 2-camadas gera uma explosão polinomial de sessões. Como evitar essa explosão? Colocando-se controladores de fluxo de trabalho entre os servidores de apresentação e os servidores de transação, ou seja, implementando o modelo 3-camadas. Considere os mesmos números anteriores, 300 clientes e 50 servidores. Acrescente 10 controladores de fluxo, cada qual conectado com todos os servidores de transação, conforme mostra a figura a seguir [8].
Dentro desta nova arquitetura, quantas sessões seriam necessárias para permitir que qualquer servidor de apresentação acessasse qualquer servidor de transação? Não existe uma resposta exata para essa pergunta, em virtude da distribuição de funcionalidades entre os controladores de fluxo. Se todos eles possuírem as mesmas funções, cada servidor de apresentação só precisa estar conectado a um controlador de fluxo para ter acesso a todas as transações disponíveis no sistema. Por outro lado, se cada função estiver disponível apenas em um único controlador de fluxo, então cada servidor de apresentação precisa estar conectado a todos os controladores de fluxo simultaneamente para ter acesso a todas as transações do sistema. Obviamente, seria possível criar configurações intermediárias nas quais algumas funções estivessem repetidas em diversos controladores de fluxo e outras fossem exclusivas. Mas, para efeito de cálculo podemos nos concentrar nos dois casos extremos descritos inicialmente. O primeiro, que exige apenas uma conexão em cada servidor de apresentação, nos levaria a um total de 800 conexões (300 entre servidores de apresentação e controladores de fluxo, mais 500 (10 X 50) entre controladores de fluxo e servidores de transação). O segundo caso, que exige 10 conexões em cada servidor de apresentação nos levaria a um total de 3500 conexões (3000 (300 X 10) entre os servidores de apresentação e os controladores de fluxo, mais 500 (50 X 10) entre os controladores de fluxo e os servidores de transação). Na pior das hipóteses haveria uma redução de cerca de 76% no número de conexões necessárias, e na melhor das hipóteses a economia seria de aproximadamente 94%. Note que em qualquer situação o monitor de transação seria capaz de reduzir drasticamente o número de sessões de comunicação e conseqüentemente de aumentar a escalabilidade do sistema. Outra contribuição dos monitores de transação para a escalabilidade é a implementação de threads e server classes. A seguir, veremos porquê cada um desses recursos é importante. Começaremos pelas threads. O sistema operacional não funciona bem com um número muito grande de processos por muitas razões, dentre as quais podemos destacar as seguintes: diversas funções do sistema operacional navegam sequencialmente pelas listas de processos; a troca de contexto entre processos implica a substituição de registradores e a recarga da memória cache; normalmente, cada processo necessita de uma quantidade de memória física que não pode ser paginada. Devido a esses problemas os vendedores de monitores de transação começaram a suportar processos multithreaded. Cada thread é um caminho de execução independente dentro do processo. Todas as threads de um processo executam o mesmo programa e usam a mesma memória, mas cada uma delas tem uma área privativa (save area) para armazenar seus registradores e suas variáveis locais (stack), conforme mostra a figura a seguir [8].
As threads economizam memória, evitam trocas de contexto e reduzem o número de processos. Elas podem ser implementadas pelo monitor de transação ou pelo sistema operacional. Quando são controladas pelo monitor de transação o sistema operacional não sabe que elas existem. Neste caso, se o servidor de transação tentar ler dados do disco ou mensagens de comunicação e esses dados ainda não estiverem disponíveis, o sistema operacional bloqueará o processo. Se existirem múltiplas threads executando dentro do processo todas elas serão temporariamente interrompidas. Isto é ruim, pois algumas threads poderiam realizar trabalho útil enquanto outras esperassem por operações de I/O. Para evitar esse problema o monitor de transação tem que transformar operações de I/O síncronas em mensagens assíncronas para o disco, para o banco de dados ou para o sistema de comunicação, e tem que solicitar que seja gerada uma interrupção de software quando cada operação de I/O for completada. A vantagem deste modelo é o uso de multithreading, mas a desvantagem é que todas as chamadas para operações de I/O precisam ser interceptadas pelo monitor de transação. As versões iniciais do CICS para mainframe funcionavam dessa forma. A outra alternativa é deixar que o sistema operacional controle as threads de um processo. Neste caso, quando uma thread executa uma operação de I/O síncrona o sistema operacional bloqueia a respectiva thread. Isto evita troca de contexto desnecessária e se o processo estiver executando em uma plataforma SMP (Symmetric Multiprocessor) ele pode distribuir suas threads para diferentes processadores, proporcionando paralelismo entre as threads do mesmo processo. Um problema desta alternativa é o overhead de performance. Uma vez que é o sistema operacional quem gerencia a execução das threads isto implica a utilização de system calls, que geralmente são mais caras que operações executadas a nível de usuário (o monitor de transações opera neste nível). Quando múltiplas threads executam dentro do mesmo processo existe pouca ou nenhuma proteção de memória entre elas. Um erro no processamento de uma thread pode comprometer a memória de todo o processo. Resumindo, multithreading oferece melhorias significativas de eficiência, mas deve ser usado cuidadosamente para evitar bloqueio durante operações de I/O em disco, interferência entre o scheduling do monitor de transação e do sistema operacional, overhead de peformance na troca de contexto das threads e corrupção de memória desprotegida. Na maioria das aplicações, multithreading controlado pelo sistema operacional é superior ao controlado pelo monitor de transação, uma vez que evita os dois primeiros problemas desta lista e pode se beneficiar de configurações SMP. Uma boa alternativa para processos multithreaded é usar diversos processos emulando um conjunto de threads, isto é, ao invés de ter um processo multithreaded, o sistema usa um conjunto de processos single-threaded, todos rodando o mesmo programa, conforme mostra a figura a seguir [8].
Esta técnica é chamada de server class porque é mais usada nos servidores de transação e sua principal característica é evitar as seguintes desvantagens de processos multithreading: tratamento especial para operações de I/O; conflitos de scheduling entre processos e threads; possibilidade de problemas provenientes de corrupção de memória; queda do processo como um todo em virtude da falha de uma thread (um processo de uma server class falha de forma independente dos demais). Quando se utiliza server classes é necessário um mecanismo adicional cujo objetivo é despachar as chamadas recebidas por uma server class para processos servidores específicos. Trata-se do balanceamento de carga entre os servidores da classe. Isto pode ser feito de diversas formas, como por exemplo:
Há pouco dissemos que muitos processos single-threaded poderiam resultar em problemas de performance e que a solução seria processos multithreaded. Por que estamos aceitando que os servidores de transação sejam implementados como múltiplos processos single-threaded? Porque o número de servidores de transação necessários é proporcional ao número de transações em execução e não ao número de estações de trabalho disponíveis. Diante de todas as alternativas que foram apresentadas, com suas vantagens e desvantagens, podemos concluir que é possível otimizar a escalabilidade de um sistema implantando uma arquitetura 3-camadas, por meio de um monitor de transações, onde os servidores de apresentação e os controladores de fluxo sejam processos multithreaded e os servidores de transação sejam server classes. 4. Como um monitor de transação contribui para a robustez No processamento transacional direto um cliente envia uma requisição para um servidor e espera de forma síncrona que este execute a transação e retorne uma resposta. Embora esse modelo seja amplamente utilizado na prática, ele apresenta algumas limitações que estão ilustradas na figura a seguir [8].
|