Kiến trúc hệ thốngThiết kế Pastebin.com (hoặc Bit.ly)

Thiết kế Pastebin.com (hoặc Bit.ly)

Nội dung bài

Thiết kế Pastebin.com (hoặc Bit.ly)

Bài tập22 phút đọcThe System Design Primer - bài giải "Design Pastebin.com (or Bit.ly)"

Mục lục
  1. Nội dung gốc
  2. Ghi chú của người dịch

Thiết kế Pastebin.com (hoặc Bit.ly)

Nội dung gốc

Lưu ý: Tài liệu này liên kết thẳng tới các phần liên quan trong danh mục chủ đề system design để tránh lặp lại. Hãy tham khảo nội dung được liên kết để nắm các ý chính cần trình bày, các đánh đổi (tradeoff) và các phương án thay thế.

Thiết kế Bit.ly - là một câu hỏi tương tự, chỉ khác ở chỗ pastebin phải lưu nội dung đoạn paste thay vì lưu url gốc chưa rút gọn.

Bước 1: Phác thảo các trường hợp sử dụng và ràng buộc

Thu thập yêu cầu và khoanh vùng bài toán. Đặt câu hỏi để làm rõ các trường hợp sử dụng (use case) và ràng buộc (constraint). Thảo luận các giả định.

Vì không có người phỏng vấn để trả lời các câu hỏi làm rõ, ta sẽ tự định nghĩa một số trường hợp sử dụng và ràng buộc.

Các trường hợp sử dụng (use cases)

Ta khoanh vùng bài toán, chỉ xử lý các trường hợp sử dụng sau
Ngoài phạm vi

Ràng buộc và giả định

Nêu các giả định
Tính toán mức sử dụng

Hãy hỏi rõ người phỏng vấn xem bạn có nên làm các phép ước lượng nhanh (back-of-the-envelope) về mức sử dụng hay không.

Bảng quy đổi tiện dụng:

Ước lượng nhanh: từ 10 triệu paste mỗi tháng ra dung lượng lưu trữ và số lượt ghi, đọc mỗi giây 10 triệu paste / tháng × 1,27 KB mỗi paste ÷ 2,5 triệu giây / tháng ≈ 12,7 GB / tháng ≈ 4 lượt ghi / giây × 36 tháng ≈ 450 GB × 10 ≈ 40 lượt đọc / giây
Hai phép tính tách biệt từ cùng một con số: nhân với 1,27 KB/paste ra dung lượng (12,7 GB/tháng, khoảng 450 GB sau 3 năm); chia cho 2,5 triệu giây/tháng ra tải (4 lượt ghi/giây, gấp 10 là 40 lượt đọc/giây).

Bước 2: Tạo thiết kế tổng quan (high level design)

Phác thảo thiết kế tổng quan với tất cả các thành phần quan trọng.

Sơ đồ thiết kế tổng quan Pastebin

Bước 3: Thiết kế các thành phần cốt lõi

Đi sâu vào chi tiết từng thành phần cốt lõi.

Ta có thể dùng một cơ sở dữ liệu quan hệ (relational database) như một bảng băm (hash table) cỡ lớn, ánh xạ url được sinh ra tới một máy chủ tệp (file server) và đường dẫn chứa tệp paste.

Thay vì tự quản lý một file server, ta có thể dùng một kho lưu trữ đối tượng (Object Store) được quản lý sẵn như Amazon S3 hoặc một kho tài liệu NoSQL (document store).

Một phương án khác thay cho cơ sở dữ liệu quan hệ đóng vai trò bảng băm lớn là dùng một kho khóa-giá trị NoSQL (key-value store). Ta nên thảo luận các đánh đổi giữa việc chọn SQL hay NoSQL. Phần thảo luận dưới đây dùng cách tiếp cận cơ sở dữ liệu quan hệ.

Hãy hỏi rõ người phỏng vấn xem bạn cần viết bao nhiêu code.

