Sistemas Distribuídos · Tanenbaum & Van Steen · 2ª ed. 1 / 10

Capítulo 2 · Andrew S. Tanenbaum & Maarten Van Steen

Arquiteturas
de Sistemas

Como organizar os componentes de software em sistemas distribuídos — estilos arquitetônicos, modelos cliente–servidor e arquiteturas peer-to-peer.

Estilos Arquitetônicos Cliente–Servidor Peer-to-Peer Middleware
Sistemas Distribuídos: Princípios e Paradigmas 2ª Edição · Pearson Prentice Hall
CAP. 2
Seção 2.1

O que é Arquitetura de Software?


Uma arquitetura de software diz como os vários componentes de software devem ser organizados e como devem interagir.

A organização de sistemas distribuídos trata, em grande parte, dos componentes de software que constituem o sistema. Mas há uma distinção fundamental a fazer:

🔍

Organização lógica (arquitetura de software) ≠ realização física (arquitetura de sistema — onde os componentes são colocados nas máquinas reais).

Tanenbaum & Van Steen, Cap. 2, p. 20

Blocos Fundamentais

Componente

Unidade modular com interfaces bem definidas, substituível dentro de seu ambiente (OMG, 2004b).

Conector

Mecanismo mediador da comunicação ou cooperação entre componentes — chamadas RPC, passagem de mensagem, fluxos de dados.

Configuração

Topologia que descreve como componentes e conectores estão ligados para formar o sistema completo.

Estilo Arquitetônico

Formulado em termos de componentes, conectores, dados trocados e como esses elementos formam um sistema.

⚠️

A especificação final de uma arquitetura de software é chamada arquitetura de sistema — ela vincula decisões lógicas a máquinas físicas.

Seção 2.1

Os 4 Estilos Arquitetônicos


Tanenbaum identifica quatro estilos principais para organizar sistemas distribuídos:

  • 1
    Em camadas — componentes organizados em hierarquia; camada Li só chama camada Li-1. Requisições descem, respostas sobem.
  • 2
    Baseada em objetos — cada objeto é um componente, conectados por chamadas de procedimento remoto (RPC). Alinha-se naturalmente ao modelo cliente–servidor.
  • 3
    Centrada em dados — processos se comunicam por um repositório compartilhado (passivo ou ativo). Base dos sistemas distribuídos de arquivos e serviços Web.
  • 4
    Baseada em eventos — processos publicam eventos; o middleware entrega apenas aos assinantes. Componentes referencialmente desacoplados.
Tanenbaum & Van Steen, p. 20–22 · Bass et al. (2003)

Diagramas (Fig. 2.1 e 2.2)

(a) Em Camadas Camada N Camada N-1 Camada 2 Camada 1 Requisição Resposta (b) Objetos Objeto Objeto Objeto Objeto Objeto chamada (c) Baseada em Eventos Componente Componente Barramento de eventos (d) Dados Compartilhados Componente Componente Repositório Fig. 2.1 e 2.2 — Quatro estilos arquitetônicos (adaptado de Tanenbaum)
Seção 2.2.1

Arquiteturas Centralizadas


O modelo mais claro para lidar com a complexidade de sistemas distribuídos é pensar em termos de clientes que requisitam serviços de servidores.

📖

Servidor: processo que implementa um serviço específico.
Cliente: processo que requisita o serviço enviando uma requisição e esperando a resposta.

Esse padrão — também chamado comportamento de requisição–resposta — pode usar protocolo sem conexão (UDP, eficiente) ou com conexão confiável (TCP, robusto).

Operações idempotentes podem ser reenviadas sem risco (ex.: "qual é o saldo?"). Operações não idempotentes não (ex.: "transfira R$ 10.000").

Tanenbaum & Van Steen, p. 22–23 · Fig. 2.3

Modelo em 3 Camadas de Aplicação

  • UI
    Nível de interface de usuário — tudo para interação direta com o usuário: gerenciamento de exibição, entrada de dados.
  • P
    Nível de processamento — contém a lógica de aplicação. É o "cérebro" — no mecanismo de busca, é o gerador de consultas + algoritmo de ordenação.
  • D
    Nível de dados — gerencia os dados persistentes. Geralmente implementado no servidor via SGBD relacional.
(a) UI App + BD Servidor (b) UI App + BD Servidor (c) UI+App BD 2 divisões (d) UI+App+BD Fat client Lado cliente Lado servidor Fig. 2.5 — Alternativas de organização cliente–servidor (a)–(d)
Seção 2.2.2

Arquiteturas Descentralizadas: Peer-to-Peer


Nas arquiteturas cliente–servidor multidivididas, a distribuição dos clientes e servidores em máquinas diferentes é chamada distribuição vertical. Em contraste, o modelo P2P explora a distribuição horizontal:

Os processos que constituem um sistema peer-to-peer são todos iguais: cada processo pode agir como cliente e servidor ao mesmo tempo (comportamento de servente).

Os sistemas P2P se organizam em torno de uma rede de sobreposição — nós são os processos e as arestas representam os canais de comunicação TCP possíveis.

