In questo tutorial, costruiremo un servizio di abbreviazione URL scalabile utilizzando Node.js e Redis. Questo servizio sfrutterà la cache distribuita per gestire efficientemente l’alto traffico, ridurre la latenza e scalare senza problemi. Esploreremo concetti chiave come l’hashing consistente, le strategie di invalidazione della cache e lo sharding per garantire che il sistema rimanga veloce e affidabile.
Alla fine di questa guida, avrai un servizio di abbreviazione URL completamente funzionale che utilizza la cache distribuita per ottimizzare le prestazioni. Creeremo anche una demo interattiva in cui gli utenti potranno inserire gli URL e visualizzare metriche in tempo reale come i colpi e gli errori di cache.
Cosa Imparerai
-
Come costruire un servizio di abbreviazione URL utilizzando Node.js e Redis.
-
Come implementare la cache distribuita per ottimizzare le prestazioni.
-
Comprensione dell’hashing consistente e delle strategie di invalidazione della cache.
-
Utilizzo di Docker per simulare più istanze Redis per lo sharding e lo scaling.
Prerequisiti
Prima di iniziare, assicurati di avere installato quanto segue:
-
Node.js (v14 o superiore)
-
Redis
-
Docker
-
Conoscenze di base di JavaScript, Node.js e Redis.
Indice
Panoramica del Progetto
Costruiremo un servizio di accorciamento URL dove:
-
Gli utenti possono accorciare URL lunghi e recuperare gli URL originali.
-
Il servizio utilizza caching Redis per memorizzare le associazioni tra URL accorciati e URL originali.
-
La cache è distribuita su più istanze Redis per gestire alti volumi di traffico.
-
Il sistema mostrerà cache hit e cache miss in tempo reale.
Architettura di Sistema
Per garantire scalabilità e prestazioni, divideremo il nostro servizio nei seguenti componenti:
-
Server API: Gestisce le richieste per accorciare e recuperare URL.
-
Layer di caching Redis: Utilizza più istanze di Redis per la memorizzazione distribuita.
-
Docker: Simula un ambiente distribuito con più contenitori Redis.
Passo 1: Configurazione del Progetto
Configuriamo il nostro progetto inizializzando un’applicazione Node.js:
mkdir scalable-url-shortener
cd scalable-url-shortener
npm init -y
Ora, installiamo le dipendenze necessarie:
npm install express redis shortid dotenv
-
express
: Un framework leggero per server web. -
redis
: Per gestire la cache. -
shortid
: Per generare ID brevi e univoci. -
dotenv
: Per la gestione delle variabili d’ambiente.
Crea un file .env nella radice del tuo progetto:
PORT=3000
REDIS_HOST_1=localhost
REDIS_PORT_1=6379
REDIS_HOST_2=localhost
REDIS_PORT_2=6380
REDIS_HOST_3=localhost
REDIS_PORT_3=6381
Queste variabili definiscono gli host Redis e le porte che utilizzeremo.
Passo 2: Configurazione delle istanze Redis
Utilizzeremo Docker per simulare un ambiente distribuito con più istanze Redis.
Esegui i seguenti comandi per avviare tre container Redis:
docker run -p 6379:6379 --name redis1 -d redis
docker run -p 6380:6379 --name redis2 -d redis
docker run -p 6381:6379 --name redis3 -d redis
Questo imposterà tre istanze Redis in esecuzione su porte diverse. Utilizzeremo queste istanze per implementare hashing consistente e sharding.
Passo 3: Implementazione del servizio di abbreviazione URL
Creiamo il nostro file di applicazione principale, index.js:
require('dotenv').config();
const express = require('express');
const redis = require('redis');
const shortid = require('shortid');
const app = express();
app.use(express.json());
const redisClients = [
redis.createClient({ host: process.env.REDIS_HOST_1, port: process.env.REDIS_PORT_1 }),
redis.createClient({ host: process.env.REDIS_HOST_2, port: process.env.REDIS_PORT_2 }),
redis.createClient({ host: process.env.REDIS_HOST_3, port: process.env.REDIS_PORT_3 })
];
// Funzione hash per distribuire le chiavi tra i client Redis
function getRedisClient(key) {
const hash = key.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
return redisClients[hash % redisClients.length];
}
// Endpoint per abbreviare un URL
app.post('/shorten', async (req, res) => {
const { url } = req.body;
if (!url) return res.status(400).send('URL is required');
const shortId = shortid.generate();
const redisClient = getRedisClient(shortId);
await redisClient.set(shortId, url);
res.json({ shortUrl: `http://localhost:${process.env.PORT}/${shortId}` });
});
// Endpoint per recuperare l'URL originale
app.get('/:shortId', async (req, res) => {
const { shortId } = req.params;
const redisClient = getRedisClient(shortId);
redisClient.get(shortId, (err, url) => {
if (err || !url) {
return res.status(404).send('URL not found');
}
res.redirect(url);
});
});
app.listen(process.env.PORT, () => {
console.log(`Server running on port ${process.env.PORT}`);
});
Come puoi vedere in questo codice, abbiamo:
-
Hashing consistente:
-
Distribuiamo le chiavi (URL abbreviati) tra più client Redis utilizzando una semplice funzione hash.
La funzione hash garantisce che gli URL siano distribuiti uniformemente tra le istanze Redis.
-
-
Abbreviazione URL:
-
Il punto finale /shorten accetta un URL lungo e genera un ID breve utilizzando la libreria shortid.
-
L’URL abbreviato viene memorizzato in una delle istanze di Redis utilizzando la nostra funzione hash.
-
-
Reindirizzamento URL:
-
Il punto finale /:shortId recupera l’URL originale dalla cache e reindirizza l’utente.
-
Se l’URL non viene trovato nella cache, viene restituita una risposta 404.
-
Fase 4: Implementazione dell’invalidamento della cache
In un’applicazione reale, gli URL possono scadere o cambiare nel tempo. Per gestire ciò, è necessario implementare l’invalidamento della cache.
Aggiunta della Scadenza agli URL in Cache
Modifichiamo il nostro file index.js per impostare un tempo di scadenza per ogni voce in cache:
// Punto finale per accorciare un URL con scadenza
app.post('/shorten', async (req, res) => {
const { url, ttl } = req.body; // ttl (time-to-live) è facoltativo
if (!url) return res.status(400).send('URL is required');
const shortId = shortid.generate();
const redisClient = getRedisClient(shortId);
await redisClient.set(shortId, url, 'EX', ttl || 3600); // TTL predefinito di 1 ora
res.json({ shortUrl: `http://localhost:${process.env.PORT}/${shortId}` });
});
-
TTL (Time-To-Live): Impostiamo un tempo di scadenza predefinito di 1 ora per ogni URL abbreviato. È possibile personalizzare il TTL per ogni URL se necessario.
-
Invalidamento della Cache: Quando il TTL scade, l’elemento viene automaticamente rimosso dalla cache.
Fase 5: Monitoraggio delle Metriche della Cache
Per monitorare cache hits e misses, aggiungeremo alcuni log ai nostri endpoint in index.js:
app.get('/:shortId', async (req, res) => {
const { shortId } = req.params;
const redisClient = getRedisClient(shortId);
redisClient.get(shortId, (err, url) => {
if (err || !url) {
console.log(`Cache miss for key: ${shortId}`);
return res.status(404).send('URL not found');
}
console.log(`Cache hit for key: ${shortId}`);
res.redirect(url);
});
});
Ecco cosa sta succedendo in questo codice:
-
Cache Hits: Se un URL viene trovato nella cache, è un cache hit.
-
Cache Misses: Se un URL non viene trovato, è un cache miss.
-
Questo logging ti aiuterà a monitorare le prestazioni della tua cache distribuita.
Passo 6: Test dell’Applicazione
- Avvia le istanze di Redis:
docker start redis1 redis2 redis3
- Esegui il server Node.js:
node index.js
-
Testare gli endpoint utilizzando
curl
o Postman:-
Ridurre un URL:
POST http://localhost:3000/shorten Corpo: "url": "https://example.com" }
-
Accedere all’URL ridotto:
GET http://localhost:3000/{shortId}
-
Conclusione: Cosa hai imparato
Congratulazioni! Hai costruito con successo un servizio di accorciamento URL scalabile con cache distribuita utilizzando Node.js e Redis. Durante questo tutorial, hai imparato come:
-
Implementa hashing consistente per distribuire voci di cache su più istanze di Redis.
-
Ottimizza la tua applicazione con strategie di invalidazione della cache per mantenere i dati aggiornati.
-
Utilizza Docker per simulare un ambiente distribuito con più nodi Redis.
-
Monitora i colpi e gli errori di cache per ottimizzare le prestazioni.
Prossimi passi:
-
Aggiungi un Database: Memorizza gli URL in un database per una persistenza oltre la cache.
-
Implementa Analytics: Tieni traccia dei conteggi dei clic e delle analisi per gli URL accorciati.
-
Effettua il deploy nel Cloud: Distribuisci la tua applicazione utilizzando Kubernetes per il ridimensionamento automatico e la resilienza.
Buon coding!