Bảng pastes có thể có cấu trúc như sau:

shortlink char(7) NOT NULL
expiration_length_in_minutes int NOT NULL
created_at datetime NOT NULL
paste_path varchar(255) NOT NULL
PRIMARY KEY(shortlink)

Đặt khóa chính (primary key) dựa trên cột shortlink sẽ tạo ra một chỉ mục (index) mà cơ sở dữ liệu dùng để đảm bảo tính duy nhất. Ta sẽ tạo thêm một chỉ mục trên created_at để tăng tốc tra cứu (thời gian logarit thay vì quét toàn bộ bảng) và để giữ dữ liệu trong bộ nhớ. Đọc tuần tự 1 MB từ bộ nhớ mất khoảng 250 micro giây, trong khi đọc từ SSD lâu hơn 4 lần và từ đĩa cứng lâu hơn 80 lần.1

Để sinh url duy nhất, ta có thể:

def base_encode(num, base=62):
    digits = []
    while num > 0
      remainder = modulo(num, base)
      digits.push(remainder)
      num = divide(num, base)
    digits = digits.reverse
url = base_encode(md5(ip_address+timestamp))[:URL_LENGTH]
Luồng sinh shortlink: MD5 của ip + timestamp, mã hoá base62, lấy 7 ký tự, kiểm tra trùng rồi mới lưu ip + timestamp MD5 → mã hoá Base62 Lấy 7 ký tự đầu Đã có trong SQL? chưa trùng Lưu SQL + Object Store trùng sinh lại
Write API băm MD5 từ ip + timestamp (hoặc dữ liệu ngẫu nhiên), mã hoá base62 rồi lấy 7 ký tự đầu; nếu mã đã có trong SQL thì sinh lại với đầu vào mới, chưa trùng mới lưu vào SQL và Object Store.

Ta sẽ dùng một REST API công khai:

$ curl -X POST --data '{ "expiration_length_in_minutes": "60", \
    "paste_contents": "Hello World!" }' https://pastebin.com/api/v1/paste

Phản hồi:

{
    "shortlink": "foobar"
}

Với giao tiếp nội bộ, ta có thể dùng lời gọi thủ tục từ xa (Remote Procedure Call - RPC).

Trường hợp sử dụng: Người dùng nhập url của một đoạn paste và xem nội dung

REST API:

$ curl https://pastebin.com/api/v1/paste?shortlink=foobar

Phản hồi:

{
    "paste_contents": "Hello World"
    "created_at": "YYYY-MM-DD HH:MM:SS"
    "expiration_length_in_minutes": "60"
}

Trường hợp sử dụng: Dịch vụ theo dõi số liệu phân tích của các trang

Vì không yêu cầu phân tích theo thời gian thực, ta chỉ cần chạy MapReduce trên log của Web Server để tạo số đếm lượt truy cập (hit count).

Hãy hỏi rõ người phỏng vấn xem bạn cần viết bao nhiêu code.

class HitCounts(MRJob):

    def extract_url(self, line):
        """Trích url được sinh ra từ dòng log."""
        ...

    def extract_year_month(self, line):
        """Trả về phần năm và tháng của timestamp."""
        ...

    def mapper(self, _, line):
        """Phân tích từng dòng log, trích xuất và biến đổi các dòng liên quan.

        Phát ra các cặp khóa-giá trị có dạng:

        (2016-01, url0), 1
        (2016-01, url0), 1
        (2016-01, url1), 1
        """
        url = self.extract_url(line)
        period = self.extract_year_month(line)
        yield (period, url), 1

    def reducer(self, key, values):
        """Cộng dồn các giá trị theo từng khóa.

        (2016-01, url0), 2
        (2016-01, url1), 1
        """
        yield key, sum(values)

Trường hợp sử dụng: Dịch vụ xóa các đoạn paste đã hết hạn