P2P Estruturada (DHT) P2P Não Estruturada Superpares
Tanenbaum & Van Steen, p. 26–31 · Fig. 2.6–2.12

Tipos de Redes de Sobreposição

Estruturada (DHT)

Nós e itens de dados recebem IDs aleatórios de 128–160 bits. Uma DHT mapeia cada chave ao nó responsável deterministicamente. Ex.: Chord organiza nós em anel; busca em O(log N) saltos.

Não Estruturada

Cada nó mantém lista aleatória de vizinhos (visão parcial). Busca por inundação — escalabilidade limitada. Ex.: Gnutella, BitTorrent (fase de busca).

🦅

Superpares (superpeers): nós selecionados que mantêm índices e intermediam a comunicação entre pares comuns. Hibridizam as vantagens de ambos os modelos — usados em CDNs colaborativas e KaZaA.

Chord Superpares Super Pares comuns Fig. 2.7 e 2.12 — Chord (DHT) e organização com superpares
Seção 2.3

Arquiteturas versus Middleware


Middleware existe para esconder a heterogeneidade e prover transparência de distribuição. Mas a transparência total tem custo: desempenho e adaptabilidade sofrem. A solução é tornar o middleware adaptativo.

Interceptores

🔌

Quebram o fluxo usual de controle e permitem executar código específico de aplicação. Exemplo clássico: interceptor de chamada de objeto remoto que verifica localização atual antes de encaminhar.

Permitem que requisições sejam interceptadas, modificadas ou redirecionadas — sem alterar o código do cliente ou do servidor.

Middleware Adaptativo

⚙️

Abordagens para tornar o middleware flexível:

  • 01Separação entre componentes e controle (separation of concerns)
  • 02Reflexão computacional — sistema inspeciona e modifica a si mesmo
  • 03Código específico de componente (component-based design)

Sistemas Autonômicos

🤖

O sistema monitora seu próprio comportamento e toma providências quando necessário — sem intervenção humana direta.

Organizado como realimentações de contato: monitor → analisador → planejador → executor → componentes gerenciados.

IBM: "sistemas autonômicos computacionais"
💡

Ponto central do capítulo: todas as arquiteturas de software vistas — camadas, objetos, eventos, dados compartilhados — visam obter transparência de distribuição em um nível razoável. Como não há solução única, diferentes estilos são combinados conforme o requisito. (Tanenbaum & Van Steen, p. 32)

Visão Comparativa

Estilos em Perspectiva


Estilo Acoplamento Comunicação Escalabilidade Exemplo Prático Trade-off Principal
Em Camadas Médio Síncrona (chamadas diretas) Vertical OSI, TCP/IP, HTTP Rígida hierarquia; difícil pular camadas
Baseada em Objetos Médio-alto RPC / RMI Moderada CORBA, Java RMI Encapsulamento x overhead de rede
Centrada em Dados Baixo Leitura/escrita em repositório Alta (BD distribuído) NFS, sistemas Web Repositório pode ser gargalo
Baseada em Eventos Muito baixo Assíncrona (pub/sub) Muito alta Kafka, sistemas IoT Depuração difícil; ordem de eventos
Cliente–Servidor Alto Requisição–resposta Limitada (servidor único) Web clássica, SSH Ponto único de falha no servidor
Peer-to-Peer Muito baixo Simétrica (servente) Horizontal BitTorrent, Bitcoin Consistência e descoberta complexas
📌

Na prática, sistemas distribuídos modernos combinam múltiplos estilos: um serviço pode usar camadas internamente, publicar eventos para outros serviços (pub/sub) e expor uma API REST no modelo cliente–servidor.

Atividade Prática

Implementando o Modelo Cliente–Servidor em Java

1

Objetivo

Implementar o comportamento de requisição–resposta descrito por Tanenbaum (Fig. 2.3) usando Sockets TCP em Java. O servidor fica aguardando conexões; o cliente envia uma string e recebe a resposta em maiúsculas.

JAVA — Servidor
// ServidorEcho.java — Lado servidor
import java.net.*;
import java.io.*;

public class ServidorEcho {
  public static void main(String[] args) throws Exception {
    // 1. Cria socket de escuta na porta 9090
    ServerSocket servidor = new ServerSocket(9090);
    System.out.println("Servidor aguardando na porta 9090...");

    while (true) {
      // 2. Bloqueia até um cliente conectar
      Socket cliente = servidor.accept();
      System.out.println("Cliente conectado: "
          + cliente.getInetAddress());

      // 3. Lê requisição e envia resposta
      BufferedReader entrada = new BufferedReader(
          new InputStreamReader(cliente.getInputStream()));
      PrintWriter saida = new PrintWriter(
          cliente.getOutputStream(), true);

      String msg = entrada.readLine();
      saida.println(msg.toUpperCase()); // processa e responde
      cliente.close();
    }
  }
}
JAVA — Cliente
// ClienteEcho.java — Lado cliente
import java.net.*;
import java.io.*;

