在本教程中,我們將使用 Node.js 和 Redis 構建一個可擴展的 URL 縮短服務。此服務將利用分佈式緩存來有效處理高流量,減少延遲,並無縫擴展。我們將探討一些關鍵概念,如一致性哈希、緩存失效策略和分片,以確保系統保持快速和可靠。

在本指南結束時,您將擁有一個完全功能的 URL 縮短服務,利用分佈式緩存來優化性能。我們還將創建一個互動式演示,讓用戶可以輸入網址並查看即時指標,如緩存命中和未命中。

您將學到什麼

  • 如何使用 Node.jsRedis 構建 URL 縮短服務。

  • 如何實現 分佈式緩存 以優化性能。

  • 理解 一致性哈希緩存失效策略

  • 使用 Docker 模擬多個 Redis 實例進行分片和擴展。

前提條件

在開始之前,請確保您已安裝以下內容:

  • Node.js (v14 或更高版本)

  • Redis

  • Docker

  • 基本的 JavaScript、Node.js 和 Redis 知識。

目錄

專案概述

我們將建立一個網址縮短服務,其中:

  1. 使用者可以縮短長網址並檢索原始網址。

  2. 此服務使用 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: 一個輕量級的網頁伺服器框架。

  • 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 實例

我們將使用 Docker 模擬具有多個 Redis 實例的分佈式環境。

運行以下命令啟動三個 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

這將在不同端口上啟動三個 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. 一致性哈希

    • 我們使用一個簡單的哈希函數將密鑰(縮短的 URL)分發到多個 Redis 客戶端。

    • 哈希函數確保 URL 均勻地分佈在 Redis 實例之間。

  2. URL 縮短:

    • /shorten 端點接受一個長 URL,並使用 shortid 庫生成一個短 ID。

    • 縮短的 URL 使用我們的哈希函數儲存在其中一個 Redis 實例中。

  3. URL 重定向:

    • /:shortId 端點從快取中檢索原始 URL 並重定向用戶。

    • 如果在快取中找不到該 URL,則返回 404 響應。

步驟4:實施緩存失效

在現實世界應用中,URL可能會過期或隨時間更改。為了處理這一點,我們需要實施緩存失效cache invalidation

將到期時間添加到已緩存的URL中

讓我們修改index.js文件以為每個已緩存的條目設置到期時間:

// 用於設置帶有到期時間的URL的端點
app.post('/shorten', async (req, res) => {
  const { url, ttl } = req.body; // ttl(生存時間)是可選的
  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(生存時間): 我們為每個縮短的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:

    • 縮短一個網址:

        POST http://localhost:3000/shorten
        Body: { "url": "https://example.com" }
      
    • 訪問縮短的網址:

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

結論:你學到了什麼

恭喜!你已成功使用Node.js和Redis構建了一個可擴展的URL縮短服務,並實現了分佈式緩存。在本教程中,你學會了如何:

  1. 實現一致性哈希以在多個Redis實例之間分發緩存項目。

  2. 優化應用程序,使用緩存失效策略來保持數據的最新。

  3. 使用Docker來模擬具有多個Redis節點的分佈式環境。

  4. 監控緩存命中和未命中以優化性能。

下一步:

  • 添加數據庫:將URL存儲在數據庫中,以實現超出緩存的持久性。

  • 實現分析:跟踪點擊次數和縮短URL的分析。

  • 部署到雲端:使用Kubernetes部署應用程序,實現自動擴展和彈性。

開始編碼!