Để xóa các paste đã hết hạn, ta chỉ cần quét SQL Database để tìm mọi bản ghi có timestamp hết hạn cũ hơn timestamp hiện tại. Tất cả bản ghi hết hạn sau đó sẽ bị xóa (hoặc đánh dấu là đã hết hạn) khỏi bảng.

Bước 4: Mở rộng thiết kế (scale the design)

Xác định và xử lý các nút thắt cổ chai (bottleneck), dựa trên các ràng buộc.

Sơ đồ thiết kế Pastebin sau khi mở rộng

Quan trọng: Đừng nhảy thẳng từ thiết kế ban đầu sang thiết kế cuối cùng!

Hãy nói rõ rằng bạn sẽ làm theo vòng lặp: 1) Đo hiệu năng/Kiểm thử tải (Benchmark/Load Test), 2) Phân tích hiệu năng (Profile) để tìm nút thắt, 3) xử lý nút thắt đồng thời đánh giá các phương án thay thế và đánh đổi, và 4) lặp lại. Xem Thiết kế hệ thống mở rộng tới hàng triệu người dùng trên AWS làm ví dụ về cách mở rộng dần thiết kế ban đầu.

Điều quan trọng là thảo luận những nút thắt nào có thể gặp với thiết kế ban đầu và cách xử lý từng cái. Ví dụ, việc thêm một Load Balancer với nhiều Web Server giải quyết vấn đề gì? CDN thì sao? Bản sao Master-Slave (Master-Slave Replicas)? Các phương án thay thế và đánh đổi cho từng lựa chọn là gì?

Ta sẽ bổ sung một số thành phần để hoàn thiện thiết kế và xử lý các vấn đề về khả năng mở rộng. Các load balancer nội bộ không được vẽ để sơ đồ đỡ rối.

Để tránh lặp lại các thảo luận, hãy tham khảo các chủ đề system design sau để nắm ý chính, đánh đổi và phương án thay thế:

Analytics Database có thể dùng một giải pháp kho dữ liệu (data warehouse) như Amazon Redshift hoặc Google BigQuery.

Một Object Store như Amazon S3 có thể thoải mái đáp ứng ràng buộc 12,7 GB nội dung mới mỗi tháng.

Để xử lý 40 yêu cầu đọc mỗi giây trung bình (cao hơn vào giờ cao điểm), lưu lượng cho nội dung phổ biến nên được phục vụ bởi Memory Cache thay vì cơ sở dữ liệu. Memory Cache cũng hữu ích để xử lý lưu lượng phân bố không đều và các đợt tăng đột biến. SQL Read Replicas sẽ đủ sức xử lý các lần trượt cache (cache miss), miễn là các bản sao không bị quá tải vì phải nhân bản các thao tác ghi.

4 lượt ghi paste mỗi giây trung bình (cao hơn vào giờ cao điểm) là khả thi với một SQL Write Master-Slave duy nhất. Nếu không, ta sẽ cần áp dụng thêm các mẫu mở rộng SQL:

Ta cũng nên cân nhắc chuyển một phần dữ liệu sang cơ sở dữ liệu NoSQL.

Các ý bàn thêm (additional talking points)

Các chủ đề bổ sung để đào sâu, tùy vào phạm vi bài toán và thời gian còn lại.

NoSQL

Bộ nhớ đệm (caching)

Bất đồng bộ (asynchronism) và microservices

Giao tiếp (communications)

Bảo mật (security)

Tham khảo phần bảo mật.

Các con số độ trễ

Xem Các con số độ trễ mọi lập trình viên nên biết (Latency numbers every programmer should know).

Tiếp tục


Nguồn: The System Design Primer - bài giải "Design Pastebin.com (or Bit.ly)" — Donne Martin và cộng đồng đóng góp

Giấy phép: CC BY 4.0 (nguyên bản: Donne Martin, The System Design Primer)

Xem bản gốc

Bài tiếp theoThiết kế hệ thống mở rộng tới hàng triệu người dùng trên AWS