ใน tutorial นี้ เราจะสร้าง URL shortening service ที่ scalable ด้วย Node.js และ Redis บริการนี้จะใช้ distributed caching จัดการ high traffic ได้อย่างมีประสิทธิภาพ ลด latency และ scale ได้ seamless เราจะสำรวจ key concepts เช่น consistent hashing, cache invalidation strategies, และ sharding เพื่อให้ระบบเร็วและน่าเชื่อถือ
จบ tutorial นี้ คุณจะได้ URL shortener service ที่สมบูรณ์ ใช้ distributed caching เพื่อ optimize performance เรายังสร้าง interactive demo ให้ผู้ใช้ input URLs และดู real-time metrics เช่น cache hits และ misses
สิ่งที่คุณจะได้เรียนรู้
- วิธีสร้าง URL shortener service ด้วย Node.js และ Redis
- วิธี implement distributed caching เพื่อ optimize performance
- เข้าใจ consistent hashing และ cache invalidation strategies
- ใช้ Docker จำลอง multiple Redis instances สำหรับ sharding และ scaling
ข้อกำหนดเบื้องต้น (Prerequisites)
ก่อนเริ่ม ตรวจสอบว่าติดตั้งแล้ว:
- Node.js (v14 หรือสูงกว่า)
- Redis
- Docker
- ความรู้พื้นฐาน JavaScript, Node.js, และ Redis
สารบัญ
- Project Overview
- Step 1: Setting Up the Project
- Step 2: Setting Up Redis Instances
- Step 3: Implementing the URL Shortener Service
- Step 4: Implementing Cache Invalidation
- Step 5: Monitoring Cache Metrics
- Step 6: Testing the Application
- Conclusion: What You've Learned
Project Overview
เราจะสร้าง URL shortener service ที่:
- ผู้ใช้ shorten long URLs และ retrieve original URLs ได้
- Service ใช้ Redis caching เก็บ mapping ระหว่าง shortened URLs และ original URLs
- Cache แบ่งกระจายข้าม multiple Redis instances จัดการ high traffic
- ระบบสาธิต cache hits และ misses แบบ real-time
System Architecture
เพื่อให้ scalable และ performant เราแบ่ง service เป็น components ต่อไป:
- API Server: จัดการ requests สำหรับ shortening และ retrieving URLs
- Redis Caching Layer: ใช้ multiple Redis instances สำหรับ distributed caching
- Docker: จำลอง distributed environment ด้วย multiple Redis containers
Step 1: Setting Up the Project
ตั้งค่าโปรเจกต์ด้วยการ initialize Node.js application:
mkdir scalable-url-shortener
cd scalable-url-shortener
npm init -y
ติดตั้ง dependencies ที่จำเป็น:
npm install express redis shortid dotenv
express: lightweight web server frameworkredis: จัดการ cachingshortid: สร้าง short unique IDsdotenv: จัดการ environment variables
สร้างไฟล์ .env ใน project root:
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 hosts และ ports ที่เราจะใช้
Step 2: Setting Up Redis Instances
เราใช้ Docker จำลอง distributed environment ด้วย multiple Redis instances
รันคำสั่งต่อไปนี้เพื่อเริ่มสาม Redis containers:
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 instances รันบน ports ต่างกัน เราจะใช้ instances นี้ implement consistent hashing และ sharding
Step 3: Implementing the URL Shortener Service
สร้าง main application file 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 })
];
// Hash function แจกแจง keys ระหว่าง Redis clients
function getRedisClient(key) {
const hash = key.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
return redisClients[hash % redisClients.length];
}
// Endpoint shorten 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: `https://localhost:${process.env.PORT}/${shortId}` });
});
// Endpoint retrieve original 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}`);
});
ในโค้ดนี้ เรามี:
Consistent Hashing:
- เราแจกแจง keys (shortened URLs) ข้าม multiple Redis clients ด้วย hash function เรียบง่าย
- Hash function รับรองว่า URLs แจกแจงอย่างสม่ำเสมอข้าม Redis instances
URL Shortening:
- Endpoint /shorten รับ long URL สร้าง short ID ด้วย library shortid
- Shortened URL เก็บในหนึ่งใน Redis instances ผ่าน hash function ของเรา
URL Redirection:
- Endpoint /:shortId ดึง original URL จาก cache และ redirect user
- หา URL ไม่เจอใน cache ส่งคืน response 404
Step 4: Implementing Cache Invalidation
ใน real-world application URLs อาจหมดอายุหรือเปลี่ยนแปลงตามเวลา เพื่อจัดการนี้ เราต้อง implement cache invalidation
เพิ่ม Expiry ให้ Cached URLs
แก้ไข index.js ให้ตั้ง expiration time สำหรับแต่ละ cached entry:
// Endpoint shorten URL พร้อม expiration
app.post('/shorten', async (req, res) => {
const { url, ttl } = req.body; // ttl (time-to-live) เป็น optional
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); // Default TTL 1 ชั่วโมง
res.json({ shortUrl: `https://localhost:${process.env.PORT}/${shortId}` });
});
TTL (Time-To-Live): เราตั้ง default expiration time 1 ชั่วโมง สำหรับ shortened URL ทุกอัน คุณ customize TTL ต่อ URL ได้หากต้องการ
Cache Invalidation: เมื่อ TTL หมดอายุ entry จะถูกลบออกจาก cache อัตโนมัติ
Step 5: Monitoring Cache Metrics
เพื่อ monitor cache hits และ misses เราเพิ่ม logging ใน endpoints ใน 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);
});
});
โค้ดนี้ทำอะไร:
Cache Hits: หากเจอ URL ใน cache คือ cache hit
Cache Misses: หา URL ไม่เจอ คือ cache miss
Logging นี้ช่วย monitor performance ของ distributed cache ของคุณ
Step 6: Testing the Application
- Start Redis instances:
docker start redis1 redis2 redis3
- Run Node.js server:
node index.js
Test endpoints ด้วย
curlหรือ Postman:Shorten a URL:
POST https://localhost:3000/shorten Body: { "url": "https://example.com" }Access the shortened URL:
GET https://localhost:3000/{shortId}
Conclusion: What You've Learned
ยินดีด้วย! คุณสร้าง URL shortener service ที่ scalable พร้อม distributed caching ด้วย Node.js และ Redis เรียบร้อยแล้ว ตลอด tutorial นี้ คุณได้เรียนรู้ว่า:
- Implement consistent hashing แจกแจง cache entries ข้าม multiple Redis instances
- Optimize application ด้วย cache invalidation strategies เก็บข้อมูล up-to-date
- ใช้ Docker จำลอง distributed environment ด้วย multiple Redis nodes
- Monitor cache hits and misses เพื่อ optimize performance
Next Steps:
Add a Database: เก็บ URLs ใน database เพื่อ persistence นอกเหนือจาก cache
Implement Analytics: Track click counts และ analytics สำหรับ shortened URLs
Deploy to the Cloud: ปรับใช้แอปพลิเคชันด้วย Kubernetes สำหรับ auto-scaling และ resilience
Happy coding!
Learn to code for free. freeCodeCamp's open source curriculum has helped more than 40,000 people get jobs as developers. Get started