Neste post serão apresentados os diferentes tipos de processos que são uma parte crucial dos sistemas distribuídos. O conceito de processo foi definido pelos estudiosos de Sistemas Operacionais, onde um processo é um programa em execução. Os Sistemas Operacionais atuais conseguem administrar vários programas executando ao mesmo tempo, como um editor de texto sendo executado ao mesmo tempo que uma impressão é realizada.
Apesar de existirem muitas vantagens em construir clientes multithreads, a grande vantagem em implementar um sistema MultiThread está nos servidores. Os servidores possuem um maior poder de processamento e desenvolver tarefas multithreads neles, faz com que o aproveitamento das máquinas seja muito melhor. Neste post serão apresentados os diferentes tipos de processos que são uma parte crucial dos sistemas distribuídos.
Considere um sistema de arquivos distribuídos, onde cada operação de leitura deve esperar pela resposta do hardware de armazenamento de dados. Estes servidores ficam esperando por requisições de dados, e assim que elas chegam ele executa a tarefa e retorna uma resposta ao cliente.
Na implementação de um servidor de arquivo, o trabalho de ler os dados do sistema de armazenamento deve ser implementado em uma thread, assim o processo principal, de receber as chamadas, não ficará travado aguardando a resposta do hardware, ele fica livre para receber chamadas, enquanto o processamento da leitura é realizado. Assim que a leitura completa, o sistema procede e compõe a resposta.
Nesta implementação múltiplas chamadas de leitura podem ser recebidas em paralelo e não necessitam ficar aguardando a execução de uma chamada completar.
Virtualização
Threads e processos, de certa forma, são maneiras de fazer múltiplas coisas ao mesmo tempo. Com eles é possível construirmos programas que aparentam serem executados em paralelo. Em um computador com uma única CPU, algo não comum hoje em dia, esta execução em paralelo é uma ilusão. A rápida troca entre as threads e processos cria esta ilusão de paralelismo.
Este tipo de ilusão também pode ser extendido a outros recursos da máquina, o que também é conhecido por virtualização de recursos.
A virtualização já é conhecida há muito tempo, porém ela vem ganhando muita importância nos sistemas distribuídos
O papel da virtualização nos sistemas distribuídos
Um dos papéis mais importantes, realizados pela virtualização, na década de 70, foi permitir que softwares legados rodassem nos hardwares dos mainframes. Isto incluía não só diversos programas, como Sistemas Operacionais. Este suporte aos softwares antigos, foi muito bem aplicado pela IBM, nos seus Mainframes 370 (e seus sucessores) os quais ofereciam uma máquina virtual onde muitos sistemas operacionais foram portados.
O hardware, assim como os sistemas de baixo nível mudam constantemente, enquanto os softwares de alto nível, que são mais abstratos, são bem mais estáveis. Com isso os sistemas legados não acompanham a evolução dos sistemas de baixo nível, o que pode gerar uma incompatibilidade entre eles. A virtualização pode ajudar bastante neste tipo de caso ao portar estes sistemas legados para as novas plataformas de hardware.
Um outro fato interessante é que as conexões de redes são quase que obrigatórias nas máquinas. Estas conexões fazem com que os administradores de sistema tenham que manter um grande número de servidores diferentes, cada um com um tipo diferente de aplicação. Porém, alguns dos recursos destes servidores, precisam ser compartilhados entre as máquinas. Neste caso a virtualização pode ajudar bastante, rodando diversos sistemas operacionais diferentes, e emulando diversos tipos de hardware diferente, tudo sobre uma mesma plataforma.
Arquitetura de Máquinas Virtuais
Existem muitas maneiras diferentes de se virtualizar um sistema e para compreender os tipos diferentes de virtualização é importante conhecer os quatro tipos diferentes de interfaces providas por um computador:
- A interface entre o hardware e o software, que consiste em instruções de máquinas que podem ser invocadas por qualquer programa.
- A interface entre o hardware e o software que só pode ser invocada por programas com privilégios, como um sistema operacional.
- A interface que possui as chamadas de sistema, oferecidas pelo sistema operacional.
- A interface de uma biblioteca geralmente formando uma API. Que em muitos casos empacota as chamadas de sistema mencionadas anteriormente.
A essência da virtualização é emular o comportamento destas interfaces.
A virtualização pode ser feita de duas maneiras, uma delas pode-se prover uma camada de abstração de instruções, como é feito pela Máquina Virtual Java (JVM), ou mesmo pode ser emulada, como é feita pelo software Wine, que emular o Windows em um sistema operacional Linux. Isso é feito de maneira a emular geralmente apenas um processo.
Uma outra forma de realizarmos a Virtualização é implementar um sistema que emula uma camada de hardware, fornecendo uma interface de software que emula o hardware como uma interface. Com isso é possível disponibilizar esta interface para diferentes programas. Desta maneira pode-se ter múltiplos Sistemas Operacionais rodando concorrentemente e independente em uma mesma plataforma. Como exemplos podemos citar o Virtualbox e VMWare.
Containers
Um container é uma unidade de software, uma forma de empacotamento, que contêm, tudo o que é necessário, inclusive as dependências, para se executar um software.
O container garante que o software rodará, da mesma maneira, independente da plataforma, ou computador onde ele rode. Containerizar um software garante a ele, uma certa independência de plataforma.
Com o uso deles, foi possível separar responsabilidades, como os desenvolvedores passaram a cuidar, apenas da parte lógica da aplicação e suas dependências, enquanto que a equipe de infra, cuida do ambiente de execução daquela aplicação. Com este isolamento, fica mais fácil identificar problemas de execução e de quem é a responsabilidade pelas falhas de um aplicativo. Os containers surgiram para acabar com a famosa frase dos desenvolvedores, na minha máquina funciona. Uma vez que é possível empacotar e testar o software, por completo, não há mais motivos para um desenvolvedor entregar algo que não execute da maneira correta. Isto ainda não impede a entrega de softwares com bugs, mas torna mais fácil descobrir onde eles estão.
É bem comum, comparar os containers, com os ambientes virtuais, e de certa forma isto é correto, uma vez que os containers isolam a execução entre várias aplicações. Porém, os containers oferecem uma solução, muito mais leve, que as máquinas virtuais, uma vez que eles compartilham, entre si, o sistema operacional. Isso acontece, pois as máquinas virtuais, emulam todo o aparato de hardware, entre os softwares, e o sistema operacional, já um container, emula um sistema operacional.
Docker, Jetty, Tomcat, Wildfy e SpringBoot são exemplos de containers que podemos usar para desenvolver aplicativos.
Em um data center, a utilização de um container, não substitui a utilização de uma máquina virtual, lembre-se que um hardware muito grande, pode emular diversas máquinas através da virtualização, e dentro de uma instância virtual, podemos ter diversos containers.
Clientes
Network User Interface
Uma das principais tarefas das máquinas clientes é prover, para o usuário, uma maneira eficiente e uma boa interface de acesso aos dados dos servidores remotos. A primeira decisão de design é o cliente ter uma parte de código responsável por cada serviço diferente que ele irá acessar.
A segunda opção é prover um acesso direto aos serviços remotos, somente provendo uma interface conveniente, o que significa que o cliente somente será usado como um terminal, sem qualquer necessidade de armazenamento de dados. Neste caso tudo será processado e armazenado no servidor.
Um dos exemplos de NUI é o XServer, o servidor de janelas do Unix, que é utilizado para controlar terminais que incluem, o monitor, o teclado e possívelmente um mouse. A principal parte do sistema é formado por um kernel, que contem alguns drivers específicos para aquele terminal e é altamente dependente do hardware.
Um aspecto interessante do sistema X é que o kernel e os aplicativos podem estar em diferentes máquinas.
Este sistema disponibiliza uma biblioteca chamada XLib, que serve para executar chamadas de baixo nível e controlar o teclado, o mouse e a tela.
Um dos principais aplicativos que são executados sobre o XServer são os Window Managers, que na verdade são os Look and Feels dos sistemas X. Estes aplicativos são responsáveis por controlar os eventos, a aparência, as janelas do sistema.
O mesmo computador Unix pode ter vários window managers para prover ao usuário diferentes interfaces, acessos e usos do hardware.
Pelo apresentado até aqui, o sistema X encaixa-se no tipo de arquitetura cliente-servidor, já que o kernel recebe as requisições para manipular as janelas, e estas requisições podem vir de máquinas remotas.
Servidores
Design Geral
Um servidor é um provedor de um serviço específico, para vários clientes. Essencialmente, cada servidor é organizado da mesma maneira, eles esperam uma requisição de um cliente e depois cuida para que esta requisição seja atendida da maneira correta, enquanto ele retorna para o estado de espera por uma nova requisição.
Existem diversas maneiras de se organizar os servidores. Nos servidores iterativos, ele cuidará das requisições e, se necessário, retorna a resposta para os clientes. Um servidor concorrente não cuida sozinho da requisição, ele encaminha ela para uma thread separada, ou um processo diferente e depois disto ele fica aguardando uma nova requisição. Um servidor web é um exemplo de um servidor concorrente.
Para se conectar com um servidor, devemos acessar uma requisição para um ponto de entrada (End point), também conhecido por porta onde o servidor está rodando. Cada servidor fica escutando uma porta específica.
E como fazer para que os clientes saibam qual é a porta de um serviço? Uma das soluções encontradas para isso foi configurar a mesma porta, sempre, para serviços comumente utilizados, como a porta FTP 21, SSH 22 e HTTP 80.
É comum associar um end point para cada serviço, porém, implementar cada serviço em um servidor diferente muitas vezes será um desperdício de recursos de hardware. Em um serviço típico de UNIX é comum termos uma série de servidores rodando simultaneamente, onde a maioria deles fica passivamente aguardando um cliente se conectar. Ao invés de ter muitos servidores passivos rodando concorrentemente, fica mais eficiente ter um super servidor, escutando cada porta de cada serviço específico configurado.
Uma outra preocupação ao desenvolver um sistema de servidor é como e quando este servidor ou o serviço poderá ser interrompido. Por exemplo se um usuário decide fazer um download de um arquivo muito grande, e após alguns minutos ele descobre que está fazendo o download do arquivo errado e gostaria de interromper aquele download. Uma solução bem comum para este tipo de problema é matar a parte do cliente, o que é muito comum nos aplicativos da internet, e com isso a conexão será interrompida abruptamente.
A solução mais elegante é prover um outro serviço no servidor, que ficará escutando por uma mensagem específica, também conhecida como Out of Band data, que é responsável por terminar a transmissão de dados. Estes dados também podem ser enviados pela mesma conexão por onde o download está sendo realizado, para isto basta fazer com que o serviço seja capaz de receber este tipo de mensagem.
Um último problema a ser levado em consideração é se o seu servidor será ou não stateless (sem estado). Os servidores stateless não guardam o estado do seu cliente e poderão mudar o seu próprio estado sem informar os seus clientes.
Por exemplo, um servidor que possui uma página de web estática é stateless, já que ele somente responde por requisições HTTP que podem ser de upload ou download de alguns arquivos, muitas vezes HTML. Assim que a requisição é processada, o servidor se esquece completamente daquele cliente. Com isso a página hospedada naquele servidor pode ser alterada, sem que qualquer cliente seja avisado.
Perceba que muitos servidores stateless mantem informações dos seus clientes, porém se estas informações foram perdidas, isso não afetará de forma alguma o serviço provido por aquele servidor.
Diferentemente de um servidor stateless, estão os servidores stateful, os quais armazenam as informações dos seus clientes. Um exemplo típico é um webmail, que precisa armazenar os dados de cada cliente, afim de mostrar para ele somente as informações referentes a sua conta. Note que se os dados das conexões dos clientes forem perdidos, todo mundo será deslogado do sistema, afetando o seu serviço.
Cluster de Servidores
Montar um cluster de servidores nada mais é do que juntar algumas máquinas, conectadas através de uma rede, onde cada uma destas máquinas roda um ou mais servidores.