0

🔗 Trận 1: TinyURL – Chuyện chàng lùn gánh gã khổng lồ (URL Shortener) - EP 1

Thiết kế TinyURL như thế nào? Cùng xây dựng một hệ thống rút gọn link từ con số 0

Nếu Facebook, YouTube hay Shopee đều có những đường link dài ngoằng như thế này

https://example.com/product/category/detail?id=182937182937&ref=campaign_summer_2026

thì việc chia sẻ chúng sẽ vô cùng bất tiện.

Đó là lý do những dịch vụ như TinyURL hay Bitly ra đời.

Chúng biến một đường link dài thành một đường link ngắn gọn như:

https://tinyurl.com/2TX8AbC

Nhìn thì có vẻ đơn giản.

Nhưng nếu mỗi ngày có 100 triệu URL mới được tạo, làm thế nào để hệ thống vẫn hoạt động nhanh, không bị trùng mãcó thể mở rộng trong nhiều năm?

Hãy cùng thiết kế hệ thống này như một senior nhé.

Nhưng trước tiên hãy nhớ 4 bước sau:

  • 1. Understand the problem and design scope: Hiểu rõ bài toán và thiết lập phạm vi thiết kế
  • 2. Propose high-level design: Đề xuất thiết kế cấp độ tổng quan và đạt sự đồng thuận
  • 3. Design deep dive: Đi sâu vào thiết kế chi tiết và phân tích các thành phần quan trọng
  • 4. Wrap up: Thảo luận các trade-off và cách mở rộng

Bước 1. Trước khi đi vào thiết kế, hãy hiểu mình đang xây cái gì

Một sai lầm rất phổ biến của người mới học System Design là lao ngay vào nghĩ về Database hay Redis.

Thực tế, điều đầu tiên các pháp sư làm luôn là làm rõ bài toán.

Ví dụ, nếu người phỏng vấn nói:

"Hãy thiết kế TinyURL."

Thì sẽ có hàng loạt câu hỏi cần được đặt ra.

  • Hệ thống cần làm được những gì?
  • Có bao nhiêu người sử dụng?
  • Một ngày tạo bao nhiêu URL?
  • Có cần thống kê lượt click không?
  • URL ngắn có được tùy chỉnh không?

Sau khi trao đổi, giả sử ta thống nhất được yêu cầu như sau:

  • Có thể tạo URL ngắn từ URL dài.
  • Khi người dùng mở URL ngắn sẽ tự động chuyển về URL gốc.
  • Mỗi ngày có khoảng 100 triệu URL mới.

Ước lượng quy mô

Một kỹ năng cực kỳ quan trọng trong System Design là Back-of-the-envelope estimation (ước lượng nhanh).

Thay vì nói "rất nhiều request", ta sẽ thử tính và đưa ra số liệu cụ thể.

Số request ghi

  • Một ngày có 100 000 000 URL
  • Một ngày = 86 400 giây

Vậy thì → 100 000 000 / 86 400 ≈ 1160 request/giây

Đây gọi là Write QPS.


Số request đọc

Giả sử số người mở link nhiều gấp 10 lần số lần tạo link.

Ta có → Read QPS ≈ 11 600 request/giây

Ngay lập tức ta nhận ra một điều.

Hệ thống chủ yếu là đọc dữ liệu, chứ không phải ghi dữ liệu.

Chi tiết nhỏ này sẽ ảnh hưởng đến toàn bộ thiết kế phía sau.


Bước 2. Hệ thống sẽ hoạt động như thế nào?

Thực ra TinyURL chỉ có hai chức năng chính.

Chức năng đầu tiên: Tạo URL ngắn

Client gửi:

POST /api/v1/data/shorten
# Request Body
{
  "longUrl": "https://example.com/very-long-url"
}

Server trả về:

# Response Body
{
  "shortUrl": "https://tinyurl.com/2TX8AbC"
}

Đơn giản.


Chức năng thứ hai: Điều hướng

Người dùng mở:

https://tinyurl.com/2TX8AbC

Server tìm URL gốc rồi trả về:

302 Redirect

hoặc

301 Redirect

301 hay 302?

Đây là điểm rất nhiều người mới bỏ qua.

Hai mã trạng thái này đều dùng để chuyển hướng nhưng hành vi lại khác nhau. Điều này khá quan trọng.

301 Moved Permanently

Trình duyệt sẽ nhớ kết quả.

Lần sau người dùng mở cùng một link, trình duyệt sẽ đi thẳng đến URL gốc.

Không cần hỏi TinyURL nữa.

Ưu điểm:

  • nhanh
  • giảm tải server

302 Found

Trình duyệt không nhớ.

Mỗi lần click đều phải đi qua server. Nghe có vẻ tệ hơn nhưng lại cực kỳ hữu ích.

Vì mỗi lần người dùng mở link, server đều biết.

Ta có thể thống kê:

  • bao nhiêu lượt click
  • click từ quốc gia nào
  • dùng điện thoại hay máy tính
  • click lúc mấy giờ

Đó là lý do Bitly thường dùng kiểu này.


Bước 3. URL dài sẽ biến thành URL ngắn bằng cách nào?

Đây mới là phần thú vị nhất.

