Escrito el 04/09/2024

Proyecto: Sistema Distribuido

Idea: sistema totalmente distribuido para aprender cómo funciona un sistema sin una central.

Descripción

Esta aplicación que maneja nodos en una red distribuida. Permite registrar nodos, sincronizar la lista de nodos conocidos, y verificar la actividad de un nodo. Además, ofrece información sobre el nodo actual y la red de nodos conectados. Usa una lista de nodos semilla para iniciar la conexión y actualizar periódicamente los nodos conocidos. La comunicación con otros nodos se hace a través de solicitudes HTTP, con endpoints para obtener, agregar y sincronizar nodos.

Diagrama

graph TD
    A[Seed Node] --> B[Node 1]
    A --> C[Node 2]
    B --> D[Node 3]
    B --> E[Node 4]
    C --> F[Node 5]
    C --> G[Node 6]
    D --> H[Node 7]

    %% Actor externo solicitando la lista de nodos a Node 3

    D -->|Devuelve Lista de Nodos| X
    F -->|Devuelve Lista de Nodos| X

¿Qué debe hacer un sistema?

  • Debe implementar todo lo necesario para descubrir los demás componentes (Nodos) en la red.
  • Debe poder listar todos los Nodos en la red.

Definición

Interacción con la base de datos

Node
DB
Application
saveKnownNodes(nodes)
listKnownNodes()
listSeedNodes()
saveSeeds(seeds)
saveLocalInfo(url, extra)
localInfo()
listOtherNodes()

Seed

Network Step 1
Add me
Node 1

Add me


Node 1
Seed

Add me


Node 1

Add me


Node 1

Add me


Node 1
Add me
Node 1

Add me


Node 1
Network Step 2
Node list
Node 1
Node list
Node 1
Node list
Seed
Node list
Node 1
Node list
Node 1
Node list
Node 1
Node 1
Node list
Node 1

Inicio

STARTUP
node url
node url
node url
Register a Node
....
Seed N
Seed 2
Seed 1
Syncronization
Initialization

Sincronización

Every 30 
minutes
node url
node url
Send local name to the known nodes
known nodes
Node 1
known nodes
Node 2
known nodes
Node N
....
node url
Update known nodes
END
Syncronization

Ejemplo en Javascript

// Importar dependencias
import express from "express";
import fetch from "node-fetch";

// Inicializar la aplicación Express
const app = express();
app.use(express.json());

// Url del Nodo (la forma de identificarlo)
const localNodeUrl = process.env.NODE_URL;
// Nombre del Nodo (la forma de identificarlo)
const nodeName = process.env.NODE_NAME ?? "unknown name";
// Iniciar el servidor en el puerto 3000
const port = process.env.PORT || 3000;
// Nodo semilla
const seedNodeUrls = process.env.SEED_NODE_URLS || "";

// Lista en memoria de nodos conocidos
let knownNodes = seedNodeUrls ? [...seedNodeUrls.split(","), localNodeUrl] : [localNodeUrl];

/************ BEGIN internal functions ************/

// Función que devuelve toda la info de este nodo
function myInfo() {
  return {
    name: nodeName,
    version: "0.0.1",
  };
}

// Función para registrar el nodo en el semilla
async function registerNode() {
  knownNodes
    .filter((n) => n !== localNodeUrl)
    .forEach(async (seedUrl) => {
      try {
        const response = await fetch(`${seedUrl}/nodes`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ nodeUrl: localNodeUrl }),
        });
        if (response.ok) {
          console.log(`Nodo registrado: ${localNodeUrl}`);
        } else {
          console.error("Error al registrar el nodo:", response.statusText);
        }
      } catch (error) {
        console.error("Error al registrar el nodo:", error);
      }
    });
}

function syncNodes() {
  console.log("Sincronizando...");
  console.log("Nodos conocidos:", knownNodes);
  knownNodes
    .filter((n) => n !== localNodeUrl)
    .forEach(async (nodeUrl) => {
      try {
        console.log("Sincronizando con el nodo", nodeUrl);
        const response = await fetch(`${nodeUrl}/nodes`);
        const newNodes = await response.json();
        knownNodes = [...new Set([...knownNodes, ...newNodes])];
      } catch (error) {
        console.error(`Error sincronizando con el nodo ${nodeUrl}: ${error.message}`);
        if (knownNodes.lengh !== 0) {
          knownNodes = [localNodeUrl];
        }
      }
    });
}
/************ END internal functions ************/

/************ BEGIN endpoints ************/

// Endpoint GET /nodes - Retorna la lista de nodos
app.get("/nodes", (req, res) => {
  res.json(knownNodes);
});

// Endpoint GET /sync para realizar la actualización de nodos conocidos
app.get("/sync", async (req, res) => {
  console.log("Sincronizando...");
  console.log("Nodos conocidos:", knownNodes);
  knownNodes
    .filter((n) => n !== localNodeUrl)
    .forEach(async (nodeUrl) => {
      try {
        console.log("Sincronizando con el nodo", nodeUrl);
        const response = await fetch(`${nodeUrl}/nodes`);
        const newNodes = await response.json();
        knownNodes = [...new Set([...knownNodes, ...newNodes])];
      } catch (error) {
        console.error(`Error sincronizando con el nodo ${nodeUrl}: ${error.message}`);
        if (knownNodes.lengh !== 0) {
          knownNodes = [...knownNodes.filter((x) => x !== localNodeUrl)];
        }
      }
    });
  res.json({ status: "ok", knownNodes });
});

// Endpoint POST /nodes - Registra un nuevo nodo
app.post("/nodes", (req, res) => {
  const { nodeUrl } = req.body;
  console.log("Agregando el nodo", nodeUrl);
  knownNodes = [...new Set([...knownNodes, nodeUrl])];
  console.log("Nodos conocidos:", knownNodes);
  res.status(201).json({ message: "Nodo agregado", knownNodes });
});

// Endpoint GET /ping - Verifica si el nodo está activo
app.get("/ping", (req, res) => {
  res.json({ message: "Nodo activo" });
});

app.get("/info", (req, res) => {
  res.json(myInfo());
});

app.get("/network", async (req, res) => {
  var rst = [myInfo()];
  var otherNodes = knownNodes.filter((n) => n !== localNodeUrl);
  for (let i = 0; i < otherNodes.length; i++) {
    const response = await fetch(`${otherNodes[i]}/info`);
    rst = [...rst, await response.json()];
  }
  res.json(rst);
});

/************ END endpoints ************/

app.listen(port, async () => {
  console.log(`Servidor ${nodeName} iniciado en ${localNodeUrl}`);
  console.log(`Seeds registrados ${seedNodeUrls}`);
  // Registrar el nodo en el seed después de que el servidor se inicie
  await registerNode();
  syncNodes(); // Conectar al nodo semilla al iniciar
});
Topología Descripción Este Código
Full Mesh Todos los nodos están directamente conectados entre sí. ❌ No es Full Mesh
Partial Mesh Algunos nodos están directamente conectados, otros no. ✅ Sí es Partial Mesh
Ad-hoc Mesh Los nodos se conectan dinámicamente y de manera descentralizada. ✅ Sí es Ad-hoc Mesh