Giới hạn tần suất: túi token hay cửa sổ trượt
Ảnh: Stytch

Giới hạn tần suất: túi token hay cửa sổ trượt

'100 yêu cầu mỗi phút' có ít nhất bốn cách hiểu, cho ra hành vi khác hẳn ở các ranh giới. Cửa sổ cố định, cửa sổ trượt, túi token, xô rò — chọn cái nào cho việc gì.

Mọi API công khai đều cần giới hạn tần suất — không có nó, một khách hàng viết vòng lặp sai hoặc một kẻ tấn công đơn lẻ có thể làm sập dịch vụ cho tất cả. Nhưng câu tưởng chừng rõ ràng "100 yêu cầu mỗi phút" thực ra có ít nhất bốn cách hiểu, và chúng cho ra hành vi khác hẳn nhau ở những ranh giới quan trọng.

Cửa sổ cố định — đơn giản và có lỗ hổng

Đếm số yêu cầu trong mỗi phút tròn, sang phút mới thì đặt lại về 0. Dễ viết nhất, tốn ít bộ nhớ nhất, nhưng có một lỗ hổng biên nghiêm trọng: người dùng gửi 100 yêu cầu ở giây thứ 59 và 100 yêu cầu nữa ở giây thứ 61 — tức 200 yêu cầu trong hai giây mà vẫn hợp lệ theo luật, vì chúng rơi vào hai phút khác nhau. Kẻ muốn tấn công biết chính xác cách khai thác điều này.

Cửa sổ trượt — chính xác nhưng đắt

Đếm số yêu cầu trong 60 giây vừa qua tính từ đúng thời điểm hiện tại. Không còn lỗ hổng biên, vì cửa sổ luôn di chuyển theo. Cái giá là phải lưu dấu thời gian của từng yêu cầu để biết cái nào đã rơi ra khỏi cửa sổ — tốn bộ nhớ khi lượng yêu cầu lớn. Cách dung hoà phổ biến là chỉ giữ hai bộ đếm (cửa sổ hiện tại và cửa sổ trước) rồi nội suy tuyến tính giữa chúng, đủ chính xác mà rẻ.

Túi token — cho phép bùng nổ có kiểm soát

Hình dung một cái túi chứa tối đa N token, được nạp thêm đều đặn theo thời gian. Mỗi yêu cầu tiêu một token; hết token thì bị từ chối. Điểm hay nằm ở chỗ nó cho phép bùng nổ có kiểm soát: người dùng im lặng một lúc thì túi tích đầy, rồi có thể gửi một loạt nhanh. Với API thật, đây thường chính là hành vi mong muốn — con người và chương trình đều hay gọi theo cụm chứ không rải đều từng giây.

Xô rò — làm phẳng hoàn toàn

Yêu cầu xếp vào một hàng đợi và được lấy ra xử lý với tốc độ cố định, như nước rò qua một cái lỗ nhỏ ở đáy xô. Nó làm phẳng lưu lượng tuyệt đối, phù hợp khi thứ nằm phía sau — một cơ sở dữ liệu cũ, một dịch vụ bên thứ ba mong manh — không chịu được đột biến. Cái giá là độ trễ chờ đợi trong hàng, và yêu cầu có thể bị bỏ nếu hàng đầy.

Chọn thế nào

  • API cho lập trình viên — túi token, vì bùng nổ ngắn là hành vi bình thường và hợp lý.
  • Bảo vệ một thứ mong manh phía sau — xô rò, để dòng chảy vào luôn phẳng dù đầu vào có giật cục.
  • Chống lạm dụng và spam — cửa sổ trượt, vì ở đây bạn cần con số chính xác và không muốn để lọt lỗ hổng biên.

Và dù chọn thuật toán nào, hãy luôn trả về trong header cho biết còn bao nhiêu lượt và khi nào giới hạn đặt lại. Một giới hạn không nói rõ chính là nguồn gốc của những vòng lặp thử-lại mù quáng — khách hàng bị từ chối mà không biết vì sao sẽ thử lại ngay lập tức, làm tình hình tệ hơn đúng lúc dịch vụ đang cần được thở.

Chia sẻ

Thảo luận