public class ClienteEcho {
  public static void main(String[] args) throws Exception {
    // 1. Conecta ao servidor
    Socket socket = new Socket("localhost", 9090);

    PrintWriter saida = new PrintWriter(
        socket.getOutputStream(), true);
    BufferedReader entrada = new BufferedReader(
        new InputStreamReader(socket.getInputStream()));

    // 2. Envia requisição
    saida.println("sistemas distribuídos");

    // 3. Aguarda e exibe resposta
    String resposta = entrada.readLine();
    System.out.println("Resposta do servidor: " + resposta);
    // → SISTEMAS DISTRIBUÍDOS

    socket.close();
  }
}
🔗

Relação com Tanenbaum: este código implementa literalmente a Fig. 2.3 do livro — cliente bloqueia aguardando resultado; servidor fornece o serviço; comunicação via TCP (protocolo orientado a conexão confiável, p. 22).

2

Como executar

javac *.java → abra dois terminais → java ServidorEcho no primeiro → java ClienteEcho no segundo.

Atividade Avançada

Arquitetura Baseada em Eventos — Publicar/Subscrever em Java

Contexto (Tanenbaum, p. 21)

No estilo baseado em eventos, a ideia básica é que processos publiquem eventos após os quais o middleware assegura que somente os processos subscritos os receberão. Implementemos isso sem biblioteca externa.

JAVA — Barramento de Eventos
import java.util.*;

// Representa o "barramento de eventos" (Fig. 2.2a)
public class BusDeEventos {
  // Mapa: tipo de evento → lista de assinantes
  private Map<String, List<Assinante>> assinantes =
      new HashMap<>();

  public void subscrever(String evento, Assinante a) {
    assinantes.computeIfAbsent(evento,
        k -> new ArrayList<>()).add(a);
  }

  public void publicar(String evento, String dados) {
    List<Assinante> lista =
        assinantes.getOrDefault(evento, List.of());
    // Middleware notifica APENAS os subscritos
    for (Assinante a : lista)
      a.notificar(evento, dados);
  }
}

// Interface do componente assinante
interface Assinante {
  void notificar(String evento, String dados);
}
JAVA — Uso do Barramento
public class DemoEventos {
  public static void main(String[] args) {
    BusDeEventos bus = new BusDeEventos();

    // Componente A subscreve "PEDIDO_CRIADO"
    bus.subscrever("PEDIDO_CRIADO", (ev, d) ->
        System.out.println("[Estoque] processando: " + d));

    // Componente B também subscreve o mesmo evento
    bus.subscrever("PEDIDO_CRIADO", (ev, d) ->
        System.out.println("[Pagamento] cobrando: " + d));

    // Produtor publica — não conhece os assinantes!
    bus.publicar("PEDIDO_CRIADO", "Pizza#42");

    /* Saída:
       [Estoque] processando: Pizza#42
       [Pagamento] cobrando: Pizza#42   */
  }
}
🔑

Desacoplamento referencial: o publicador não precisa saber que Estoque e Pagamento existem. Adicionar um novo assinante (ex.: Notificações) não altera o publicador — exatamente o que Tanenbaum chama de componentes referencialmente desacoplados (p. 21).

Resumo — Cap. 2

O que vimos hoje


Estilo Arquitetônico

Formulado em componentes, conectores e dados trocados. Os quatro principais: camadas, objetos, dados compartilhados, eventos.

Cliente–Servidor

Modelo centralizado: servidor implementa o serviço; cliente requisita. Organizado em 3 camadas lógicas: UI, processamento, dados.

Peer-to-Peer

Distribuição horizontal. Todos os nós iguais. DHT (estruturada) vs. aleatória (não estruturada). Superpares como híbrido.

Middleware Adaptativo

Interceptores e reflexão computacional tornam o middleware flexível. Sistemas autonômicos auto-gerenciam sem humano.

📖

Leitura obrigatória: Tanenbaum & Van Steen, Capítulo 2, pp. 20–41. Observe especialmente as Figuras 2.1, 2.3, 2.5, 2.7 e 2.12.

Questões para Reflexão

  • 1
    Por que a transparência de distribuição total é, em geral, indesejável? Dê um exemplo onde esconder a localização prejudica o desempenho. Tanenbaum, p. 32
  • 2
    Explique a diferença entre distribuição vertical e distribuição horizontal. Em qual categoria se enquadra o BitTorrent? Por quê?
  • 3
    Um servidor de banco de dados bancário que reencaminha consultas a outros servidores age como cliente. Isso viola o modelo cliente–servidor? Justifique usando a Fig. 2.6 do livro.
  • 4
    (Lab) Modifique o ServidorEcho para que cada cliente seja atendido em uma thread separada. Como isso altera a escalabilidade?
  • 5
    No sistema Chord, por que a busca por uma chave requer no máximo O(log N) saltos? O que acontece quando um nó abandona o sistema?

Material elaborado com base em: TANENBAUM, A. S.; VAN STEEN, M. Sistemas Distribuídos: Princípios e Paradigmas. 2ª ed. Pearson Prentice Hall, 2007.