このチュートリアルでは、Node.jsとRedisを使用してスケーラブルなURL短縮サービスを構築します。このサービスでは分散キャッシュを活用して、高トラフィックを効率的に処理し、遅延を減らし、シームレスにスケーリングします。一貫性のあるハッシング、キャッシュの無効化戦略、およびシャーディングなどの主要な概念を探求し、システムが迅速かつ信頼性の高い状態を維持することを確認します。

このガイドの最後までには、分散キャッシュを使用してパフォーマンスを最適化する完全な機能を備えたURL短縮サービスを持つようになります。また、ユーザーがURLを入力し、キャッシュヒットやミスなどのリアルタイムメトリクスを表示できるインタラクティブなデモも作成します。

学ぶこと

  • Node.js

    Redisを使用してURL短縮サービスを構築する方法。

  • 分散キャッシュを実装してパフォーマンスを最適化する方法。

  • 一貫性のあるハッシングとキャッシュ無効化戦略の理解。

  • シャーディングとスケーリングのために複数のRedisインスタンスをシミュレートするためにDockerを使用する方法。

前提条件

始める前に、次のものがインストールされていることを確認してください:

  • Node.js(v14以上)

  • Redis

  • Docker

  • JavaScript、Node.js、およびRedisの基本知識。

目次

プロジェクト概要

以下のようなURL短縮サービスを構築します:

  1. ユーザーは長いURLを短縮し、元のURLを取得できます。

  2. サービスは短縮URLと元のURLのマッピングを保存するためにRedisキャッシュを使用します。

  3. キャッシュは高トラフィックを処理するために複数のRedisインスタンスに分散されています。

  4. システムはリアルタイムでキャッシュヒットミスを示します。

システムアーキテクチャ

スケーラビリティとパフォーマンスを確保するために、以下のコンポーネントにサービスを分割します:

  1. APIサーバー:URLの短縮と取得リクエストを処理します。

  2. Redisキャッシングレイヤー:分散キャッシングのために複数のRedisインスタンスを使用します。

  3. Docker:複数のRedisコンテナで分散環境をシミュレートします。

ステップ1:プロジェクトのセットアップ

Node.jsアプリケーションを初期化してプロジェクトをセットアップしましょう:

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

必要な依存関係をインストールしてください:

npm install express redis shortid dotenv
  • express:軽量なWebサーバーフレームワーク。

  • redis:キャッシングを処理するために。

  • shortid:短い一意のIDを生成するために。

  • dotenv: 環境変数の管理に使用します。

プロジェクトのルートに.envファイルを作成してください:

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

以下の変数は、使用するRedisホストとポートを定義します。

ステップ2: Redisインスタンスのセットアップ

複数のRedisインスタンスを使用した分散環境をシミュレートするためにDockerを使用します。

次のコマンドを実行して3つの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

これにより、異なるポートで実行される3つのRedisインスタンスがセットアップされます。これらのインスタンスを使用して一貫性のあるハッシングとシャーディングを実装します。

ステップ3: URL短縮サービスの実装

メインのアプリケーションファイル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 })
];

// Redisクライアント間でキーを分散するハッシュ関数
function getRedisClient(key) {
  const hash = key.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
  return redisClients[hash % redisClients.length];
}

// 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}` });
});

// オリジナルのURLを取得するエンドポイント
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}`);
});

このコードでは、以下が確認できます:

  1. 一貫性のあるハッシング:

    • シンプルなハッシュ関数を使用して、複数のRedisクライアント間でキー(短縮URL)を分散させます。

    • ハッシュ関数により、URLがRedisインスタンス全体に均等に分散されます。

  2. URLの短縮:

    • /shorten エンドポイントは長いURLを受け入れ、shortid ライブラリを使用して短いIDを生成します。

    • 短縮URLは、当社のハッシュ関数を使用してRedisインスタンスの1つに保存されます。

  3. URLのリダイレクト:

    • /:shortId エンドポイントは、キャッシュから元のURLを取得してユーザーをリダイレクトします。

    • キャッシュ内でURLが見つからない場合、404 の応答が返されます。

ステップ4:キャッシュの無効化の実装

実際のアプリケーションでは、URLが期限切れになるか変更されることがあります。これを処理するために、キャッシュの無効化を実装する必要があります。

キャッシュされたURLに有効期限を追加する

index.jsファイルを変更して、キャッシュされたエントリごとに有効期限を設定しましょう:

// 有効期限付きURLを短縮するエンドポイント
app.post('/shorten', async (req, res) => {
  const { url, ttl } = req.body; // ttl(Time-To-Live)はオプションです
  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は1時間です
  res.json({ shortUrl: `http://localhost:${process.env.PORT}/${shortId}` });
});
  • TTL(Time-To-Live): 各短縮URLに対してデフォルトで有効期限を1時間に設定します。必要に応じて各URLのTTLをカスタマイズできます。

  • キャッシュの無効化: TTLが切れるとエントリは自動的にキャッシュから削除されます。

ステップ5:キャッシュメトリクスの監視

キャッシュヒットミスを監視するために、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);
  });
});

このコードでは以下が行われています:

  • キャッシュヒット:キャッシュ内でURLが見つかった場合、それはキャッシュヒットです。

  • キャッシュミス:URLが見つからない場合、それはキャッシュミスです。

  • このログは分散キャッシュのパフォーマンスを監視するのに役立ちます。

ステップ6:アプリケーションのテスト

  1. Redisインスタンスを起動:
docker start redis1 redis2 redis3
  1. Node.jsサーバーを実行:
node index.js
  1. エンドポイントをテストするには、curlまたはPostmanを使用してください:

    • URLを短縮する:

      POST http://localhost:3000/shorten
      Body: {"url": "https://example.com"}
      
    • 短縮URLにアクセスする:

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

結論: 学んだこと

おめでとうございます!Node.jsとRedisを使用して、分散キャッシングを備えたスケーラブルなURL短縮サービスを構築しました。このチュートリアルを通じて、以下のことを学びました:

  1. 複数のRedisインスタンスにキャッシュエントリを分散するために、一貫性のあるハッシュを実装します。

  2. データを最新の状態に保つために、キャッシュ無効化戦略でアプリケーションを最適化します。

  3. Dockerを使用して複数のRedisノードで分散環境をシミュレートします。

  4. パフォーマンスを最適化するために、キャッシュヒットとミスをモニタリングします。

次のステップ:

  • データベースを追加:キャッシュを超えてURLをデータベースに保存します。

  • アナリティクスを実装:クリック数や短縮URLのアナリティクスを追跡します。

  • クラウドに展開:自動スケーリングと耐障害性のために、Kubernetesを使用してアプリケーションを展開します。

コーディングを楽しんでください!