Sistemas Distribuídos Walfredo Cirne & Fubica Brasileiro http://walfredo.dsc.ufcg.edu.br/cursos/2005/distsis20052 Aula 4: Mais Conceitos Básicos As figuras que aparecem nesses slides são de Veríssimo&Rodrigues, reproduzidas com o consentimento dos mesmos. Sincronia • O que significa sincronia? – envio de mensagens blocking non-blocking – iteração same-time different-time – hardware clock-driven – limite superior para execução de ações Graus de Sincronia • Assíncrono – sem limites de tempo para ações • Síncrono – ações têm limites de tempo conhecidos • Parcialmente síncrono – ações têm limites de tempo para acontecer, mas estes são desconhecidos e/ou válidos somente parte do tempo Qual a gente escolhe? • Assíncrono – fácil de implementar, mas pouco útil • Síncrono – muito poderoso, mas conflita com escala, abertura, interatividade, bom uso dos recursos – usado em sistemas embarcados • Parcialmente síncrono – muito usado em computação de uso geral – semântica tipicamente não é claramente definida Ordenação • Fundamental para que se possa raciocinar sobre sistemas distribuídos • A priori A posteriori Precedência [Lamport] • Se a e b são eventos no mesmo processo e a precede b, então a b • Se a é o envio da mensagem m e b é a recepção da mensagem m, então a b • Se a b e b c, então a c Ordenação de Mensagens • FIFO: entre quaisquer dois processos, as mensagens são entregues em ordem • Causal: se envio(m) envio(n), então entrega(m) entrega(n) • Total: Quaisquer duas mensagens entregues a quaisquer dois processos são entregues na mesma ordem – Fundamental para replicação ativa • Causal e total são ortogonais!?! Implementando FIFO Limitações do FIFO Implementando Ordenação Causal • Toda mensagem enviada é armazenada em past • Toda mensagem recebida é armazenada em past • Toda mensagem m vai acompanhada de past • Na recepção de m, checa-se primeiro past e caso haja mensagens que ainda não foram entregues, estas são entregues antes que m Implementação por Relógios Lógicos • • • • • Cada processo mantém o inteiro lclock Toda mensagem enviada carrega lclock O envio de uma mensagem incrementa lclock Mensagens são enviadas usando canais FIFO Quando a mensagem m, com lclockm, é entregue, fazemos lclock = max(lclock, lclockm) • Mensagens só são entregues quando se recebe mensagens de todos os outros processos com lclock maior Refinamento: Relógios Vetoriais • Assumindo que todas as mensagens são broadcast • Se unicast ou multicast são permitidos, necessita-se de uma matriz no lugar do vetor Canais Ocultos Relembrando: Exclusão Mútua • Semáforo S: variável não-negativa inteira que só pode se modificada pelos procedimentos up() e down() • down(S) se S > 0: decremente S senão: bloqueia esperando up(S) • up(S) se há alguém bloqueado: desbloqueie senão: incremente S Usando Semáforos (* exclusão mútua *) var mutex: semaphore := 1; thread P1; statement X down(mutex); statement Y up(mutex); statement Z end P1; thread P2; statement A; down(mutex); statement B; up(mutex); statement C; end P2; Exclusão Mútua via Servidor de Lock • Necessário para coordenar processos e evitar “colisões” • Servidor de lock – Interessados em entrar na região crítica mandam mensagem LOCK para o servidor – O servidor só responde com LOCK-GRANTED para um processo – Processo envia UNLOCK ao sair da região • Servidor é ponto único de falhas e possível gargalo de performance • Falha no cliente também é problema Exclusão Mútua Distribuída • Usando um protocolo que garante entrega ordenada total, podemos replicar o servidor de lock em todos os processos do sistema • Podemos também eleger dinamicamente um líder para função de servidor de lock – Precisamos também pensar em como passar estado entre lideres, ou então resetar o sistema quando há troca de líder Eleição de Líder • Note a necessidade de sincronia para detecção da falha do líder!! Deadlock • Deadlock ocorre quando um processo fica esperando por outro • Para deadlock é necessário ter exclusão mútua, espera circular, obtém-e-espera, não-preempção • Tudo que você conhece deadlock em sistemas concorrentes vale para sistemas distribuídos, com a complicação que você não tem uma visão global do sistema Consistência • Como garantir que alguma propriedade sistêmica é válida? • Ou seja, como garantir que (ou checar se) a execução do sistema é consistente? • Para checar, podemos parar o sistema e montar um estado global – Naturalmente, isso é dispendioso – Protocolos de snapshot distribuído são uma solução bem mais eficiente para o problema Consenso • Problema básico para coordenação de processos • No consenso, cada processo p propõe um valor vp e decide por um valor final fp • As propriedades de consenso são: – Acordo: p,q que decidem: fp = fq – Validade: p: fp = vp – Terminação: Todo processo correto eventualmente decide Consenso em um Sistema sem Falhas Acordo sobre Participantes • Muitas vezes é importante saber quais processos constituem o sistema – Por exemplo, rank e size viabilizam a computação determinística de uma propriedade por todos os processos • Conjunto de participantes = Visão • É fundamental que cada mensagem seja entregue a todos os seus destinatários na mesma visão Troca de Visão Desincronizada Atomic Broadcast • Atomic Broadcast = View Synchrony + Total Order • Atomic Broadcast = Mudança de visão via entrega de mensagem + Total Order Determinismo de Replicas • Atomic broadcast permite implementarmos replicação, desde que as replicas sejam totalmente determinísticas • Mas, como construir replicas perfeitamente determinísticas? • Será que um malloc() é determinístico? Partição do Grupo • Note que detecção de partição precisa ser entregue como uma mensagem!! Concorrência • “A good understanding of the memory consistency model is paramount to building correct programs” • Consistência atômica – todos os acessos são a mesma memória • Consistência seqüencial – equivalente a consistência atômica de alguma execução – indistinguível da consistência atômica se comunicação é somente via memória Exemplo: Double-Checked Locking class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) synchronized(this) { if (helper == null) helper = new Helper(); } return helper; } } Modelo de Memória de Java • Cada thread de Java tem memória local • Dados inexistentes na memória local são copiados da memória global • Eventualmente, dados gravados na memória local são refletidos na memória global • Ao entrar num synchronized, todos os dados da memória local são invalidados • Ao sair de um synchronized, todos os dados gravados são refletidos globalmente Outro Exemplo: Objetos Ativos public class DataRace extends Thread { int a = 0; public DataRace() { this.start(); a = 1; } public void run() { System.out.println(a); } } Objetos Ativos: O problema é sutil public class DataRace extends Thread { protected int a = 0; public DataRace() { a = 1; this.start(); } } public class ExtendRace extends DataRace { public ExtendRace() { super(); a = 2; } } Suporte a Transações • Transações são uma abstração muito poderosa tipicamente implementadas por banco de dados • Transações são ACID – Atomicity: tudo ou nada – Consistency: atualizações levam os dados de um estado consistente para outro estado consistente – Isolation: transações executadas em paralelo não vêem uma a outra – Durability: é um banco de dados, né? Transações Distribuídas: Two-phase Commit