Kiến Trúc Rate Limiting Phân Tán: Token Bucket, Sliding Window & Redis Lua Script
So sánh định lượng Token Bucket, Leaky Bucket và Sliding Window Counter. Viết Lua script thực thi nguyên tử trên Redis để giới hạn tần suất truy cập chính xác mili-giây mà không sinh race condition.
Vì Sao Cần Rate Limiting Ở Tầng API Gateway?
Rate Limiting (Giới hạn tần suất truy cập) là tuyến phòng thủ đầu tiên bảo vệ hệ thống khỏi các mối đe dọa:
- Tấn công từ chối dịch vụ (DDoS) hoặc Brute-Force: Kẻ gian dùng botnet spam hàng trăm nghìn request vào API đăng nhập hoặc thanh toán.
- Scraping dữ liệu trái phép: Thu thập giá cả và thông tin nhạy cảm của sản phẩm với tần suất cao.
- Bảo vệ tính công bằng (Fair Usage Policy): Bảo đảm một khách hàng dùng API quá đà không thể chiếm đoạt tài nguyên của các khách hàng khác cùng hệ thống.
Mổ Xẻ 4 Thuật Toán Giới Hạn Tần Suất
1. Fixed Window Counter (Bộ Đếm Cửa Sổ Cố Định)
Chia thời gian thành các khung cố định (ví dụ: 1 phút từ 00:00 đến 00:59, tối đa 100 requests).
- Lỗ hổng chết người (Traffic Burst at Boundary): Nếu client gửi 100 requests lúc 00:59 và gửi tiếp 100 requests lúc 01:00, trong khoảng thời gian chỉ 2 giây hệ thống phải hứng chịu 200 requests, vượt gấp đôi giới hạn cho phép.
2. Sliding Window Log (Nhật Ký Cửa Sổ Trượt)
Lưu timestamp của từng request vào một mảng hoặc Redis Sorted Set. Khi có request mới, xóa toàn bộ timestamp cũ hơn thời điểm hiện tại trừ 60 giây, rồi đếm số lượng còn lại.
- Ưu điểm: Chính xác tuyệt đối từng mili-giây.
- Nhược điểm: Tốn kém bộ nhớ RAM khổng lồ khi có hàng triệu request.
3. Token Bucket (Thùng Chứa Token)
Một cái thùng có dung lượng tối đa $B$. Mỗi giây, hệ thống nạp thêm $R$ token vào thùng. Mỗi request đến phải lấy đi 1 token.
- Ưu điểm: Hỗ trợ xử lý các đợt lưu lượng tăng vọt ngắn hạn (Burst Traffic) một cách mềm dẻo.
Triển Khai Sliding Window Counter Bằng Redis Lua Script
Để bảo đảm tính nguyên tử (Atomicity), ta bắt buộc phải đóng gói logic kiểm tra và cập nhật vào một Lua Script chạy trên máy chủ Redis. Điều này ngăn ngừa hoàn toàn tình trạng Race Condition khi nhiều máy chủ backend cùng lúc kiểm tra hạn ngạch của cùng một IP:
lua(26 dòng)-- KEYS[1]: Tên key rate limit (ví dụ: "rate:ip:192.168.1.1") -- ARGV[1]: Thời điểm hiện tại tính bằng mili-giây (now) -- ARGV[2]: Kích thước cửa sổ tính bằng mili-giây (window, ví dụ 60000ms = 1 phút) -- ARGV[3]: Giới hạn số lượng request tối đa (limit, ví dụ 100) local key = KEYS[1] local now = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local limit = tonumber(ARGV[3]) local clearBefore = now - window -- 1. Xóa tất cả các request nằm ngoài cửa sổ thời gian redis.call('ZREMRANGEBYSCORE', key, 0, clearBefore) -- 2. Đếm số lượng request còn lại trong cửa sổ local currentRequests = redis.call('ZCARD', key) if currentRequests < limit then -- Thêm request hiện tại vào Sorted Set (member = now, score = now) redis.call('ZADD', key, now, now) -- Đặt thời gian sống cho key để tự dọn dẹp bộ nhớ redis.call('PEXPIRE', key, window) return { 1, limit - currentRequests - 1 } -- [1: Cho phép, Còn lại] else return { 0, 0 } -- [0: Bị chặn] end
Chuẩn Hóa HTTP Response Headers
Khi phản hồi cho client, API Gateway chuẩn mực quốc tế luôn đính kèm các header thông báo hạn ngạch:
http(10 dòng)HTTP/1.1 429 Too Many Requests Content-Type: application/json Retry-After: 35 X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1774358435 { "error": "Quá giới hạn tần suất truy cập. Vui lòng thử lại sau 35 giây." }
Nhận bản tin khảo luận kỹ thuật chuyên sâu
Ghi chép thực chiến về tháo gỡ điểm nghẽn hiệu năng, scale CSDL PostgreSQL chịu tải lớn và giải thuật phân tán. Không bài viết thương mại, không thư rác.