Ta cần một thuật toán sinh ra chuỗi như:

2TX8AbC

Ý tưởng đầu tiên: Hash URL

Ví dụ dùng:

  • MD5
  • SHA1

Hash URL thành chuỗi rất dài. Sau đó lấy 7 ký tự đầu.

Nghe hợp lý nhưng có một vấn đề lớn.

Hai URL khác nhau hoàn toàn có thể sinh ra cùng 7 ký tự đầu.

Ví dụ:

abc123X...

abc123X...

Nếu chuyện này xảy ra thì hệ thống không biết phải mở URL nào.

Đây gọi là Hash Collision.


Cách xử lý

Server phải hỏi Database:

Chuỗi này đã tồn tại chưa?

Nếu đã có rồi. Hash lại. Nếu vẫn trùng. Hash tiếp.

Có thể phải lặp nhiều lần. Điều đó đồng nghĩa với rất nhiều lần truy cập Database.

Không hiệu quả.


Ý tưởng thứ hai: Đừng hash URL nữa

Đây là cách Alex Xu đánh giá là tối ưu hơn.

Thay vì hash URL.

Ta sinh một ID duy nhất.

Ví dụ:

1
2
3
4
...
11157

Sau đó đổi ID sang hệ Base62.

Ví dụ:

11157 → 2TX

Thế là xong.


Base62 là gì?

Có tất cả:

26 chữ thường + 26 chữ hoa + 10 chữ số = 62 ký tự

Ví dụ:

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

Mỗi ID sẽ được đổi sang bảng ký tự này.

Giống như việc đổi:

255 → FF

trong hệ Hex.

Chỉ khác là bây giờ ta đổi sang Base62.


Vì sao cách này tốt hơn?

Vì ID luôn là duy nhất, không bao giờ trùng nhau.

Nên

  • Không cần kiểm tra collision.
  • Không cần hash lại.
  • Không cần truy vấn Database nhiều lần.

Bước 4. Làm sao để mở link thật nhanh?

Hãy nhớ lúc đầu.

Read QPS khoảng: 11 600 request/giây

Nếu mỗi request đều đọc Database, sẽ rất nhanh bị quá tải.

Giải pháp gần như mọi hệ thống lớn đều dùng là: Redis


Luồng xử lý sẽ trở thành:

image.png

Nếu Redis có dữ liệu. Trả về ngay. Nếu Redis không có mới đọc Database. Sau đó lưu ngược lại Redis.

Lần sau sẽ nhanh hơn rất nhiều.

Đây chính là mô hình Cache Aside Pattern rất phổ biến.


Kiến trúc tổng thể

Khi ghép tất cả lại, hệ thống sẽ trông như sau:

image.png

Mỗi thành phần đều có một nhiệm vụ rõ ràng:

  • Web Server nhận request từ người dùng.
  • ID Generator tạo ID không trùng lặp.
  • Base62 Encoder biến ID thành chuỗi ngắn.
  • Redis giúp truy cập cực nhanh.
  • Database lưu dữ liệu lâu dài.

Nếu hệ thống có hàng tỷ người dùng thì sao?

Một kỹ sư giỏi sẽ không dừng lại ở việc "hệ thống chạy được".

Họ sẽ nghĩ tiếp:

Nếu ngày mai lượng người dùng tăng gấp 100 lần thì sao?

Khi đó chúng ta có thể bổ sung thêm nhiều thành phần:

  • Rate Limiter để ngăn bot tạo hàng triệu URL giả.
  • Database Sharding để chia dữ liệu sang nhiều máy chủ.
  • Replication để tăng khả năng đọc.
  • Auto Scaling để tự động thêm Web Server khi lưu lượng tăng.
  • Analytics Pipeline để thống kê lượt click, quốc gia, thiết bị... mà không làm chậm luồng chính.

Đây là những ý tưởng thường được nhắc đến ở cuối các buổi phỏng vấn System Design. Và đây là thực sự là điểm khác biệt giữa senior và phần còn lại. Tôi sẽ viết chi tiết ở 1 bài khác.


Tổng kết

Thoạt nhìn, TinyURL chỉ là một ứng dụng "biến link dài thành link ngắn". Nhưng phía sau nó là rất nhiều bài toán quen thuộc trong thiết kế hệ thống:

  • Làm sao tạo ID duy nhất?
  • Làm sao tránh trùng lặp?
  • Làm sao xử lý hàng chục nghìn request mỗi giây?
  • Làm sao giảm tải Database?
  • Làm sao mở rộng khi số lượng người dùng tăng lên hàng trăm lần?

Điều thú vị là những kỹ thuật xuất hiện trong TinyURL như Cache, Base62, Distributed ID Generator, Sharding hay Rate Limiter cũng chính là những mảnh ghép được sử dụng trong hầu hết các hệ thống lớn như Facebook, YouTube, TikTok hay Shopee.

Vì vậy, dù TinyURL chỉ là một ứng dụng nhỏ, nó lại là một trong những bài tập System Design kinh điển nhất dành cho người mới bắt đầu. Chỉ cần hiểu thật kỹ bài toán này, bạn sẽ có nền tảng rất tốt để tiếp cận những hệ thống phức tạp hơn trong tương lai.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.