Distributed System
Coleção de computadores autônomos conectados por uma rede e equipados com um sistema de software distribuído e que aparenta ao usuário ser um computador único.
Compartilhamento de Recursos
O principal objetivo de um SD é o compartilhamento de recursos - permitindo que múltiplos computadores colaborem e ofereçam serviços de forma integrada e eficiente. Esses recursos compartilhados podiam ser:
- Processamento
- Arquivos
- Dispositivos físicos (impressoras, sensores, servidores)
Exemplo
Imagine uma universidade com vários laboratórios de informática.
Cada laboratório tem seus próprios computadores e impressoras.
Com um sistema distribuído:
- Qualquer aluno pode imprimir em qualquer impressora da rede;
- Um programa pode ser executado em qualquer máquina disponível;
- Dados podem ser acessados de qualquer ponto da rede. Essa é a motivação histórica para SDs.
Vantagens da abordagem distribuída
Existe vários benefícios que explicam o por que SDs são amplamente utilizados em aplicações modernas.
Custo-benefício
Lei de Grosch: o poder de computação de um computador é proporcional ao quadrado do seu preço.
Desempenho
Tarefas podem ser executadas ao mesmo tempo, distribuídas entre várias máquinas.
Confiabilidade e Disponibilidade
O sistema continua funcionando mesmo que parte dele falhe. Se um node cair, outro assume automaticamente suas tarefas.
Escalabilidade
Se a demanda aumenta, basta adicionar mais nodes ao sistema para aumentar sua capacidade sem perder desempenho. Ou seja, permitem o crescimento incremental.
Proximidade geográfica
Nodes podem ser distribuídos geograficamente, permitindo que usuários acessem o node mais próximo, reduzindo a latência. Exemplo: CDNs (Content Delivery Networks) como Cloudflare.
Desvantagens da abordagem distribuída
Embora ofereçam muitas vantagens, os SDs também apresentam diversos desafios e limitações.
Complexidade de desenvolvimento
É preciso garantir consistência dos dados, sincronização entre processos, tratamento de falhas e coordenação de mensagens entre os nodes da rede.
Problemas de rede
A rede pode saturar, falhar ou introduzir latência, comprometendo o desempenho geral do sistema.
Segurança
Há mais superfícies de ataques em SDs, como acessos não autorizados, interceptações de dados ou DDoS.
Comunicação
A comunicação entre nós ocorre por meio de troca de mensagens em rede, o que aumenta o risco de perdas, atrasos ou duplicações de mensagens.
Elementos Funcionais
São os componentes conceituais responsáveis por permitir que várias máquinas trabalhem juntas como se fossem um único sistema coeso.
Naming System
Atribuir nomes únicos a recursos compartilhados. Permite identificar e localizar recursos dentro do SD. Exemplos:
- DNS é o sistema de nomes distribuído usado na Internet.
- NFS (Network File System) usa nomes de arquivos para localizar dados armazenados em servidores remotos.
Comunicação
Troca de Mensagens (ou seja, comandos e dados) entre nodes. SD devem otimizar implementação da comunicação e prover alto nível do modelo de programação dessa comunicação. Exemplos:
- gRPC
- REST APIs
Estrutura de Software
É preciso ter interfaces (regras) de comunicação claras e bem definidas.
Load Balancing
Otimização do uso de capacidade de processamento, de comunicação e recursos da rede em geral, para assim, ter um bom desempenho.
Manutenção de Consistência
- Consistência de atualização: atomicidade como meio de atualização instantânea de muitos elementos
- Consistência de replicação: cópias de um mesmo recurso devem ser “idênticas”
- Consistência de cache: modificações em um cliente devem ser propagadas ao gerenciador e aos demais clientes
- Consistência de falha: deve se evitar falhas múltiplas (em cascata); isolamento de falhas
- Consistência de relógio: relógios físicos (sincronização aproximada) e relógios lógicos (timestamp em mensagens)
- Consistência de interface de usuário: atrasos devido a comunicação podem causar visão inconsistente de aplicações gráficas
Middleware
É a camada de SW/lógica que permite que dois OS heterogênicos se comuniquem. Existem diversos tipos de Middleware, como por exemplo: Corba (Common Object Request Broker Architecture)
- Java RMI
- Microsoft DCOM
- XML WebServices (usa protocolo SOAP, que permite a comunicação entre aplicações desenvolvidas em diferentes plataformas e linguagens)
- RESTFul APIs (API que seguem esse estilo arquitetural)
Troca de Mensagens
É o conceito mais baixo nível para a comunicação em sistemas distribuídos. Baseado em duas operações primitivas entre dois processos rodando em diferentes computadores:
- send: um processo envia uma mensagem para outro.
- receiver:um processo fica aguardando para receber uma mensagem.
Marshalling
Ou também chamado de Serialization ou empacotamento, é o processo de transformar um objetivo/estrutura de dados em memória em um formato que pode ser transmitido pela rede, um formato adequado para a troca de mensagens. Sistemas distribuídos são classificados como abertos se utilizam métodos de marshaling públicos/conhecidos e não um protocolo proprietário que só funciona no sistema. (Exemplo: REST API com JSON, um texto padronizado, amplamente usado ⟶ SD aberto. No entanto, Oracle DB se comunica com um binário proprietário ⟶ SD fechado).
RPC - Remote Procedural Call
É um protocolo de comunicação em sistemas distribuídos. Chama uma função remota de forma que parece ser local (transparência de acesso). O marshalling (empacotamento) e unmarshalling (desempacotamento) de dados através de componentes chamados stubs.
Stub
São trechos de códigos gerados automaticamente pelo compilador que servem como proxy/middleware entre a comunicação via RPC. Dependendo da tecnologia, ele pode:
- ser gerado automaticamente (ex:
protocdo gRPC,rmicdo Java RMI); - ou vir pronto numa biblioteca (ex: SDKs de APIs REST).
Client Stub
Para o programa cliente, ele se parece exatamente com o procedimento real. No entanto, sua função é "capturar" a chamada, empacotar (marshalling) os argumentos em uma mensagem e enviá-la para o servidor.
Server Stub
Recebe a mensagem do cliente, desempacota (unmarshalling) os argumentos, chama o procedimento local real e, quando a execução termina, empacota o resultado para enviá-lo de volta.
Semânticas
Em uma comunicação local, é possível ter certeza que a chamada de um método só acontecerá uma vez. No entanto, no RPC, como a chamada é via rede, o cliente pode enviar uma request e ficar sem response (timeout). Normalmente, o client stub então tenta reenviar a request, fazendo o comportamento de quantas vezes o procedimento remoto será de fato executado ser possivelmente não determinístico.
At Least Once
Nesta semântica, o cliente é persistente: ele continua reenviando a requisição até receber uma resposta do servidor. Garante que o procedimento será executado, mas pode ser executado mais de uma vez. Isso acontece se a resposta do servidor se perder e o cliente, pensando que a requisição falhou, a enviar novamente. É seguro usar esta semântica apenas para operações idempotentes, ou seja, operações que podem ser executadas várias vezes sem causar problemas ou efeitos colaterais. Por exemplo, consultar a hora ou o saldo de uma conta são operações seguras para esta semântica.
At Most Once
Esta semântica garante que o procedimento remoto será executado zero ou uma vez, mas nunca mais do que uma vez. Para implementar isso, cada chamada RPC recebe um identificador único. O servidor mantém um cache com os identificadores das chamadas que já processou. Se ele recebe uma requisição com um ID que já está no cache (uma duplicata), ele não executa o procedimento novamente; em vez disso, simplesmente reenvia o resultado que está armazenado. O Sun RPC utiliza essa abordagem. Evita os efeitos colaterais de múltiplas execuções, mas não garante que a operação será concluída (pode ser executada zero vezes se a mensagem nunca chegar ao servidor, mesmo com as retransmissões).
Exactly Once
Esta é a semântica ideal, que busca replicar o comportamento de uma chamada local, garantindo que o procedimento seja executado exatamente uma vez, sem falhas. Realidade: É a semântica desejada, mas é extremamente difícil de conseguir na prática em um sistema distribuído devido à imprevisibilidade da rede e às possíveis falhas do servidor. Ela é mais adequada para operações não idempotentes, como uma transferência bancária, onde a execução múltipla seria catastrófica.
Java RMI - Remote Method Invocation
É a aplicação do RPC voltada para o Java e ao Paradigma de Orientação a Objetos.
O objetivo é o mesmo: permitir que um programa em uma máquina chame uma função em outra máquina de forma transparente. A grande diferença é que, em vez de chamar um procedimento (uma função solta), o RMI permite que você invoque um método de um objeto que está em outra JVM (Java Virtual Machine).
Interface Remota: Tudo em RMI começa com uma interface Java. Esta interface é o
contrato que define quais métodos podem ser chamados remotamente. Ela precisa obrigatoriamente herdar de java.rmi.Remote, e todos os seus métodos devem declarar que podem lançar uma RemoteException.
Stub (Proxy do Cliente): É um objeto que fica no lado do cliente e se passa pelo objeto real. Quando você chama um método nele, o stub não executa a lógica; ele empacota os parâmetros (marshalling) e os envia pela rede para o servidor.
Skeleton (Esqueleto do Servidor): É a parte que fica no servidor. Ele recebe a mensagem da rede, desempacota os parâmetros (unmarshalling) e chama o método no objeto real que implementa a lógica de negócio.
RMI Registry
O rmiregistry é um Naming Service, análogo ao portmapper do Sun RPC. Ele funciona como um guia telefônico para objetos remotos.
O Servidor faz o rebind: Quando o servidor cria um objeto para ser acessado remotamente (ex: new CalculatorImpl()), ele o registra no Registry com um nome único (ex: "CalculatorService"). Isso é como publicar seu número de telefone no guia.
O Cliente faz o lookup: Quando o cliente quer usar o objeto, ele contata o Registry (que roda em um endereço e porta conhecidos) e pede a referência para o objeto com o nome "CalculatorService". O Registry devolve o stub para o cliente.
Ou seja, o Registry é um daemon que roda em server side e mapeia/ fazem o binding de chamadas de métodos para as implementações de fato. Ou seja, o cliente precisa apenas saber o IP/DNS e a well-known port (1099) do registry. Dessa forma, o próprio serviço do registry irá vincular a chamada que chegou com a execução correta do método.
Sincronização
Manter uma coordenação consistente entre processos ou nós que estão espalhados em diferentes máquinas, principalmente no que diz respeito a tempo, ordem de eventos e acesso a recursos compartilhados.
É preciso garantir exclusão mútua em seções críticas, atomicidade e evitar deadlocks.
Sincronização de Relógios
Em SDs, é inerente que com o tempo os relógios dos nodes fiquem ligeiramente diferentes/ dessincronizados (um adianta, outro atrasa) → Clock Drift. E isso causa uma série de problemas, como:
- Log inconsistente/ordem incorreta de eventos. Exemplo: Servidor A: grava “Pedido recebido” às 10:00:01; Servidor B: grava “Pagamento confirmado” às 09:59:59 → Parece que o pagamento ocorreu antes do pedido.
- Erros em SDs dependentes de tempo. Timeouts, tokens, autenticações temporais (como JWTs com expiração). Algoritmos de consenso (como o Paxos ou Raft) dependem de tempos confiáveis.
Física
Objetivo: Fazer os relógios reais das máquinas terem o mesmo horário
Algoritmo de Cristian
Um cliente quer sincronizar seu relógio com um servidor de tempo confiável na rede. No entanto, a comunicação em rede possui um tempo de latência de ida e de volta. (Ou seja, após a consulta do tempo, é preciso contabilizar essa latência que a rede trás).
- Cliente anota o local current time .
- Logo depois, o cliente envia a request para o servidor.
- O servidor envia o horário como response
- O cliente recebe no horário . Ou seja, o tempo de atraso na rede, de ida e de volta, é . Dessa forma, o cliente assume que o tempo de ida é simétrico ao tempo de volta (o que não é 100% verdade). O tempo correto então seria: .
NTP - Network Time Protocol
Sistema hierárquico de fontes de tempo, chamadas de stratum. A sincronização ocorre geralmente em um modelo cliente-servidor:
- Um cliente NTP envia uma solicitação de tempo a um servidor NTP.
- O servidor responde com o Tempo Universal Coordenado (UTC).
- O cliente calcula a diferença de tempo entre seu relógio e o do servidor, levando em conta o atraso da rede.
- Com base nesse cálculo, o cliente ajusta seu relógio local, corrigindo a diferença de forma gradual (para pequenas correções) ou abrupta (para grandes diferenças).
Lógica
Objetivo: Garantir uma ordem coerente dos eventos, mesmo que os relógios físicos estejam diferentes. Ou seja, o que importa é a sequência de eventos, e não o tempo físico exato que ocorreram.
A notação criada, '→' é chamada de Happens-Before. Ou seja, um evento A → B happens-before (antes) que B.
Algoritmo de Lamport
É a implementação primitiva da relação Happens-Before, introduzindo um relógio lógico em cada node do sistema. Ou seja, cada node possui um contador inteiro que cresce monotonicamente. Se A → B, então C(A) C(B).
- Cada node possui um counter/relógio lógico
- Ao acontecer algum evento no node,
- Sempre que for enviar uma message , além de incrementar (como qualquer evento), enviar pela rede
- Sempre que um node receber , . Ou seja, o local vira ou o recebido ou o local, (incrementando 1 como em qualquer evento que ocorra).
Propriedades: Seja o valor de após incrementos.
- . Ou seja, se é um evento que ocorreu antes de , isso implica que o relógio lógico de é MENOR que o de .
- . Isso significa que NÃO é uma relação de equivalência! (Eles podem estar acontecendo ao mesmo tempo)
- . Pode acontecer de haver dois eventos diferentes com o mesmo timestamp.
Ou seja, existe uma limitação: dado não é possível dizer se ou .
Relógios Lógicos Vetoriais
É uma implementação diferente que o algorítimo de Lamport que consegue superar sua limitação.
- Para cada node, inicializar um vetor zerado, com cada posição sendo um evento possível.
- Ao acontecer algum evento no node, .
- Para enviar uma mensagem, fazer e enviar
- Ao receber, varrer os vetor atual e o recebido e deixar os maiores valores entre eles. Por fim, (incrementar)
Propriedades:
-
se todos os elementos forem iguais
-
se todos elementos forem menores ou iguais
-
se
-
se e nem .
-
. Aconteceu antes
-
. São iguais
-
. (não é possível comparar)
Exclusão Mútua
Em sistemas distribuídos, múltiplos processos executando em máquinas diferentes podem precisar acessar um recurso compartilhado ou uma seção de código específica, chamada de Seção Crítica (SC), de forma exclusiva. A exclusão mútua distribuída é o mecanismo que garante que, no máximo, um processo execute na seção crítica em um determinado momento, prevenindo a inconsistência de dados. Dependem da troca de mensagens para coordenar o acesso.
Algoritmo Centralizado
Esta é a abordagem mais simples. Um processo é eleito como coordenador (PC) e gerencia todo o acesso à seção crítica.
- Se um P deseja entrar na SC, ele deve enviar uma mensagem ao PC
- Se SC estiver livre, PC responde com uma resposta de permissão. Se SC estiver ocupada, PC enfileira a request e não responde.
- Quando um P sai da SC ele enviar uma mensagem avisando ao PC, que envia uma mensagem ao primeiro da fila para liberá-lo.
Vantagem: Simplicidade Desvantagem: Centralizado, potencial gargalo e é um único ponto de falha
Algoritmo de Ricart e Agrawala
Este é um algoritmo totalmente distribuído que usa relógios lógicos de Lamport para ordenar as requisições.
- O P que deseja entrar na SC faz broadcast/requisição para todos os nodes, enviando junto seu relógio lógico (counter).
- P só entra na SC se receber uma response de todos os nodes
- Um processo ao receber uma request de outro querendo usar a SC ele avalia: Se ele não estiver usando a SC, ele envia uma response permitindo. Se estiver dentro da SC ele enfileira a request. E, se estiver aguardando para entrar na SC ele compara seu com o recebido. Se seu for menor que o que chegou ele enfileira, caso contrário envia um OK imediatamente.
Vantagem: É totalmente distribuído, sem um ponto central de falha. Desvantagem: A complexidade de mensagens é alta
Eleição
São procedimentos utilizados em SDs com o objetivo de escolher um único processo para atuar coordenador (PC) dentre um conjunto de processos. A necessidade de uma eleição geralmente surge quando o coordenador atual falha ou deixa de responder. Não são um mecanismo de sincronização direto como os relógios lógicos ou a exclusão mútua, mas estão indiretamente envolvidos para garantir a sincronização.
Algoritmo do Valentão
O nome "valentão" deriva do seu princípio de funcionamento: o processo com o número de identificação (ID) mais alto sempre vence a eleição e "assume o poder".
- Cada P possui um ID associado.
- Todos os Ps conhecem os IDs de todos os outros Ps.
- Os Ps não sabem quais dos seus pares estão ativos ou inativos.
A eleição é iniciado por qualquer processo que perceba que o coordenador não está mais a responder.
- Início da Eleição. Um P que detecta a falha do coordenador envia uma mensagem de
ELEIÇÃOpara todos os Ps que possuem um ID mais alto que o seu. - Respostas e Tomada de Poder. Se P não receber nenhuma resposta dos processos com IDs mais altos (porque estão todos inativos), ele vence a eleição, torna-se o novo coordenador e anuncia a sua vitória a todos os outros processos. Se um processo com ID mais alto responde com uma mensagem de
OK, este processo "valentão" assume a responsabilidade pela eleição. O trabalho de P na eleição termina ali. - Propagação. Qualquer P que recebe uma mensagem
ELEIÇÃOde um P com ID mais baixo, responde comOKpara indicar que está ativo e que vai assumir o controle. Este P receptor então inicia a sua própria eleição, enviando mensagens deELEIÇÃOpara os processos com IDs ainda mais altos que o seu. - Convergência. Eventualmente, o processo ativo com o ID mais alto de todos receberá mensagens de
ELEIÇÃOmas não terá ninguém com ID maior para contactar. Ele vence a eleição.
SOA
Arquitetura: Ideia de estruturar um sistema complexo - seja ele um prédio, uma cidade, um computador ou um software - definindo:
- Componentes
- Relacionamento entre componentes
- Organização dos componentes Dessa foram, a arquitetura de SW seria essa ideia aplicada ao desenvolvimento de SW. Existem diferentes tipo de arquitetura de software e elas NÃO são mutualmente excludentes (ou seja, podem coexistir). Exemplo: Arquitetura em Camadas (Foco em organizar o código), Arquiteturas Distribuídas (que é o caso do SOA).
Service-Oriented Architecture é um modelo de arquitetura de software em que as funcionalidades do sistema são divididas em serviços independentes, que se comunicam entre si por meio de interfaces bem definidas (geralmente via rede).
Em SOA, um serviço é uma unidade de software autônoma, reutilizável e interoperável, que realiza uma função de negócio específica (ex: emitir fatura, validar pagamento, cadastrar cliente).
Esses serviços:
- Podem ser usados por diferentes aplicações;
- São independentes da tecnologia (podem ser Java, .NET, Python, etc.);
- Se comunicam normalmente via protocolos padronizados (como HTTP, SOAP, REST, XML, JSON...).
Baixo acoplamento
Sem SOA, grandes soluções como uma bancária iriam ser monolitos GIGANTES. Com SOA, cada funcionalidade do sistema é quebrado em serviços (Ex: Serviço de pagamento, serviço de login, serviço de pedidos, etc) para refletir as regras de negócio. É a aplicação de SDs para construir uma aplicação.
Atualmente, Microservices "são uma forma de SOA", só que com serviços ainda mais granulares, leves e autônomos.
WEB Service
É uma tecnologia que permite a chamada remota de objetos, fornecendo uma infraestrutura para o desenvolvimento de aplicações distribuídas. Ele permite a criação de pequenos módulos de código reutilizáveis que podem ser combinados para construir aplicações, de forma análoga a um "LEGO".
- Comunicação via protocolos web (como o HTTP)
- Independência de Plataforma/Linguagem
- Autodescritivo: Um serviço web sabe descrever sua própria interface, permitindo que seja descoberto e chamado de forma automática.
- Comunicação via XML: A troca de dados é feita através de XML, um formato de texto para informações estruturadas. (Pelo menos na era clássica)
SOAP
Simple Object Access Protocol é o protocolo de comunicação. Ou seja, um conjunto de regras para nodes em SDs possam trocar mensagens de forma padronizada.
- Baseado em XML. (+ interoperabilidade)
- HTTP como protocolo
- Estrutura definida: Envelope, Header e Body
O SOAP é uma das principais tecnologias para implementar o SOA, junto com (CORBA, RMI e etc).
SOAP é uma solução mais robusta com um contrato rígido (WSDL), sendo confiável, robusto. Ideal para sistemas corporativos complexos/ B2B. E o SOAP suporta outros protocolos alem do HTTP, como o SMTP.
REST
REpresentational State Transfer NÃO é uma arquitetura de SW, é um estilo arquitetural/ não é um protocolo. Uma arquitetura de SW implica em definir componentes, como se organizam e relacionamento entre os componentes.
REST é ainda mais abstrato. É um conjunto de restrições e boas práticas a serem seguidos. → criar web services escaláveis e simples.
Foi criado por um dos criadores do protocolo HTTP.
REST é mais simples, legível (e muitas vezes até ser mais performático devido a cache/proxies HTTPs) que o SOAP.
Restrições
O que REST realmente define. O REST NÃO restringe formatos de representações específicos, contanto que eles sejam compreensíveis pelo cliente e servidor.
Interface Uniforme
A ideia central é de tornar a comunicação auto-descritiva, previsível e fácil de usar. (+Simplicidade, +interoperabilidade, +escalabilidade, +flexibilidade (Não é necessário ter uma adaptação abrupta para usar outro serviço)).
Cada recurso (como um usuário, um pedido, ou um produto) é identificado por um URI único (ex.: https://api.exemplo.com/usuarios/123). Diferentemente de um sistema não REST, que pode usar algo como /executarOperacao?tipo=buscarUsuario&id=123(é preciso saber a operação chamar de antemão). Ou outro exemplo de interface não uniforme seria o SOAP, como:
POST /Servico HTTP/1.1
Content-Type: text/xml
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ObterUsuario>
<IdUsuario>123</IdUsuario>
</ObterUsuario>
</soap:Body>
</soap:Envelope>A request e a response são definidas com uma estrutura rígida definida pelo WSDL - Web Service Definition Language (É o XML que contém informação sobre a interface, a
semântica, e outros detalhes de chamadas a um Serviço Web). Não há URIs específicas para recursos (como /usuarios/123), e a operação é definida como uma função (ObterUsuario) em vez de um método HTTP. O cliente precisa conhecer o contrato WSDL para interagir corretamente.
Recursos manipulados por representações
Ou seja, se comunicam por formatos estruturados e padronizadas (como JSON ou XML) de recursos, manipuladas por métodos HTTP padrão (GET, POST, PUT, DELETE) e identificadas por URIs, com o cabeçalho Content-Type indicando o formato.
Em serviços web não REST (como SOAP, RPC, e outras APIs) o conceito de "representação" não é tão claro ou consistente. Em padrões não REST, as requests possuem formatos que focam NÃO em representar uma entidade, mas sim em ser ARGUMENTOS para um função (e as responses seriam outputs da função).
HATEOAS
Hypermedia as the Engine of Application State é um princípio do REST que permite que o cliente navegue pela API de forma dinâmica, usando hiperlinks fornecidos nas respostas do servidor. Em vez de o cliente conhecer previamente todos os URIs e fluxos da API, o servidor inclui links nas representações dos recursos, indicando as ações possíveis a partir do estado atual do recurso. Isso torna a API mais flexível e desacoplada, pois o cliente pode "descobrir" as próximas etapas sem depender de documentação estática.
Stateless
O servidor não mantém informações necessárias para que o servidor processe a requisição, sem depender de informações armazenadas de requisições anteriores.
RESTful
RESTful é um sistema que segue 100% os princípios REST.
Sistemas de Nomes
Nomes são usados para designar recursos em SDs. Podem ser textuais (ex www.google.com) ou um Identificador de Sistema (ou Endereço) (ex 142.250.218.68).
Binding
É a associar um nome e o recurso.