Algoritmos Distribuídos
MAC0219/5742
Alfredo Goldman
Algoritmos Distribuídos
Sem memória compartilhada → Troca de mensagens
Suposições:
Primitivas
Mas, na prática em comunicação baseada em TCP
Bakery distribuído
Implementação com dois processos por nó:
int myNum = 0
set of deferred nodes ← empty set
Bakery (Main)
loop forever� non critical section� myNum = choose a number� for all other nodes N� send( request, N, myID, myNum)� await replies from all other nodes� critical section� for all nodes N in deferred� remove N from deferred� send( reply, N, myID)�
Bakery (receive)
int source, requestedNum�loop forever� receive( request, source, requestNum)� if (requestNum < myNum)� send( reply, source, myID)� else� add source to deferred
Problemas
Bakery distribuído (Ricart-Agrawala)
int myNum = 0
set of deferred nodes ← empty set
int highestNum = 0
boolean requestCS = false
Ricart-Agrawala (Main)
loop forever� non critical section� requestCS = true� myNum = highestNum + 1� for all other nodes N� send( request, N, myID, myNum)� await replies from all other nodes� critical section� for all nodes N in deferred� remove N from deferred� send( reply, N, myID)�
Ricart - Agrawala (receive)
int source, requestedNum�loop forever� receive( request, source, requestNum)� if (!requestedCS or requestNum < myNum)� send( reply, source, myID)� else� add source to deferred
Limitações
Pode ser ineficiente com um grande número de nós
Não se beneficia da ausência de contenção
Proposta alternativa:
Usar um token
exclusão mútua é automática
como evitar deadlock e starvation?
Vetores distribuídos: requested e granted
Token
boolean haveToken = true no nó 0, false nos outros
int array[NODES] requested = [0,..., 0]
int array[NODES] granted = [0,...,0]
int myNum = 0
boolean inCS = false
(três processos)
sendToken
if exists N such that requested[N] > granted[N]
for some such N
send( token, N, granted)
haveToken = false
Main
loop forever� non-critical section� if !haveToken � myNum = myNum + 1� for all other nodes N� send(request, N, myID, myNum)� receive( token, granted)� haveToken = true� inCS = true� critical section� granted[myID] = myNum� inCS = false� sendToken
Receive
int source, reqNum
loop forever� receive(request, source, reqNum)� requested[source] = max( requested[ source], reqNum)� if haveToken and !inCS� sendToken�
Propriedades Globais em Sistemas Distribuídos
Não há a possibilidade de tirar uma foto instantânea
Mensagens levam um tempo > 0
Mas, dá para garantir consistência
Primeiro problema
Finalização distribuída
Simples com memória compartilhada
Fácil com um coordenador
Para complicar vamos imaginar que há uma topologia
Ok, ela é conexa :)
Motivação um nó começa mandando mensagens para os seus vizinhos
Processo recursivo
Quando receber a confirmação de todos que acabou, acabou :)
Nó 1
Nó 2
Nó 3
Nó 4
algoritmo preliminar
inDeficit i[E] - número de mensagens recebidas menos respondidas pelo nó i na aresta E
outDeficit i - diferença entre o número de mensagens enviadas nas arestas e respondidas
Algoritmo termina quando
outDeficit da origem chegar a 0
Nó origem
int outDeficit = 0�Computação� for all outgoing edge E� send(message, E, myID)� increment outDeficit� await outDeficit = 0� announce system termination
Receive Signal� receive(signal, source)� decrement outDeficit��
Outros nós
int array[incoming] inDeficit = [0,...,0]�int inDeficit = 0�int outDeficit = 0�send message� send(message, destination, myID)� increment outDeficit
Receive message� receive(message, source)� increment inDeficit[source] and inDeficit��
Outros nós
send signal� when inDeficit > 1 or (inDeficit == 1 and isTerminated and outDeficit == 0)� E = some edge E with inDeficit[E] != 0� send(signal, E, myID)� decrement inDeficit[E] and inDeficit
receive signal� receive(signal, ...)� decrement outDeficit
Pode dar problema… solução árvore geradora
Para evitar estouros, pode se usar números reais e mandar um pedaço��
Snapshots
Como tirar uma foto do sistema?
Onde podem estar as mensagens?
Algoritmo Chandy&Lamport (assume canais FIFO)
Nó 1
Nó 2
m4, m3, m2
ideia: marcadores
Os receptores registram as mensagens em trânsito
Consenso
Fácil se não há falhas
Cada nó manda informações para todos os outros
Os nós usam o mesmo algoritmo e chegam ao mesmo resultado
Dois tipos de falhas
Quebra - Um nó para de mandar mensagens
Falhas Bizantinas - Um nó pode mandar mensagens arbitrárias
Problemas dos generais bizantinos
Mensageiros confiáveis
Não há problemas nos canais
Generais podem ser traidores
Cada general toma uma decisão individual
Atacar ou Recuar
Decisões de consenso são boas
Mas, se apenas um subconjunto dos generais atacar → problema
Tentar ver um pouco da intuição
A
R
A
R
R
A
A
Algoritmo Trivial
planType finalPlan�planType array[generals] plan
plan[myID] = choose Attack or Retreat�for all other general G� send(G, myID, plan[myID])�for all other general G� receive(G, plan[G])
finalPlan = majority(plan)
Exemplo com três generais
1 deles falha
1 A
2 R
3 A
Solução
Não estamos usando o fato que generais são leais
Ideia: Adicionar rodadas, os leais sempre mandarão a mesma coisa
Algoritmo duas fases 1/2
planType finalPlan�planType array[generals] plan�planType array[generals, generals] reportedPlan�planType array[generals] majorityPlan
plan[myID] = choose Attack or Retreat�for all other generals G� send(G, myID, plan[myID])�for all other generals G� receive(G, plan[G])
Algoritmo duas fases 2/2
for all other generals G� for all other generals G' except G� send(G', myID, G, plan[G])�for all other generals G� for all other generals G' except G� receive(G, G', reportedPlan[G, G'])
for all other generals G� majorityPlan[G] = majority(plan[G] U reportedPlan[*, G])�majorityPlan[myID] = plan[myID]�finalPlan = majority(majorityPlan)
ideia usar árvores de conhecimento
Uma representação virtual sobre o que se sabe
Ele me disse que …
Funciona para falhas bizantinas
Para t traidores o número de generais deve ser ao menos 3 t + 1
t rodadas são necessárias
mas o número de mensagens é enorme
(n - 1) + (n - 1)* (n - 2) + (n - 2) * (n - 3) + ...
Dá para simplificar para o caso de falhas?
Inundação
planType finalPlan�set of planType plan = choose Attack or Retreat�set of planType receivedPlan
do t + 1 times� for all other generals G� send(G, plan)� for all other generals G� receive(receivedPlan)� plan = plan U receivedPlan� finalPlan = majorityPlan
Sim, tem muito mais...
King, que manda menos mensagens, mas tolera menos traidores
Paxos, para ambientes assíncronos não bizantinos
BFT, para ambientes assíncronos bizantinos
POW, para ambientes assíncronos com blockchain