Họ đang đo gì
Bạn có nhìn ra lỗ hổng ở biên cửa sổ không — đây là chi tiết phân biệt người đã cài thật với người đọc mô tả.
Trả lời ngắn~30 giây
Cửa sổ cố định đơn giản nhất nhưng cho phép bùng nổ gấp đôi: 100 request lúc 10:00:59 và 100 nữa lúc 10:01:00 là 200 trong một giây. Sliding window log chính xác tuyệt đối nhưng phải lưu mọi mốc thời gian. Token bucket là lựa chọn thực dụng nhất: hai con số (số token và tốc độ nạp), cho phép bùng nổ có kiểm soát, và cài bằng Redis rất gọn. Mình mặc định dùng token bucket, trừ khi cần chính xác tuyệt đối theo hợp đồng.
Giải thích sâu
Token bucket được ưa chuộng vì nó mô hình hoá đúng thứ ta thật sự muốn: cho phép người dùng dồn một ít lưu lượng (họ mở trang, mười request cùng lúc là bình thường) nhưng chặn tốc độ trung bình. Kích thước bucket là mức bùng nổ cho phép, tốc độ nạp là hạn mức bền vững. Hai tham số này diễn đạt được chính sách mà một con số duy nhất không diễn đạt nổi.
Sliding window counter là dung hoà đáng biết: giữ hai bộ đếm — cửa sổ hiện tại và cửa sổ trước — rồi nội suy theo tỷ lệ đã trôi qua. Nó xoá được lỗ hổng ở biên với chi phí bộ nhớ gần bằng cửa sổ cố định, và đó là cách nhiều CDN dùng. Nó xấp xỉ chứ không chính xác, nhưng sai số nhỏ hơn nhiều so với hại của việc lưu từng mốc thời gian.
Phần cài đặt hay bị bỏ qua: giới hạn phải nguyên tử khi có nhiều instance. Đọc rồi ghi vào Redis từ ba máy cùng lúc sẽ cho qua nhiều hơn hạn mức. Cách đúng là một script Lua chạy nguyên tử trên Redis, hoặc INCR với EXPIRE đặt trong cùng một pipeline có điều kiện. Và luôn trả Retry-After cùng 429 — giới hạn mà không nói khi nào được thử lại chỉ khiến client thử ngay lập tức.
Câu hỏi tiếp theo họ sẽ hỏi
?Giới hạn theo cái gì — IP, user, hay API key?
Theo thứ định danh được chủ thể chịu trách nhiệm. IP là lựa chọn cuối vì NAT gộp cả một văn phòng vào một địa chỉ, và IPv6 thì kẻ tấn công có thừa địa chỉ. Với người dùng đã đăng nhập thì theo user id; với endpoint đăng nhập thì theo cả IP và tên tài khoản, vì lúc đó chưa có ai để mà tính.
Trả lời thế này là mất điểm
- Đếm trong bộ nhớ của từng instance. Với ba pod thì hạn mức thực tế gấp ba lần con số bạn đặt.