Escolar Documentos
Profissional Documentos
Cultura Documentos
Sincronizacao
Sincronizacao
Sincronização
Os processos em um sistema distribuído devem comunicar-se entre si, sempre no intuito de fazer algo útil, ou
seja, a cooperação entre processos é essencial em um sistema distribuído…
Sincronia:
• Ordem de operações
• Espera pela conclusão
• Recurso compartilhado e controle de acesso exclusivo ou simultâneo
UPDATE …
SET …
Usuario = “pedro”,
DataHora = ?
WHERE …
Clock/Timer
Cristal de - n (Registrador)
Quartzo
Oscilações => n – 1
12, 11, 10, …, 0 => Ciclo => Interrupção => Procedimento => RAM CMOS (time++)
Mesmo com relógios em perfeita sincronia, com o passar do tempo a sincronização é perdida devido a
diferenças nas oscilações dos cristais de quartzo.
A hora UTC (Coordinated Universal Time) também sofre ajustes anuais (cerca de 1s) para se manter em
sincronia com a rotação da Terra e seu posicionamento na órbita solar.
Desafio!
code1.c code1.o
code2.c code2.o
code3.c compilação code3.o link executável
…
coden.c coden.o
codek.c é recompilado somente se codek.o for mais antigo, ou seja, data(codek.o) < data(codek.c).
Quando a hora UTC é t, o valor do relógio na máquina p é Cp(t). Idealmente teríamos Cp(t) = t para toda
máquina p e a qualquer instante t, ou seja, sincronização perfeita.
Teoricamente, timers que geram, por exemplo, 60 interrupções por segundo (H = 60), devem gerar em uma
hora (3600 segundos) 3600 * 60 = 216.000 interrupções. Na prática, porém, o valor pode variar entre
aproximadamente 215.998 e 216.002.
Isso implica que difrenças nos valores de clock podem e, normalmente ocorrem, ainda que todos os relógios em
um sistema distribuído estivessem perfeitamente sincronizados. Os algoritmos de sincronização levam em conta
essa defasagem inerente ao hardware e procuram uma maneira de sincronizar os relógios não apenas uma vez,
mas também periodicamente.
A ideia aqui consiste em ter um servidor de tempo (Time Server), de modo que os clientes possam consultar a
hora atual. Contudo, os atrasos na propagação de mensagens fazem com que a hora fornecida se torne
desatualizada e, portanto, não mais correta. A solução adotada pelo protocolo consiste em encontrar uma boa
estimativa para esses atrasos.
Não é possível calcular o tempo que a requisição demorou para ser transmitida nem o tempo que a resposta
levou para chegar. Contudo, o tempo total de ida e volta (Round Trip Time) pode ser calculado como:
O protocolo NTP assume que os atrasos de propagação das mensagens de A até B e de B até A sejam
aproximadamente iguais. Assim sendo, o deslocamento temporal pode ser estimado da seguinte forma:
θ = T2 – (T1 + RTT / 2)
θ = T2 – (T1 + ((T4 – T1) – (T3 – T2)) / 2)
θ = (T2 – T1 + T3 – T4) / 2
Se o relógio de A estiver adiantado (θ < 0), significa que A deve atrasar seu relógio. Como isso pode causar
problemas, tal alteração é feita gradativamente pelo sistema reduzindo-se o número de milisegundos que são
acrescentados ao relógio de A a cada interrupção até que o ajuste seja feito.
O algoritmo de Berkeley
No algoritmo de Berkeley não existe a figura de um servidor, e sim um daemon de tempo que consulta todas as
máquinas na rede periodicamente para saber a hora de cada uma. Com base nas respostas, uma média é obtida e
as máquinas são instruídas a adiantarem ou atrasarem seus relógios.
média = (-10 + 25 + 0) / 3 = 5
O ponto chave a se notar aqui é que o algoritmo não se preocupa em manter cada relógia em sincronia com a
hora UTC, mas apenas manter os horários dos computadores na rede sincronizados entre si.
É importante observar que, mesmo que os relógios estejam atualmente sincronizados com a hora UTC, com o
passar do tempo tal sincronia é perdida em razão de fatores como, por exemplo, o posicionamento do globo na
órbita solar. Fica claro então que o propósito do algoritmo de Berkeley é apenas garantir a sincronia entre os
relógios da rede, sem qualquer preocupação com a hora global. Assim sendo, o algoritmo de Berkeley visa a
mera sincronização dos relógios em âmbito interno.
Relógios lógicos de Lamport
Conceitos iniciais…
Exemplo: a e b são “disparados” por um mesmo processo, sendo que este dispara a e somente
depois dispara b.
a e b são concorrentes, ou seja, não se pode afirmar que a → b ou b → a, pois ambos ocorrem em
processos diferentes que não trocam mensagens.
Objetivo: Garantir que se a → b, então C(a) < C(b). Onde C (clock) é o instante em que ocorre o evento.
Não necessariamente ligado ao relógio real. Tipicamente um contador local.
Exemplo de funcionamento
Os processos P1, P2 e P3 executam em máquinas diferentes, cada uma com seu próprio relógio e com taxas de
pulsos diferentes, ou seja, pequenas variações nos ciclos de clock.
Problema: Em um banco de dados replicado, ocorre uma operação de depósito (Atualização 1 = +100) em uma
conta, cujo saldo atual é 1.000, ao mesmo tempo em que o sistema bancário concede um acréscimo de 1% na
mesma conta, porém em uma réplica diferente (Atualização 2 = +1%).
Resultados possíveis:
De fato, os dois resultados podem ser considerados como corretos, a questão é como garantir que as operações
sejam executadas na mesma ordem em todas as réplicas do banco de dados, ou seja, os dados devem ser
consistentes.
A ideia por trás do multicast totalmente ordenado é garantir que todas as mensagens sejam entregues (à
aplicação = SGBD) na mesma ordem em todos os destinatários.
1. Quando uma atualização é requisitada a um processo, este cria uma mensagem de requisição contendo
sua marca temporal atual e então envia essa mensagem aos outros processos envolvidos na replicação
dos dados: multicast. Além disso, a requisição é incluída na fila local do processo fazendo multicast,
ordenada por sua marca temporal e já marcada como reconhecida (ACK) pelo próprio processo.
2. Quando uma mensagem é recebida por um processo, este a coloca em sua fila local ordenada pela marca
temporal que acompanha a mensagem e já marcada como reconhecida (ACK) pelos processos emissor e
receptor. Logo em seguida, uma mensagem de reconhecimento (ACK) é enviada aos outros processos
do grupo.
Uma mensagem é removida da fila e encaminhada para processamento após a confirmação (ACK) de
recebimento de todos os processos envolvidos e somente se a mensagem for a primeira na fila.
Impasses podem ocorrer quando os contadores lógicos dos processos forem iguais durante duas requisições
simultâneas e, neste caso, uma abordagem simples do tipo Pid.contador pode ser utilizada para resolver o
problema de ordem.
What about Alice, Bob, and Carol… :^(
38 37
38
3 6
ACK 36 ACK
39 39
AC
AC
K 36 39
K
36
40
ACK 6 ACK
6
41 41
43 41
Relógios vetoriais
Em um sistema distribuído, os relógios lógicos de Lamport garantem o seguinte: se um evento a ocorre antes de
um evento b, então C(a) < C(b), ou seja, os eventos são totalmente ordenados.
Exemplos:
Com base no exemplo acima, podemos também afirmar o seguinte: se Trec(m1) < Tsend(m3), então m3 foi enviada
após o recebimento de m1, mas isto apenas porque estes são eventos que ocorreram em um mesmo processo: P2.
Neste caso pode-se dizer que há uma relação de causalidade, ou seja, o envio de m3 dependeu, ou teve como
causa, o recebimento de m1. Ou ainda, o que foi recebido em m1 desencadeou (influenciou) a mensagem m3.
Entretanto, não podemos afirmar que, se Trec(m1) < Tsend(m2), então m2 foi enviada após o recebimento de m1, ou
seja, não podemos concluir que há uma relação de causalidade entre as duas mensagens: não podemos afirmar
que m2 só foi enviada por causa do conteúdo de m1.
De fato, pela representação gráfica apresentada, observa-se que o envio de m2 ocorre no mesmo instante (hora
real) que o recebimento de m1. Poderia inclusive ter acontecido um pouco antes (digamos, send(m2) = 19) e
ainda assim a relação temporal Trec(m1) < Tsend(m2) seria mantida, mais uma vez confirmando que não é possível
tirar conclusões de ordem sobre eventos não interligados pela troca direta de mensagens.
Logo, os relógios lógicos de Lamport garantem a ordem total entre eventos que ocorrem em um mesmo
processo ou entre eventos que ocorrem entre processos que se comunicam por mensagem. Ou ainda, se C(a) <
C(b), então não é possível afirmar que o evento a ocorreu antes do evento b, a não ser que ambos os eventos
tenham ocorrido em um mesmo processo ou os eventos sejam o resultado da comunicação entre dois processos.
Análise de m1 considerando-se os relógios lógicos de Lamport…
Trec(m1) = 16
Tsend(m2) = 20
Tsend(m3) = 32
Tsend(m4) = 60
Tsend(m5) = 69
Mas como obter essa relação de causalidade? Como saber se o conteúdo de uma mensagem que foi recebida
influenciou ou poderia ter influenciado (causalidade potencial) no conteúdo de uma mensagem enviada?
Resposta: relógios vetoriais.
Se VC(a) < VC(b), então o evento a precede por causalidade o evento b, ou seja, o evento a pode ter influência
no evento b.
Processo P1: VC1 = [n1, ?, ?] → n1 é o número de eventos ocorridos em P1 (seu relógio lógico local)
Processo P2: VC2 = [?, n2, ?] → n2 é o número de eventos ocorridos em P2 (seu relógio lógico local)
Processo P3: VC3 = [?, ?, n3] → n3 é o número de eventos ocorridos em P3 (seu relógio lógico local)
Notar que cada processo também conhece, ainda que defasado, o relógio lógico dos outros processos. Esse
contador defasado é atualizado sempre que uma mensagem é recebida por um processo, isso porque o relógio
vetorial do processo emissor sempre é enviado com a mensagem:
1. Antes de processar qualquer evento (exemplo: enviar uma mensagem), Pi executa VCi[i] ← VCi[i] + 1.
2. Quando um processo Pi envia uma mensagem m, esta é enviada com uma marca de tempo vetorial,
ts(m), igual ao relógio vetorial de Pi, o qual já teve seu relógio local incrementado na etapa anterior:
ts(m) ← VCi.
3. Ao receber uma mensagem m, Pj ajusta seu próprio vetor fixando VCj[k] ← max{VCj[k], ts(m)[k]} para
cada k. Em seguida executa a primeira etapa e entrega a mensagem à aplicação.
Neste caso, se a marca de tempo de um evento a for ts(a), então ts(a)[i] – 1 é o número de eventos ocorridos em
Pi que precedem a por causalidade.
Assim sendo, quando Pj recebe uma mensagem de Pi com marca de tempo ts(m), ele sabe o número de eventos
que ocorreram em Pi e que precedem por causalidade o envio de m. Porém, o mais importante é que Pj também
é informado de quantos eventos ocorreram em outros processos, antes de Pi enviar a mensagem m. Em outras
palavras, a marca de tempo ts(m) informa ao receptor quantos eventos ocorreram em outros processos antes do
envio de m e dos quais m pode depender por causalidade.
Voltemos ao nosso exemplo inicial, desta vez utilizando relógios vetoriais…
VC(rec(m1)) = [2, 1, 0]
VC(send(m2)) = [0, 0, 1]
VC(send(m3)) = [2, 4, 1]
VC(send(m4)) = [2, 4, 3]
VC(send(m5)) = [2, 6, 3]
A relação VC(e1) < VC(e2) implica que marca temporal do evento e1 é menor ou igual a marca temporal do
evento e2 (para cada posição do vetor).
https://levelup.gitconnected.com/distributed-systems-physical-logical-and-vector-clocks-7ca989f5f780
https://www.youtube.com/channel/UCekZUWSJkX9kHuvfPbt_gvg (Professora Lindsey Kuper)
Exclusão mútua
Quando um processo obtém acesso a um recurso, os outros processos são impedidos de acessar o recurso até
que este seja liberado pelo processo que o detém. A exclusão mútua é uma forma simples de se impedir que um
recurso seja levado a um estado inconsistente.
Em um sistema não distribuído, essa é uma questão fácil de se resolver, mas em sistemas distribuídos a
exclusão mútua requer uma análise mais aprofundada.
O processo que detém o token pode, se desejar, acessar o recurso ou simplesmente passar o token adiante.
Vantagens:
Desvantagem:
Se o processo que detém o token falha, um “complicado” procedimento para disponibilizar um novo token deve
ter início e, principalmente, esse token deve ser único.
Exemplo: Algoritmo Token Ring – Um anel lógico é contruído em software e a cada processo é designada uma
posição no anel.
2. Soluções baseadas em solicitação de permissão
Algoritmo centralizado: O processo que deseja acesso a um recurso compartilhado deve requisitar ao
coordenador (servidor responsável pelo controle de acesso ao recurso) o recurso desejado.
Vantagens:
- Simples de implementar.
- Justo, pois as permissões são concedidas na ordem em que as requisições foram recebidas.
- Não há inanição, ou seja, nenhum processo espera para sempre.
Desvantagens:
Algoritmo descentralizado: O processo que deseja acesso a um recurso compartilhado deve enviar um pedido
de acesso a todos os coordenadores responsáveis pelo recurso. Supondo n coordenadores, o requisitante espera
apenas por uma resposta majoritária m: m > n / 2.
Vantagens:
Desvantagens:
Algoritmo distribuído: Os relógios lógicos de Lamport podem ser utilizados para se obter exclusão mútua por
meio das marcas de tempo da seguinte forma…
Quando um processo quer acessar um recurso compartilhado, este envia uma mensagem a todos os processos
na rede (inclusive ele mesmo, conceitualmente) contendo o nome do recurso desejado, seu número de processo
e sua marca de tempo atual. Ao receber a mensagem de requisição, cada processo receptor age da seguinte
forma:
1. Se o receptor não estiver acessando o recurso e também não tiver interesse em acessá-lo, então
simplesmente devolve uma mensagem OK ao remetente.
2. Se o receptor não estiver acessando o recurso, mas tiver interesse em acessá-lo, então ele compara a
marca de tempo que veio junto com a mensagem de requisição com a marca de tempo que ele mesmo
enviou como parte de sua requisição ao recurso. A mais baixa vence. Se a mais baixa for do processo
solicitante, o receptor devolve uma mensagem OK, caso contrário o receptor coloca a requisição na fila
e nada responde ao processo requisitante.
3. Se o receptor já tiver acesso ao recurso, simplesmente não responde. Em vez disso, coloca a requisição
na fila.
O acesso ao recurso é concedido quando um processo requisitante recebe uma mensagem OK (permissão
concedida) de todos os processos na rede. Após utilizar o recurso, o processo envia mensagens OK a todos os
processos requisitantes que estão atualmente em sua fila.
Vantagens:
- Não há deadlock.
- Não há inanição.
Desvantagem:
- Infelizmente, o ponto de falha único foi substituído por n (processos) pontos de falha. Se qualquer processo
falhar, não responderá as requisições e esse silêncio será interpretado (incorretamente) como recusa de
permissão.
É importante destacar também que, se antes com uma abordagem centralizada, era ruim ter que pedir permissão
a um único nó da rede, com uma abordagem distribuída ter que pedir permissão a todos é pior ainda.
Algoritmos de eleição
Dado um conjunto de nós em um sistema distribuído, como eleger um líder?
O que os processos não sabem é, quais estão funcionando e quais estão inativos no momento em que é preciso
escolher um líder. Assim sendo, o problema principal consiste em fazer com que todos os processos concordem
com o líder escolhido, mesmo quando alguns nós estão inativos.
Algoritmo do valentão
Quando um processo P “percebe” que o líder atual não está mais respondendo, P inicia uma eleição:
• P envia uma mensagem “ELEIÇÃO” a todos os processos com ID maior que o seu.
• Se nenhum destinatário responder, P vence a eleição e se torna o líder.
• Se um processo com ID maior responder, P desiste da eleição e o processo destinatário toma o poder.
Quando uma mensagem “ELEIÇÃO” chega a um destinatário, este responde com uma mensagem “OK”
informando ao processo P que ele irá assumir o controle (Valentão) iniciando sua própria candidatura, caso
ainda não o tenha feito.
O processo que vence a eleição comunica a todos os nós que agora ele é o líder.
Algoritmo do anel
Quando um processo P “percebe” que o líder atual não está mais respondendo, P inicia uma eleição:
• P envia uma mensagem “ELEIÇÃO + [PID]” a seu sucessor. Caso o sucessor não esteja ativo, P submete
a mensagem ao sucessor seguinte na cadeia.
• Cada nó que recebe a mensagem adiciona a si mesmo como candidato inserindo seu próprio ID na
mensagem: “ELEIÇÃO + [PID, QID, …]”.
• Quando o nó que iniciou a eleição (P) recebe a mensagem de volta após um ciclo, este reconhece seu
próprio ID na cadeia e envia novamente uma mensagem, desta vez passando apenas pelos sucessores
identificados na cadeia e comunicando o novo líder eleito: o maior ID presente no ciclo.