Dans ce tutoriel, nous allons construire un service de raccourcissement d’URL évolutif en utilisant Node.js et Redis. Ce service tirera parti de la mise en cache distribuée pour traiter efficacement un trafic élevé, réduire la latence et évoluer sans effort. Nous explorerons des concepts clés tels que le hachage cohérent, les stratégies d’invalidation de cache et le partitionnement pour garantir que le système reste rapide et fiable.

À la fin de ce guide, vous disposerez d’un service de raccourcissement d’URL entièrement fonctionnel qui utilise la mise en cache distribuée pour optimiser les performances. Nous créerons également une démo interactive où les utilisateurs pourront entrer des URL et voir des métriques en temps réel telles que les réussites et les échecs de cache.

Ce que vous allez apprendre

  • Comment construire un service de raccourcissement d’URL en utilisant Node.js et Redis.

  • Comment implémenter la mise en cache distribuée pour optimiser les performances.

  • Comprendre le hachage cohérent et les stratégies d’invalidation de cache.

  • Utiliser Docker pour simuler plusieurs instances de Redis pour le partitionnement et l’évolutivité.

Prérequis

Avant de commencer, assurez-vous d’avoir les éléments suivants installés :

  • Node.js (v14 ou supérieur)

  • Redis

  • Docker

  • Connaissances de base en JavaScript, Node.js et Redis.

Table des matières

Présentation du projet

Nous allons créer un service de raccourcisseur d’URL où :

  1. Les utilisateurs peuvent raccourcir de longues URL et récupérer les URL originales.

  2. Le service utilise le cache Redis pour stocker les correspondances entre les URL raccourcies et les URL originales.

  3. Le cache est distribué sur plusieurs instances Redis pour gérer un trafic élevé.

  4. Le système démontrera les hits de cache et les misses en temps réel.

Architecture du système

Pour assurer l’évolutivité et la performance, nous diviserons notre service en les composants suivants :

  1. Serveur API: Gère les demandes de raccourcissement et de récupération des URL.

  2. Couche de mise en cache Redis: Utilise plusieurs instances de Redis pour la mise en cache distribuée.

  3. Docker: Simule un environnement distribué avec plusieurs conteneurs Redis.

Étape 1 : Configuration du projet

Configurons notre projet en initialisant une application Node.js :

mkdir scalable-url-shortener
cd scalable-url-shortener
npm init -y

Maintenant, installez les dépendances nécessaires :

npm install express redis shortid dotenv
  • express: Un framework de serveur web léger.

  • redis: Pour gérer la mise en cache.

  • shortid: Pour générer des ID courts et uniques.

  • dotenv: Pour gérer les variables d’environnement.

Créez un .env fichier à la racine de votre projet :

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

Ces variables définissent les hôtes et ports Redis que nous allons utiliser.

Étape 2 : Configuration des instances Redis

Nous allons utiliser Docker pour simuler un environnement distribué avec plusieurs instances Redis.

Exécutez les commandes suivantes pour démarrer trois conteneurs 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

Cela mettra en place trois instances Redis fonctionnant sur des ports différents. Nous allons utiliser ces instances pour implémenter le hachage cohérent et le sharding.

Étape 3 : Mise en œuvre du service de raccourcissement d’URL

Créons notre fichier d’application principal, 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 })
];

// Fonction de hachage pour distribuer les clés entre les clients Redis
function getRedisClient(key) {
  const hash = key.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
  return redisClients[hash % redisClients.length];
}

// Point de terminaison pour raccourcir une 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}` });
});

// Point de terminaison pour récupérer l'URL d'origine
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}`);
});

Comme vous pouvez le voir dans ce code, nous avons :

  1. Hachage Cohérent :

    • Nous distribuons les clés (URLs raccourcies) entre plusieurs clients Redis en utilisant une simple fonction de hachage.

    • La fonction de hachage garantit que les URLs sont distribuées uniformément entre les instances Redis.

  2. Raccourcissement d’URL :

    • L’endpoint /shorten accepte une longue URL et génère un identifiant court en utilisant la bibliothèque shortid.

    • L’URL raccourcie est stockée dans l’une des instances Redis en utilisant notre fonction de hachage.

  3. Redirection d’URL :

    • L’endpoint /:shortId récupère l’URL originale depuis le cache et redirige l’utilisateur.

    • Si l’URL n’est pas trouvée dans le cache, une réponse 404 est renvoyée.

Étape 4 : Mise en œuvre de l’invalidation du cache

Dans une application réelle, les URL peuvent expirer ou changer au fil du temps. Pour gérer cela, nous devons mettre en œuvre l’invalidation du cache.

Ajout d’une expiration aux URL mises en cache

Modifions notre fichier index.js pour définir une durée d’expiration pour chaque entrée mise en cache :

// Point de terminaison pour raccourcir une URL avec expiration
app.post('/shorten', async (req, res) => {
  const { url, ttl } = req.body; // ttl (time-to-live) est optionnel
  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 par défaut de 1 heure
  res.json({ shortUrl: `http://localhost:${process.env.PORT}/${shortId}` });
});
  • TTL (Time-To-Live) : Nous définissons une durée d’expiration par défaut de 1 heure pour chaque URL raccourcie. Vous pouvez personnaliser le TTL pour chaque URL si nécessaire.

  • Invalidation du cache : Lorsque le TTL expire, l’entrée est automatiquement supprimée du cache.

Étape 5 : Surveillance des métriques de cache

Pour surveiller les succès de cache et les échecs de cache, nous allons ajouter quelques journaux à nos points de terminaison dans 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);
  });
});

Voici ce qui se passe dans ce code:

  • Succès de Cache: Si une URL est trouvée dans le cache, c’est un succès de cache.

  • Échecs de Cache: Si une URL n’est pas trouvée, c’est un échec de cache.

  • Ce journal vous aidera à surveiller les performances de votre cache distribué.

Étape 6 : Tester l’application

  1. Démarrez vos instances Redis:
docker start redis1 redis2 redis3
  1. Exécutez le serveur Node.js:
node index.js
  1. Testez les points de terminaison en utilisant curl ou Postman :

    • Raccourcissez une URL :

        POST http://localhost:3000/shorten
        Corps: { "url": "https://example.com" }
      
    • Accédez à l’URL raccourcie :

        GET http://localhost:3000/{shortId}
      

Conclusion : Ce que vous avez appris

Félicitations ! Vous avez construit avec succès un service de raccourcisseur d’URL évolutif avec mise en cache distribuée en utilisant Node.js et Redis. Tout au long de ce tutoriel, vous avez appris comment :

  1. Implémentez le hachage cohérent pour distribuer les entrées du cache sur plusieurs instances Redis.

  2. Optimisez votre application avec des stratégies d’invalidation du cache pour garder les données à jour.

  3. Utilisez Docker pour simuler un environnement distribué avec plusieurs nœuds Redis.

  4. Surveillez les réussites et échecs du cache pour optimiser les performances.

Étapes suivantes :

  • Ajouter une base de données : Stockez les URL dans une base de données pour une persistance au-delà du cache.

  • Implémenter des analyses : Suivez les comptes de clics et les analyses pour les URL raccourcies.

  • Déployer dans le Cloud : Déployez votre application en utilisant Kubernetes pour l’auto-scaling et la résilience.

Bonne programmation !