Hỏi rõ trước: giới hạn theo cái gì (API key, user, IP), có nhiều bậc hạn mức không, và hành vi khi bộ đếm không truy cập được là chặn hay cho qua. Câu cuối mới là phần phân biệt ứng viên.
Đặt ở đâu: ưu tiên tại API gateway / edge để request bị chặn không tiêu tài nguyên của service phía sau. Rate limit trong từng service chỉ dùng thêm cho các endpoint đắt tiền cụ thể (gửi OTP, xuất báo cáo).
Thuật toán: với hạn mức dùng chung, sliding window counter hoặc token bucket là hai lựa chọn thực dụng. Fixed window đơn giản nhưng cho phép gấp đôi hạn mức ở ranh giới cửa sổ — đây là câu bẫy hay gặp. Token bucket cho phép burst có kiểm soát, hợp với API công khai.
Cài đặt phân tán: bộ đếm ở Redis, cập nhật bằng script Lua để đọc-tính-ghi diễn ra nguyên tử:
-- token bucket: refill by elapsed time, then try to take one token
local now, rate, burst = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])
local state = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local tokens = tonumber(state[1]) or burst
local ts = tonumber(state[2]) or now
tokens = math.min(burst, tokens + (now - ts) * rate)
if tokens < 1 then return 0 end
redis.call('HMSET', KEYS[1], 'tokens', tokens - 1, 'ts', now)
redis.call('EXPIRE', KEYS[1], 60)
return 1Hợp đồng với client: trả 429 kèm Retry-After và các header hạn mức còn lại. Thiếu Retry-After thì client sẽ thử lại ngay và làm tình hình xấu hơn.
Điểm nghẽn:
- Mỗi request thêm một round-trip Redis. Giảm bằng cách cấp phát hạn mức theo lô cho từng instance (mỗi instance xin trước 50 token) — đổi độ chính xác lấy độ trễ.
- Hot key: một khách hàng lớn dồn vào một key. Chia key theo shard, hoặc dùng shuffle sharding để một khách hàng ồn ào không ảnh hưởng toàn bộ.
Khi Redis chết: phải chọn trước, và nói rõ lý do. Fail-open (cho qua) giữ dịch vụ sống nhưng mất lá chắn chống lạm dụng; fail-closed an toàn nhưng biến sự cố cache thành sự cố toàn hệ thống. Thực tế phổ biến: fail-open cho traffic thường + giữ một giới hạn cục bộ trong tiến trình làm lưới an toàn, fail-closed cho các endpoint tốn tiền như gửi SMS.