Este repositório implementa um sistema bancário distribuído simples, usado como estudo de caso para três algoritmos clássicos de sistemas distribuídos. É implementado em Python, usando sockets TCP e mensagens em JSON para a comunicação entre os nós.
Atividade 2 disciplina MC714 - Sistemas Distribuídos (Unicamp)
Autoras: Gabriela Jacob e Letícia Lopes
Vários processos (nós) compartilham um único saldo bancário e podem, a qualquer momento, pedir para sacar ou depositar. Isso levanta três problemas clássicos de sistemas distribuídos que a atividade pede para resolver:
- Não existe um relógio global: como ordenar eventos (mensagens, operações) que acontecem em processos diferentes?
- O saldo é um recurso compartilhado: como garantir que dois nós não alterem o saldo ao mesmo tempo (exclusão mútua distribuída)?
- Os nós podem cair e voltar a qualquer momento: como o sistema continua funcionando sem travar esperando resposta de um nó que já morreu, e como um nó que entra na rede depois descobre o saldo atual?
Cada nó roda o mesmo script (node.py), com uma thread de servidor (escuta
conexões TCP) e um menu interativo (cliente). Toda comunicação é feita por
mensagens JSON trocadas em conexões TCP. Os três
algoritmos abaixo foram implementados para resolver os problemas listados
acima.
-
Relógio Lógico de Lamport: cada nó mantém um contador (
lamport_clock). A cada envio o contador é incrementado; a cada recebimento, o nó atualiza seu relógio paramax(relógio_local, relógio_recebido) + 1. É esse timestamp que dá uma ordem total (com desempate por ID de nó) a eventos concorrentes. -
Exclusão mútua com Ricart-Agrawala: antes de sacar ou depositar, o nó entra em estado
WANTEDe mandaREQUEST_MUTEX(com seu Lamport timestamp) para todos os nós ativos. Cada nó que recebe o pedido respondeREPLY_MUTEXna hora, a não ser que ele próprio esteja usando o recurso (ou também querendo usá-lo com prioridade maior - timestamp menor, ou timestamp igual e ID menor); nesse caso, guarda o pedido numa fila de espera e só responde depois de terminar sua própria operação. O nó só executa a operação (e faz o broadcast da alteração de saldo) depois de receberREPLY_MUTEXde todos os outros nós ativos. -
Eleição de líder com o Algoritmo do Bully: existe sempre um nó líder, responsável por saber quais nós estão vivos (
self.ativos). O líder mandaPINGperiódico para cada nó ativo, e cada nó mandaPINGperiódico para o líder atual (para detectar a queda do próprio líder). Quando alguém não responde a tempo:- Se foi um nó comum que caiu, o líder remove o nó do array de ativos e avisa todos
os outros nós com um
NODE_DOWN, para que o Ricart-Agrawala pare de esperarREPLY_MUTEXdele. - Se foi o líder que caiu, o nó que percebeu dispara uma eleição
(
ELECTION/ELECTION_OK/COORDINATOR), seguindo o algoritmo clássico: desafia os nós de ID maior, e quem não é desafiado com sucesso por ninguém se autoproclama líder. - Quando um nó novo entra na rede (ou um nó reinicia depois de cair), ele
manda um
JOIN_REQUESTde broadcast; o líder responde comJOIN_ACCEPTcontendo o saldo e o estado atuais, e avisa os demais nós ativos com umNEW_NODE.
- Se foi um nó comum que caiu, o líder remove o nó do array de ativos e avisa todos
os outros nós com um
config.py: mapeamento de ID de nó para IP e porta.node.py: implementação completa do nó (servidor, cliente, Lamport, Ricart-Agrawala e Bully).teste_concorrencia.py: script auxiliar que dispara duas requisições de mutex simultâneas (saque no Nó 1 e depósito no Nó 2) para demonstrar a disputa do Ricart-Agrawala.docker-compose.yamleDockerfile: constroem os nós da rede. Cada nó roda em um contêiner isolado dentro de uma rede virtual (bank_net), utilizando o DNS interno do Docker para resolução de nomes (node1,node2,node3).
Nota: é necessário ter o Docker e Docker Compose instalados.
1. Clone o repositório e entre na pasta do projeto:
git clone <url-do-repositorio>
cd MC714-atividade-22. Inicializando a Rede
Para construir as imagens e subir os três nós do banco distribuído em background, execute na raiz do projeto:
docker compose up --buildOs nós iniciarão automaticamente, realizarão o procedimento de JOIN e disputarão a eleição inicial do líder usando o algoritmo do Bully.
3. Interagindo com o Menu
Você pode se acoplar (attach) ao terminal de qualquer nó para interagir com o menu do banco:
# Para acessar o menu do Nó 1
docker attach node1O menu interativo possui a seguinte estrutura:
--- MENU ---
1. Ver saldo
2. Sacar
3. Depositar
4. Sair
5. Ver status da rede (líder e nós ativos)
4. Monitorando os Logs
Para acompanhar as mensagens em tempo real trocadas pelos algoritmos, abra outro terminal e execute:
docker compose logs -f1. Algoritmo de Exclusão Mútua (Ricart-Agrawala):
O container tester roda o script teste_concorrência.py internamente. Esse script espera alguns segundos (para a eleição do líder convergir) e então dispara um saque no Nó 1 e um depósito no Nó 2 ao mesmo tempo. Acompanhe os logs dos 3 nós: o de menor ID vence a disputa e os demais ficam retidos na fila até ele liberar o mutex; ao final, o saldo deve convergir para o mesmo valor nos 3 nós.
2. Algoritmo de Eleição (Bully):
-
Descubra quem é o líder atual consultando o status da rede (Opção 5 do menu de qualquer nó) ou olhando os logs.
-
Derrube o contêiner do nó líder simulando uma queda abrupta de hardware:
# Supondo que o líder atual seja o Nó 3 docker compose stop node3 -
Abra o terminal de logs (docker compose logs -f) ou acesse o menu de um dos nós restantes (node1 ou node2). Você observará a detecção da falha via heartbeat e a eleição de um novo líder.
-
Para testar a reentrada, suba o nó antigo novamente, observe-o reentrar na rede recebendo o saldo atual do líder:
docker compose start node3