Thành viên trong nhóm:
- 22026552 Nguyễn Văn Quân
- 22026507 Mai Tiến Mạnh
ShortURL Service là một ứng dụng rút gọn URL hiệu quả, tối ưu hóa để xử lý nhanh chóng các yêu cầu thông qua cơ sở dữ liệu MongoDB, bộ nhớ đệm Redis, và hỗ trợ giới hạn tốc độ (Rate Limiting) để bảo mật.
- Rút gọn URL: Tạo mã ngắn duy nhất cho mỗi URL gốc.
- Truy xuất URL gốc: Lấy lại URL gốc từ mã rút gọn.
- Lưu trữ hiệu quả: Sử dụng MongoDB để lưu trữ, Redis để cache tăng tốc truy xuất.
- Kiểm tra hợp lệ URL: Đảm bảo chỉ nhận URL hợp lệ.
- Giới hạn tốc độ (Rate Limiting): Ngăn chặn spam/tấn công DDoS.
- Retry Pattern: Tự động thử lại thao tác khi gặp lỗi tạm thời (DB, Redis).
- Hỗ trợ CORS: Cho phép tích hợp với các ứng dụng web khác.
- Giao diện web: Có sẵn UI đơn giản tại
/public.
- Node.js v16+
- MongoDB (mặc định:
mongodb://localhost:27017/shorturl) - Redis (mặc định:
127.0.0.1:6379) - npm
-
Clone repository:
git clone https://github.com/manhtienmai/Short_Link.git cd Short_Link -
Cài đặt dependencies:
npm install
-
Cấu hình môi trường: Tạo file
.envở thư mục gốc với nội dung:MONGODB_URI=mongodb://localhost:27017/shorturl REDIS_HOST=127.0.0.1 REDIS_PORT=6379 -
Chạy ứng dụng:
npm run dev
Server chạy tại http://localhost:3001.
- Endpoint:
POST /create?url=<url-goc> - Request:
- Query string:
url(bắt buộc)
- Query string:
- Response:
- Thành công (201):
"abc12" - Lỗi (400):
{ "error": "URL para is required" } - Lỗi định dạng:
{ "error": "invalid url format" } - Vượt quá giới hạn request (429):
{ "error": "Too Many Requests" }
- Thành công (201):
- Endpoint:
GET /short/:id - Response:
- Thành công (200):
https://example.com - Không tìm thấy (404):
<h1>404 Not Found</h1>
- Vượt quá giới hạn request (429):
{ "error": "Too Many Requests" }
- Thành công (200):
- Endpoint:
GET / - Trả về trang HTML đơn giản cho phép nhập URL và nhận link rút gọn.
- Express.js: Framework backend chính.
- MongoDB: Lưu trữ cặp (id, url).
- Cache Aside: Dùng Node-Cache và Redis tạo các tầng cache.
- Mongoose: ODM cho MongoDB.
- Rate Limiter: Sử dụng
rate-limiter-flexiblevới Redis để giới hạn 1000 request/10 giây mỗi IP. - Retry Pattern: Dùng
async-retryđể tự động thử lại thao tác DB/Redis khi gặp lỗi tạm thời (3 lần, delay 1-5s).
- Kiểm tra hợp lệ URL.
- Kiểm tra cache Redis: Nếu đã có, trả về id.
- Nếu chưa có, kiểm tra DB: Nếu đã có, trả về id.
- Nếu chưa có, sinh id ngẫu nhiên (5 ký tự, dùng crypto), kiểm tra trùng, lưu vào DB và cache.
- Trả về id.
- Kiểm tra cache Redis: Nếu có, trả về URL.
- Nếu không, kiểm tra DB: Nếu có, trả về URL và lưu vào cache.
- Nếu không có, trả về 404.
- Middleware kiểm tra số lượng request từ mỗi IP.
- Nếu vượt quá 1000 request/10s, trả về 429.
- Các thao tác DB/Redis được bọc bởi
callWithRetry(tối đa 3 lần thử lại nếu lỗi).
- Khi truy xuất, ưu tiên lấy từ cache, nếu không có thì lấy từ DB rồi lưu vào cache.
.
├── server.js # Khởi tạo server, định nghĩa route
├── utils.js # Logic xử lý rút gọn URL, cache, validate
├── middleware.js # Các middleware: rate limit, logger, validate, error handler
├── optimizer/
│ ├── mongoDBConfig.js # Kết nối và định nghĩa model MongoDB
│ ├── redisConfig.js # Kết nối và thao tác với Redis
│ └── retryHelper.js # Hàm retry pattern
│ └── memoryCache.js # Thiết lập các hàm liên quan in-memory
├── public/ # Giao diện web (index.html, script.js, styles.css)
-
Ban đầu nhóm có cải tiến bằng việc sử dụng thư viện Sequelize để định nghĩa Schema cho model Link.
-
Sử dụng ORM để viết hai hàm findOriginORM và createORM trong branch dev-v0.1 và trong file utils.
-
Tuy nhiên, do SQLite có hạn chế trong việc xử lý nhiều kết nối đồng thời, dẫn đến hiện tượng nghẽn cổ chai khi truy cập dữ liệu song song, nhóm đã đề xuất chuyển sang sử dụng MongoDB làm cơ sở dữ liệu chính và xây dựng lại lớp persistent layer sử dụng ODM Mongoose.
-
Ưu điểm:
- Khả năng mở rộng cao: MongoDB là NoSQL, phù hợp cho dữ liệu lớn, dễ scale horizontal.
- Tối ưu truy vấn: Truy vấn nhanh hơn, hỗ trợ index tốt cho các trường thường xuyên tìm kiếm (id, url).
- ODM Mongoose: Định nghĩa schema rõ ràng, tự động validate, dễ bảo trì và mở rộng model.
-
Chi tiết code:
- Định nghĩa schema và model trong
optimizer/mongoDBConfig.js. - Kết nối MongoDB qua biến môi trường, log trạng thái kết nối.
- Định nghĩa schema và model trong
-
Công nghệ sử dụng: Node-Cache (Memory), Redis, MongoDB
-
Cơ chế Cache đa tầng:
-
Tier 1 - Memory Cache (Node-Cache):
- Cache nhanh nhất, lưu trữ trực tiếp trong bộ nhớ của Node.js
- TTL mặc định: 60 giây
- Kiểm tra và dọn dẹp cache mỗi 120 giây
- Ưu tiên kiểm tra đầu tiên khi có request
-
Tier 2 - Redis Cache:
- Cache phân tán, có thể chia sẻ giữa nhiều instance
- TTL mặc định: 3600 giây (1 giờ)
- Được kiểm tra nếu không tìm thấy trong Memory Cache
-
Tier 3 - MongoDB:
- Lưu trữ dài hạn, nguồn dữ liệu chính
- Được truy vấn khi không tìm thấy trong cả Memory và Redis Cache
-
-
Cơ chế hoạt động:
-
Read-Through (Đọc dữ liệu):
- Kiểm tra lần lượt từ Tier 1 → Tier 2 → Tier 3
- Khi tìm thấy ở tier thấp hơn, tự động cập nhật lên các tier cao hơn
- Ví dụ: Nếu tìm thấy ở Redis, sẽ cập nhật vào Memory Cache
-
Write-Through (Ghi dữ liệu):
- Cập nhật đồng thời vào tất cả các tier
- Sử dụng Promise.all để cập nhật song song Redis và Memory Cache
- Đảm bảo tính nhất quán dữ liệu giữa các tier
-
-
Ưu điểm:
- Hiệu năng tối ưu: Memory Cache cho response nhanh nhất, Redis cho phân tán, MongoDB cho lưu trữ
- Giảm tải cho DB: Hầu hết request được phục vụ từ cache
- Khả năng mở rộng: Dễ dàng thêm/sửa/xóa các tier cache
- Độ tin cậy cao: Nhiều lớp bảo vệ, tự động đồng bộ giữa các tier
- Cache 2 chiều: Lưu cả id→url và url→id ở mỗi tier
-
Chi tiết code:
- Memory Cache:
optimizer/memoryCache.jssử dụng Node-Cache - Redis Cache:
optimizer/redisConfig.jsquản lý kết nối và thao tác Redis - Logic cache:
utils.jstriển khai cơ chế đa tầng trong các hàmfindOrigin,shortUrl
- Memory Cache:
- Ưu điểm:
- Đảm bảo dữ liệu đầu vào chuẩn: Tránh lưu các URL không hợp lệ vào hệ thống.
- **Giảm lỗi phát sinh khi redirect hoặc truy xuất URL gốc.
- Chi tiết code:
- Middleware
validateUrltrongmiddleware.jskiểm tra queryurlvà validate định dạng. - Hàm
isValidUrltrongutils.jskiểm tra tiền tốhttp://hoặchttps://.
- Middleware
- Ưu điểm:
- Bảo vệ API: Ngăn chặn spam, tấn công DDoS, lạm dụng dịch vụ.
- Công bằng cho người dùng: Mỗi IP chỉ được phép gửi tối đa 1000 request/10 giây.
- Dễ mở rộng: Sử dụng Redis làm backend, phù hợp cho hệ thống nhiều node.
- Chi tiết code:
- Middleware
rateLimittrongmiddleware.jssử dụngrate-limiter-flexiblevới Redis. - Trả về HTTP 429 nếu vượt quá giới hạn.
- Middleware
- Ưu điểm:
- Tăng độ ổn định: Tự động thử lại thao tác DB/Redis khi gặp lỗi tạm thời (network, timeout...).
- Giảm lỗi do sự cố nhất thời: Đặc biệt hữu ích khi làm việc với hệ thống phân tán.
- Chi tiết code:
- Hàm
callWithRetrytrongoptimizer/retryHelper.jssử dụng packageasync-retry. - Áp dụng cho các thao tác DB/Redis trong các route chính (
server.js).
- Hàm
- Ưu điểm:
- Sinh ID bảo mật: Dùng
crypto.randomBytesthay vìMath.randomđể sinh ID ngẫu nhiên, khó đoán, an toàn hơn. - Tối ưu vòng lặp: Sử dụng
Array.fromđể chuyển bytes thành ký tự, code ngắn gọn, hiệu suất cao. - Giới hạn số lần thử sinh ID: Tránh vòng lặp vô hạn khi tạo ID mới, chỉ thử tối đa 10 lần.
- Ghi cache song song: Dùng
Promise.allđể ghi cache 2 chiều cùng lúc, giảm thời gian chờ.
- Sinh ID bảo mật: Dùng
- Chi tiết code:
- Hàm
makeID,shortUrl,findOrigintrongutils.js.
- Hàm
- Ưu điểm:
- Ghi log request: (Có thể bật/tắt) giúp theo dõi, debug, kiểm soát truy cập.
- Xử lý lỗi tập trung: Middleware
errorHandlergiúp trả về lỗi chuẩn, log lỗi server. - Xử lý route không tồn tại: Middleware
notFoundtrả về lỗi 404 chuẩn RESTful. - Hỗ trợ CORS: Cho phép các ứng dụng web khác truy cập API dễ dàng.
- Chi tiết code:
- Các middleware trong
middleware.js, sử dụng trongserver.js.
- Các middleware trong
Dưới đây là kịch bản kiểm thử tải lớn được sử dụng với Artillery:
config:
target: "http://localhost:3001"
phases:
- duration: 60 # test 60 giây
arrivalRate: 500 # 500 requests/giâyartillery run --output report-get.json test-api-get.yml
artillery run --output report-post.json test-api-post.yml
- File kết quả:
report-get.jsonreport-post.json
Sau khi thực hiện test với 30000 request trong 60s, nhóm có kết quả như sau:
1. Kết quả khi sử dụng phần code được cung cấp
- GET:
- POST:
2. Kết quả khi đã thực hiện tối ưu code và kiến trúc
- GET:
- POST:
| Chỉ số | Trước cải tiến | Sau cải tiến | Nguyên nhân khác biệt |
|---|---|---|---|
| errors.ECONNREFUSED | 14 961 | 0 | Chuyển từ SQLite (dễ lock/I/O) sang MongoDB + retry → hầu như không còn từ chối kết nối. |
| errors.EADDRINUSE | 4 | 978 | Rate-limiter giới hạn kết nối, retry mở nhiều kết nối mới nhanh → port exhaustion. |
| errors.ETIMEDOUT | 219 | 12 694 | Nhiều request bị giữ trong hàng đợi rate-limiter đến timeout; một số fallback sang DB gốc chậm. |
| http.request_rate | 434 req/sec | 326 req/sec | Redis-based rate-limiter giới hạn xuống ~326/sec để bảo vệ hệ thống. |
| http.downloaded_bytes | 177 792 bytes | 638 929 bytes | Cache trả nhanh nội dung → lượng dữ liệu tải tăng gấp ~3,6×. |
| http.response_time.mean | 1 329,8 ms | 13,4 ms | Hầu hết request được phục vụ từ Cache hoặc Mongo nhanh; SQLite I/O bị loại bỏ. |
| http.response_time.median | 1 300,1 ms | 4 ms | Median giảm mạnh nhờ cache hit cao. |
| http.codes.200 | 14 816 | 8 561 | Nhiều request bị rate-limit → trả 429. |
| http.codes.429 | 0 | 7 660 | Rate-limiter chủ động trả 429 khi vượt ngưỡng. |
| http.responses | 14 816 | 16 221 | Tổng responses tăng (bao gồm cả 429). |
| vusers.completed | 14 816 | 16 221 | Retry + cache giúp nhiều virtual user hoàn thành hơn. |
| vusers.failed | 15 184 | 13 672 | Giảm 1 500 failures nhờ retry & cache giảm tải DB. |
| vusers.session_length.mean | 3 085 ms | 84,5 ms | Session ngắn hơn nhiều do response time giảm sâu. |
| Chỉ số | Trước cải tiến | Sau cải tiến | Nguyên nhân khác biệt |
|---|---|---|---|
| Lỗi kết nối | ECONNREFUSED: 19 940 |
EADDRINUSE: 1 635ETIMEDOUT: 10 706 |
SQLite → MongoDB + retry loại bỏ ECONNREFUSED; retry mở nhiều kết nối → EADDRINUSE, ETIMEDOUT. |
| HTTP Status Codes | 200: 10 060 |
201: 8 917429: 7 678 |
Trước gộp mọi thành công thành 200; sau phân biệt rõ 201 và trả 429 khi vượt rate-limit. |
| Request rate / Throughput | 500 req/sec → 30 000 total | 229 req/sec → 28 156 total | Rate-limiter giảm từ 500→229 req/sec để bảo vệ hệ thống; một số request bị chặn hoặc timeout. |
| Dung lượng tải về | 50 300 bytes | 436 163 bytes | Redis cache tăng hit rate, phục vụ nhanh và nhiều dữ liệu hơn (metadata, doc). |
| Thời gian đáp ứng (all) | mean: 2 246 ms median: 2 322 ms p95: 2 618 ms p99: 2 725 ms |
mean: 16,7 ms median: 3 ms p95: 13,9 ms p99: 497,8 ms |
Trung bình & median giảm mạnh nhờ cache & MongoDB; tail (p99) còn ~500 ms do fallback hoặc retry chậm. |
| Thời gian đáp ứng (2xx) | mean: 2 246 ms p95: 2 618 ms p99: 2 725 ms |
mean: 28,6 ms p95: 40,9 ms p99: 671,9 ms |
2xx chủ yếu phục vụ từ cache, tail vẫn ảnh hưởng bởi retry/DB. |
| Thời gian đáp ứng (4xx) | – | mean: 2,7 ms p95: 5 ms p99: 8,9 ms |
429 trả ngay khi vượt ngưỡng → rất nhanh. |
| Users (vusers) | created: 30 000 completed: 10 060 failed: 19 940 |
created: 28 156 completed: 16 595 failed: 11 561 |
Hoàn thành tăng gần 6 500 nhờ retry + cache hiệu quả hơn, giảm fail. |
Dự án đã đạt được mục tiêu ban đầu về xây dựng service rút gọn URL với hiệu năng cao và ổn định. Các cải tiến về kiến trúc và công nghệ đã giúp service có thể xử lý tải lớn, dễ mở rộng và bảo trì. Kết quả test hiệu năng cho thấy cải thiện đáng kể về thời gian phản hồi và độ tin cậy của hệ thống.



