在本教程中,我們將使用 Node.js 和 Redis 構建一個可擴展的 URL 縮短服務。此服務將利用分佈式緩存來有效處理高流量,減少延遲,並無縫擴展。我們將探討一些關鍵概念,如一致性哈希、緩存失效策略和分片,以確保系統保持快速和可靠。
在本指南結束時,您將擁有一個完全功能的 URL 縮短服務,利用分佈式緩存來優化性能。我們還將創建一個互動式演示,讓用戶可以輸入網址並查看即時指標,如緩存命中和未命中。
您將學到什麼
-
如何使用 Node.js 和 Redis 構建 URL 縮短服務。
-
如何實現 分佈式緩存 以優化性能。
-
理解 一致性哈希 和 緩存失效策略。
-
使用 Docker 模擬多個 Redis 實例進行分片和擴展。
前提條件
在開始之前,請確保您已安裝以下內容:
-
Node.js (v14 或更高版本)
-
Redis
-
Docker
-
基本的 JavaScript、Node.js 和 Redis 知識。
目錄
專案概述
我們將建立一個網址縮短服務,其中:
-
使用者可以縮短長網址並檢索原始網址。
-
此服務使用 Redis 快取 來存儲縮短網址和原始網址之間的映射。
-
快取分佈在多個 Redis 實例之間,以處理高流量。
-
系統將實時展示 快取命中 和 未命中。
系統架構
為了確保可擴展性和性能,我們將把服務分成以下幾個組件:
-
API 伺服器: 處理縮短和檢索 URL 的請求。
-
Redis 快取層: 使用多個 Redis 實例進行分散式快取。
-
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}`);
});
如您在此代碼中所見:
-
一致性哈希:
-
我們使用一個簡單的哈希函數將密鑰(縮短的 URL)分發到多個 Redis 客戶端。
-
哈希函數確保 URL 均勻地分佈在 Redis 實例之間。
-
-
URL 縮短:
-
該 /shorten 端點接受一個長 URL,並使用 shortid 庫生成一個短 ID。
-
縮短的 URL 使用我們的哈希函數儲存在其中一個 Redis 實例中。
-
-
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:測試應用程式
- 啟動你的 Redis 實例:
docker start redis1 redis2 redis3
- 運行 Node.js 伺服器:
node index.js
-
測試端點 使用
curl
或 Postman:-
縮短一個網址:
POST http://localhost:3000/shorten Body: { "url": "https://example.com" }
-
訪問縮短的網址:
GET http://localhost:3000/{shortId}
-
結論:你學到了什麼
恭喜!你已成功使用Node.js和Redis構建了一個可擴展的URL縮短服務,並實現了分佈式緩存。在本教程中,你學會了如何:
-
實現一致性哈希以在多個Redis實例之間分發緩存項目。
-
優化應用程序,使用緩存失效策略來保持數據的最新。
-
使用Docker來模擬具有多個Redis節點的分佈式環境。
-
監控緩存命中和未命中以優化性能。
下一步:
-
添加數據庫:將URL存儲在數據庫中,以實現超出緩存的持久性。
-
實現分析:跟踪點擊次數和縮短URL的分析。
-
部署到雲端:使用Kubernetes部署應用程序,實現自動擴展和彈性。
開始編碼!