在本教程中,我们将使用 Node.js 和 Redis 构建一个可扩展的 URL 缩短服务。这项服务将利用分布式缓存来有效处理高流量,降低延迟,并实现无缝扩展。我们将探讨一些关键概念,如一致性哈希、缓存失效策略和分片,以确保系统保持快速和可靠。
在本指南结束时,您将拥有一个使用分布式缓存优化性能的全功能 URL 缩短服务。我们还将创建一个互动演示,用户可以在其中输入 URL,并查看缓存命中和未命中等实时指标。
您将学到什么
-
如何使用 Node.js 和 Redis 构建 URL 缩短服务。
-
如何实现 分布式缓存 以优化性能。
-
理解 一致性哈希 和 缓存失效策略。
-
使用 Docker 模拟多个 Redis 实例进行分片和扩展。
先决条件
开始之前,请确保已安装以下内容:
-
Node.js(v14或更高版本)
-
Redis
-
Docker
-
JavaScript,Node.js和Redis的基础知识。
目录
项目概述
我们将构建一个URL缩短服务,其中:
-
用户可以缩短长URL并检索原始URL。
-
该服务使用Redis缓存来存储缩短URL和原始URL之间的映射。
-
缓存分布在多个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
:轻量级 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实例
我们将使用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可能会随时间过期或更改。为了处理这种情况,我们需要实现缓存失效。
为缓存的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}` });
});
-
存活时间(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:测试应用程序
- 启动您的Redis实例:
docker start redis1 redis2 redis3
- 运行Node.js服务器:
node index.js
-
使用
curl
或Postman测试端点:-
缩短URL:
POST http://localhost:3000/shorten Body: "url": "https://example.com" }
-
访问缩短后的URL:
GET http://localhost:3000/}
-
总结: 你学到了什么
恭喜!你已成功构建了一个使用 Node.js 和 Redis 实现 URL 缩短服务,具备 分布式缓存 的可伸缩性。在本教程中,你学会了如何:
-
实现一致性哈希以在多个Redis实例间分发缓存条目。
-
通过缓存失效策略优化您的应用程序,以保持数据更新。
-
使用Docker模拟具有多个Redis节点的分布式环境。
-
监控缓存命中和未命中以优化性能。
接下来的步骤:
-
添加数据库:将URL存储在数据库中,以实现缓存之外的持久性。
-
实现分析:跟踪点击次数和缩短URL的分析。
-
部署到云端:使用Kubernetes部署您的应用程序,实现自动扩展和弹性。
祝您编码愉快!