Padrões de Arquiteturas de Computação em Nuvem

A era em que, para usar o poder computacional e versatilidade de servidores, era necessário comprar, instalar e gerenciar esse equipamentos chegou ao fim. Atualmente, existem diversos provedores de serviços de computação em nuvem que permitem o consumo de uma infinidade de recursos em computadores muito poderosos (caso necessário). O catálogo de produtos que esse provedores oferecem é muito grande e não para de crescer. Alguns desses são:

  • Máquinas virtuais
  • Redes
  • Bancos de Dados
  • Funções como serviço(FaaS)
  • Instâncias de containers
  • Cache e Filas como serviço
  • Orquestradores de Contêineres gerenciados
  • Etc

Essa infinidade de produtos dá aos engenheiros e desenvolvedores extrema flexibilidade ao desenvolverem seus serviços. Assim como em outros áreas do desenvolvimento de software, foram criados alguns padrões de arquiteturas de sistemas distribuídos, visando, principalmente, escalabilidade e resiliência desses.

O incrível Designing Distributed Systems, escrito pelo Brendan Burns, atualmente engenheiro na Microsoft e co-fundador do Kubernetes, mostra alguns dos padrões mais importantes usados atualmente no mundo, garantindo assim, serviços de qualidade, baixa latência, resiliência e escalabilidade. Nesse artigo, vou explicar alguns desses (principalmente os padrões para projetos distribuídos) e mostrar possíveis casos de uso.

  • Balanceamento de carga em serviços replicados: esse é um padrão muito comum em sistemas distribuídos, que visa distribuir o trabalho entre suas diversas réplicas de aplicações. Com isso, fica garantido um maior número de respostas para os usuários. Um balanceador de carga pode funcionar de diversas maneiras, mas sempre visa tentar dividir as requisições de maneira uniforme entre as instâncias, sempre ficando na frente das réplicas e, ao receber a chamada, decide qual instância está menos ocupada e envia o trabalho para ela.
Imagem 1 — Balanceador de carga em ação
  • Caches “quebradas”(sharded): a tradução desse padrão é um pouco difícil se ser feita, mas ao explicar o que ela significa, ficará um pouco mais claro. Caches são locais que armazenam dados e possuem um tempo de resposta muito mais rápido que realizar buscas em bancos de dados ou buscar dados em disco. Elas são usadas para aumentar a agilidade de entrega de dados para o usuário. O fluxo de uma aplicação que necessita de leituras de dados e possui Cache normalmente é o seguinte:
  1. Busca dado na cache
  2. Encontrou? Retorna ao usuário.
  3. Não encontrou? Busca no banco de dados ou em algum outro tipo de armazenamento.
Figura 2 — Como sistemas distribuídos com cache normalmente funcionam

O chamado cache miss acontece quando você busca um dado na cache e ele não está lá, ou seja, precisa ser buscado em uma outra fonte de armazenamento, possivelmente mais lenta. Quando você possui uma cache muito pequena para seu volume de dados, a chance dos cache misses acontecerem é grande, o que tornam o sistema mais lento, piorando a experiência do usuário. Para esse tipo de problema, temos um padrão chamado de sharding(quebra) de cache. Nesse modo, você separa o conjunto de dados que cada cache pode conter. Por exemplo, no caso de um sistema que guarda registro de CPF’s, uma cache pode guardar todos os CPF’s que começam com números entre 0 e 5 e uma segunda instância guarda o restante. Dessa maneira, é possível escalar horizontalmente a cache de seu sistema. Vale ressaltar ainda, que sharding não é uma operação exclusiva de caches, também muito realizada em bancos de dados, por exemplo.

  • Quebra e junção (Scatter/Gatter): esse padrão é extremamente popular entre aplicações que podem tirar proveito de paralelização de código para ganho de performance. Isso acontece da seguinte maneira: ao receber a entrada do usuário, o sistema “quebra” a entrada em várias partes independentes, envia para as instâncias de máquinas de maneira separada, para que essas resolvem uma parcela menor do problema total, e ao final, uni todas as partes e devolve a resposta ao usuário. Por exemplo: imagine que você possui um vetor com 10000 elementos e duas máquinas que podem realizar o processamento. O usuário final deseja receber o vetor com todos os seus números elevados ao quadrado. Usando esse padrão, podemos deixar 5000 números com a primeira máquina e 5000 com a segunda, concatenando as duas metades do vetor ao final do trabalho.
Imagem 3- Padrão de Quebra e Junção
  • Fila de Trabalho: O sistema de filas de trabalho é extremamente eficaz em situações que algum tipo de tarefa que vai demorar o suficiente para exceder o tempo de uma conexão cliente-servidor, por exemplo. Nesse cenário, possuímos uma fila que recebe pedidos de trabalho e instâncias de aplicação que realizam tal trabalho. De tempos em tempos, esses “trabalhadores” analisam a fila procurando por trabalhos, e, se existirem, os executam, retornando a resposta ao terminar.
Imagem 4 — Sistema de filas de trabalho

Por exemplo, um sistema que redimensiona imagens. Os pedidos de redimensionamento chegam em uma fila, os consumidores a verificam, pegam uma instância, realizam o redimensionamento e devolvem o resultado.

Esses são quatro padrões muito importantes nos dias de hoje, mas ainda existem uma infinidade de outros, que pretendo abordar em outros textos.

Muito obrigado por ler até aqui e até a próxima.