Thiết Kế Hệ Thống: Xây Dựng một Bộ Giới Hạn Tốc Độ Phân Tán (Distributed Rate Limiter)

Giới thiệu
Bất kỳ API nào cung cấp ra công chúng đều cần có cơ chế giới hạn tốc độ (rate limiting). Nếu không có nó, hệ thống của bạn sẽ dễ bị tấn công brute-force, DDoS, và các vấn đề "hàng xóm ồn ào" (noisy neighbor) khi một client hoạt động quá tích cực làm suy giảm trải nghiệm của tất cả những người khác.
Thiết kế một bộ giới hạn tốc độ cho một máy chủ đơn lẻ tương đối đơn giản. Nhưng thiết kế một hệ thống như vậy cho kiến trúc microservices phân tán toàn cầu, xử lý hàng triệu yêu cầu mỗi giây, lại là một thách thức kinh điển trong thiết kế hệ thống. Trong bài viết này, chúng ta sẽ phân tích chi tiết về kiến trúc và các thuật toán cần thiết.
1. Lựa chọn Thuật toán Phù hợp
Trái tim của một bộ giới hạn tốc độ là thuật toán mà nó sử dụng để đếm các yêu cầu.
Token Bucket
- Cách hoạt động: Tưởng tượng một cái xô chứa các token. Token được thêm vào với một tốc độ cố định. Mỗi yêu cầu sẽ tiêu tốn một token. Nếu xô rỗng, yêu cầu sẽ bị từ chối.
- Ưu điểm: Hiệu quả về bộ nhớ, cho phép các đợt truy cập tăng đột biến trong thời gian ngắn (burst traffic).
- Nhược điểm: Khó tinh chỉnh giữa kích thước burst và tốc độ duy trì.
Fixed Window Counter (Bộ đếm cửa sổ cố định)
- Cách hoạt động: Chia thời gian thành các cửa sổ cố định (ví dụ: từ 12:00 đến 12:01). Tăng một bộ đếm cho mỗi yêu cầu. Reset bộ đếm vào đầu cửa sổ tiếp theo.
- Ưu điểm: Rất dễ triển khai.
- Nhược điểm: Gây ra tình trạng lưu lượng truy cập tăng vọt ở các cạnh của cửa sổ. Một client có thể gửi toàn bộ hạn ngạch của mình vào lúc 12:00:59 và gửi lại vào lúc 12:01:01, thực chất đã tăng gấp đôi tốc độ cho phép trong một khoảng thời gian ngắn.
Sliding Window Log (Nhật ký cửa sổ trượt)
- Cách hoạt động: Lưu lại nhật ký timestamp của mỗi yêu cầu từ một người dùng. Từ chối yêu cầu nếu kích thước nhật ký vượt quá giới hạn trong khung thời gian.
- Ưu điểm: Độ chính xác rất cao.
- Nhược điểm: Tốn nhiều bộ nhớ và tài nguyên tính toán để duy trì và cắt tỉa nhật ký. Không khả thi ở quy mô lớn.
Sliding Window Counter (Bộ đếm cửa sổ trượt - Lựa chọn tối ưu)
- Cách hoạt động: Một phương pháp lai. Nó theo dõi bộ đếm của cửa sổ cố định trước đó và cửa sổ hiện tại, rồi tính toán một giá trị trung bình có trọng số dựa trên thời gian hiện tại chồng lấn lên cửa sổ.
- Ưu điểm: Làm mượt các đợt tăng vọt ở cạnh cửa sổ, rất hiệu quả về bộ nhớ, độ chính xác cao.
2. Kiến trúc cho Hệ thống Phân tán
Trong một hệ thống đa máy chủ, việc duy trì trạng thái trở thành thách thức chính. Nếu một bộ cân bằng tải (load balancer) định tuyến yêu cầu đầu tiên của Người dùng A đến Máy chủ 1 và yêu cầu thứ hai đến Máy chủ 2, làm thế nào chúng có thể chia sẻ bộ đếm giới hạn tốc độ?
Lựa chọn A: Sticky Sessions (Phiên dính)
Định tuyến tất cả lưu lượng của một người dùng cụ thể đến cùng một máy chủ. Máy chủ đó sẽ giữ trạng thái giới hạn tốc độ trong bộ nhớ cục bộ.
- Kết luận: Ý tưởng tồi. Nó phân phối tải không đồng đều và không có khả năng chịu lỗi tốt khi một máy chủ bị sập.
Lựa chọn B: Kho dữ liệu tập trung (Redis)
Tất cả các máy chủ đọc và ghi vào một cụm Redis tập trung. Redis đủ nhanh (vì là in-memory) để đáp ứng các yêu cầu về độ trễ.
- Kết luận: Đây là phương pháp tiêu chuẩn. Tuy nhiên, nó tạo ra độ trễ mạng và các tình huống tranh chấp (race conditions).
3. Xử lý Race Conditions trong Redis
Nếu hai yêu cầu từ cùng một người dùng đồng thời đến hai máy chủ API khác nhau, cả hai có thể cùng đọc bộ đếm là 4, cùng tăng lên 5, và cùng cho phép yêu cầu đi qua, qua mặt giới hạn đã đặt ra.
Giải pháp: Lua Scripts
Redis cho phép bạn thực thi các script Lua một cách nguyên tử (atomically). Bạn có thể viết một script để lấy bộ đếm, kiểm tra giới hạn, tăng giá trị, và cập nhật TTL trong một thao tác nguyên tử duy nhất.
-- Script Lua đơn giản cho Token Bucket
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then
return 0 -- Bị từ chối
else
redis.call('INCRBY', key, 1)
redis.call('EXPIRE', key, 60)
return 1 -- Được phép
end
4. Tối ưu Hiệu năng: Cache Cục bộ + Đồng bộ Bất đồng bộ (Async Sync)
Đối với các hệ thống có lưu lượng cực cao, nơi mà ngay cả độ trễ của Redis cũng không thể chấp nhận được, bạn có thể sử dụng một phương pháp lai.
- Các máy chủ API giữ một bộ giới hạn tốc độ cục bộ, trong bộ nhớ.
- Chúng đồng bộ các bộ đếm cục bộ của mình lên cụm Redis trung tâm một cách bất đồng bộ sau mỗi vài giây.
- Đánh đổi: Điều này đánh đổi độ chính xác nghiêm ngặt để lấy hiệu năng. Một client có thể vượt quá giới hạn của họ một chút giữa các cửa sổ đồng bộ, nhưng hệ thống vẫn duy trì tính sẵn sàng cao và tốc độ nhanh.
Kết luận
Xây dựng một bộ giới hạn tốc độ phân tán đòi hỏi phải cân bằng giữa việc thực thi nghiêm ngặt và độ trễ của hệ thống. Đối với hầu hết các API hiện đại, việc kết hợp thuật toán Sliding Window được triển khai thông qua các script Lua nguyên tử trên Redis cung cấp sự cân bằng tốt nhất về độ chính xác, hiệu năng và khả năng mở rộng.
Bạn cũng có thể thích
- How to Harden Firefox on Linux (Beginner's Guide)
- VPN vs Proxy: Differences, Fingerprinting, Leaks, and Privacy Testing
- The 5 Best Playwright Alternatives for E2E Testing in 2026
- Cypress to Playwright Migration Consulting: How to Upgrade Your Testing Strategy
Các câu hỏi thường gặp
Công cụ lập trình trực tiếp trên trình duyệt
Làm sạch log AI CLI, tạo biểu thức cron, giải mã JWT và tính quyền chmod offline.
Bài viết liên quan

Tương lai của System Design với micro-frontend
Xây dựng kiến trúc micro-frontend cấp doanh nghiệp có khả năng mở rộng bằng Webpack Module Federation, single-spa, hệ thống thiết kế dùng chung, CI/CD độc lập và định tuyến SSR biên.
Đọc tiếp
Thiết kế cảnh báo Prometheus Grafana và Burn Rate
Thiết kế cảnh báo Prometheus và Grafana cho môi trường sản xuất bằng cách sử dụng các quy tắc PromQL multi-window multi-burn-rate cho mục tiêu mức dịch vụ (SLO) và ngân sách lỗi.
Đọc tiếp
FastAPI vs Celery: Khi nào nên dùng BackgroundTasks và khi nào cần Task Queue phân tán?
So sánh chuyên sâu FastAPI BackgroundTasks và Celery: kiến trúc in-process vs phân tán, rủi ro nghẽn event loop, benchmark bộ nhớ 10k task và cấu hình chuẩn 2026.
Đọc tiếp