Professional Documents
Culture Documents
240
SHARES
Share on Facebook Share on Twitter
Làm việc nhiều với API chắc hẳn ai cũng đã từng nghe thấy từ API Rate Limiting. Ví dụ như với
Github API thì Rate Limiting của nó như sau:
Những chú ý khi chọn MySQL
làm database
NEXT POST
Nhìn vào hình trên có thể thấy Rate Limiting của nó 60 reques/min. Nếu mà trong vòng 1 phút gửi
quá 60 reques thì sẽ không gửi tiếp được nữa. Mà phải đợi đến phút tiếp theo mới gửi được.
Hiện tại hầu hết các hệ thống lớn trên thế giới đều cung cấp Rate Liming cả. Nhưng mà ít ai để ý đến
công nghệ đằng sau nó như thế nào.
Hôm nay mình viết bài này để hướng dẫn mọi người cách thiết kế Rate Limiting như thế nào.
Mục lục
1. Rate Limiting là gì?
2. Tại sao API cần phải có Rate Limiting?
3. Yêu cầu hệ thống
4. Rate Limiting hoạt động như thế nào?
5. Thuật toán sử dụng trong Rate Limiting
6. Kiến trúc Rate Limiting
7. Cài đặt thuật toán
7.1. 1. Thuật toán Token Bucket
7.2. 2. Thuật toán Sliding Window
8. Kết luận
Để giải quyết được vấn đề này thì cơ chế Rate Limiting đã ra đời. Mục đích của nó chỉ cho phép
nhận 1 số lượng reques nhất định trong 1 đơn vị thời gian. Nếu quá sẽ trả về response lỗi.
Mỗi người dùng chúng ta chỉ cho phép gửi 100request/s. Nếu vượt quá thì sẽ trả về response
lỗi.
Mỗi người dùng chỉ cho phép nhập sai thẻ credit 3 lần trong 1 ngày
Mỗi địa chỉ IP chỉ có thể tạo được 2 account trong 1 ngày.
Về cơ bản thì rate limiter sẽ cho phép gửi bao nhiêu reques trong 1 khoảng thời gian nào đó.
Nếu vượt quá sẽ bị block lại.
Brute force thông tin thẻ credit card (quyét kiểu vét cạn)
Những cuộc tấn công này nhìn có vẻ như đến từ người dùng thực, nhưng thực tế nó được tạo ra bởi
bot. Do vậy mà nó rất khó bị phát hiện và dễ dàng làm service của chúng ta bị sập.
Ngoài ra, Rate Limiting cũng mang lại cho chúng ta 1 số lợi ích sau:
Bảo mật: Không cho phép nhập sai password quá nhiều lần
Doanh thu: Với mỗi plan sẽ có rate limit khác nhau. Nếu muốn dùng nhiều hơn thì cần mua lên
plan đắt tiền hơn.
Giới hạn số lượng request được gửi trong 1 đơn vị thời gian. Ví dụ 15 request/s
Hệ thống chịu tải cao. Bởi vì rate limiter luôn luôn phải hoạt động để bảo vệ hệ thống từ các
cuộc tấn công bên ngoài vào.
Rate limiting cần có latency thấp nhất để giúp trải nghiệm người dùng tốt hơn.
Khi vượt quá giới hạn, nó sẽ trả về response với HTTP Status là 429 – Too many requess.
Thuật toán này sẽ được tính từ đầu đơn vị thời gian cho đến hết đơn vị thời gian.
Ví dụ như trong ví dụ bên trên ta có thể thấy, trong khoảng thời gian từ giây thứ 0 cho đến giây thứ 1
có 2 reques được gửi, đó là m1 và m2.
Trong khoảng thời gian từ giây thứ 1 đến giây thứ 2 có 3 reques là m3, m4, m5.
Nếu như rate limiting đang được cài đặt là 2 thì trong ví dụ bên trên thì thằng m5 sẽ bị block lại.
Trong thuật toán này thì thời gian sẽ được tính từ phần tử thời gian mà yêu cầu được thực hiện cộng
với độ dài của time window.
Như trong ví dụ bên trên, giả sử như m1 và m2 được gửi từ giây thứ 0.3s. Khi đó từ 0.3s này sẽ bắt
đầu tính.
Trong khoảng 0.3s tiếp theo chúng ta thấy có 2 reques tiếp theo được gửi đến đó là m3 và m4.
Như vậy trong khoảng thời gian 0.6s này đã có 4 reques được gửi là m1,m2,m3,m4.
Nếu Rate Limiting đang cài đặt là 2 thì khi đó m3,m4 sẽ bị block lại.
Giả sử như thời điểm gửi m5 là lúc 1.1s chẳng hạn. Thì khi đó lúc này rate limiting sẽ reset lại thành
từ 0. Và lại lặp lại quá trình đó, và m5 sẽ được tính thành 1 lần.
Rate Limiting sẽ đảm nhiệm việc quyết định xem reques nào sẽ được gửi đến API Server, reques
nào sẽ bị block.
Mỗi khi Client gửi reques đến Web Server, Web Server sẽ gọi đến Rate Limiter để check xem ngươi
dùng này đã vượt quá số Rate Limiting hay chưa? Nếu chưa thì sẽ gửi reques đó đến API Server.
Nếu đã vượt quá rồi thì sẽ block lại và trả về http satus 429 (Too many requess).
Sau đó sẽ lưu count vs timesamp vào 1 hash table (Có thể là Redis, memcached …). Trong đó key
sẽ là user_id và value sẽ chứa json bao gồm số lượng reques đã gửi, cùng với thời điểm timesamp.
Dùng mysql để lưu trữ dữ liệu là 1 ý tưởng khá tồi. Bởi vì thời gian đọc ghi dữ liệu ở mysql sẽ lâu
hơn rất nhiều so với việc dùng redis (memory).
Ví dụ như chúng ta mất 1ms để lưu 1 record vào mysql thì khi đó 1s chúng ta sẽ xử lí được 1000
reques.
Thế nhưng với hệ thống như Redis nó không sử dụng disk mà sử dụng memory để lưu nên tốc độ
đọc ghi sẽ khá là nhanh. Ví dụ nó mất 100 nanosec để lưu 1 record thì 1s nó sẽ xử lí được tầm 10
triệu reques.
Mà hệ thống Rate Limiting này cần phải tối đa tốc độ query để lấy count nên Redis sẽ hợp lí hơn rất
nhiều.
1
2 user_id: {count, start_time}
3
4 Ví dụ:
5 12: {3, 1560580210}
6 5: {2, 1560520442}
7
Giả sử như rate limiter sẽ cho phép 3 reques/min. Khi đó fow thực hiện sẽ như sau:
1. Nếu user_id không có trong hash table, khi đó sẽ insert user_id vào trong hash table cùng với
thời điểm gửi (dạng epoch time)
2. Nếu user_id có nằm trong hash table, khi đó sẽ tìm value tương ứng với user_id đó và tính toán
xem current_time – start_time >= 1 min hay không?
1. Nếu có lớn hơn thì khi đó set start_time bằng current_time và count = 1 và cho phép
request gửi đến API Server.
2. Nếu nhỏ hơn thì kiểm tra xem count có lớn hơn 3 hay không. Nếu lớn hơn thì sẽ block
request đó lại. Nếu nhỏ hơn thì sẽ count = count + 1 và cho phép request gửi đến API
Server.
Trong hệ thống phân tán thì hành vi “read and then write – đọc sau đó ghi” sẽ tạo ra 1 race condition
(khi nhiều thread cùng truy cập và cùng 1 lúc muốn thay đổi dữ liệu), và làm cho dữ liệu tính toán sẽ
không đúng nữa.
Ví dụ như tại giây 45.01 count hiện tại đang là 2. Khi đó người dùng đó call api. Vì count vẫn đang
nhỏ hơn 3 (giả sử như rate limiting đang là 3 reques/min). Trong khi đang chuẩn bị update count này
thành 3 thì ngay lúc đó có 1 reques nữa được gửi đến. Và lúc này reques đó cũng vẫn đang xác
nhận count = 2 và rate limiter cho phép call api. Và kết quả là 2 thằng này đều update count bằng 3.
Mặc dù chính xác count phải bằng 4.
Để giải quyết được vấn đề này thì chúng ta có thể dùng Redis lock để lock lại count trước khi update
(tức là phải update xong mới cho select).
Tuy nhiên điều này có thể sẽ làm cho logic của chúng ta bị chậm khi nhiều reques được gửi đồng
thời từ 1 người dùng. Bởi vì khi select sẽ lock lại count của người dùng đó lại. Sau khi update count
xong mới giải phóng lock để cho reques tiếp theo dùng.
Chú ý là cái việc lock này chỉ lock lại record của người dùng đó thôi nhé. Còn không lock lại record
của người dùng khác. Hay nói cách khác chỉ lock row chứ không lock cả table.
Cần bao nhiêu memory để lưu trữ tất cả dữ liệu của người dùng?
Vì hash table của chúng ta sẽ lưu (user_id, count, sart_time) nên chúng ta sẽ cần:
2 bytes cho count (vậy giá trị max của count sẽ là 2^16 = 65,536)
Giả sử như chúng ta cần 20 bytes cho overhead của hash table. Khi đó với 1 triệu người dùng sẽ
cần:
Giả sử chúng ta cần 4 bytes để khoá mỗi record của người dùng để giải quyết vấn đề bên trên thì
tổng cộng chúng ta sẽ cần:
Với rate limiting là 3 reques/min thì hoàn toàn có thể đặt redis trên 1 con server. Tuy nhiên n ếu
chúng ta tăng rate limiting thành 10 reques/s thì khi đó redis sẽ phải chịu khoảng 10 triệu reques/s.
Khi đó 1 con redis sẽ không thể chịu được nhiệt.
Để giải quyết bài toán này thì chúng ta sẽ dùng nhiều con redis đặt phân tán. Khi update count vào
trong redis thì rate limiter sẽ cần phải update trong tất cả những con Redis đó thì mới được.
1
2 user_id: {count, Sorted Set <start_time>}
3
4 Ví dụ:
5 12: {1560580210, 1560580212, 1560580213}
6 5: {1560520442}
7
Giả sử như rate limiter cho phép gửi 3 reques/min. Khi đó fow sẽ như sau:
1. Xoá tất cả các start_time trong Sorted Set mà có giá trị current_time – 1 min < 0
2. Tính tổng số lượng phần tử trong Sorted Set. Nếu tổng này > 3 thì sẽ block request đó lại.
3. Insert start_time đó vào trong Sorted Set và cho phép request gửi đến API Server.
Với thuật toán này thì cần bao nhiêu memory để lưu dữ liệu?
1
2 user_id: {start_time_1, start_time_2 ...., start_time_n}
3
Giả sử như overhead của Sorted Set là 20 bytes, overhead của hash table là 20 bytes. Rate Limiting
là 500 reques/hour. Khi đó chúng ta sẽ cần:
8 + (4 + 20 bytes Sorted Set overhead) * 500 + 20 bytes hash table overhead = 12KB
Nếu chúng ta có 1 triệu user thì khi đóng tổng dung lượng bộ nhớ cần sẽ là:
12 KB * 1 triệu = 12GB
So sánh với thuật toán Fixed Window thì thuật toán Sliding Window dùng khá nhiều memory để lưu
trữ.
Kết luận
Các bạn thấy việc thiết kế Rate Limiting cho API thế nào? Cũng không quá khó phải không nào.
Hi vọng qua bài này sẽ giúp các bạn biết cách thiết kế Rate Limiting, giúp cho API của mình trở nên
chuyên nghiệp hơn.
==============
Để nhận thông báo khi có bài viết mới nhất thì các bạn có thể like fanpage của mình ở bên dưới nhé:
Tags: api api rate limiter api rate limiting rate limiter rate limiting
Related Poss
DATABASE
Hệ thống Viblo Code Challenge được xây Insagram đã sinh ra ID trong database của
dựng như thế nào họ như thế nào
BY TANGO 1 MONTH AGO BY TANGO 2 MONTHS AGO
Tes tải hệ thống thực sự cần thiết? Thiết kế hệ thống URL Shortening giống
BY TANGO 2 MONTHS AGO Bit.ly chịu tải 6 tỷ click 1 tháng
BY TANGO 2 MONTHS AGO
SYSTEM DESIGN
Comments 8
Reply
thanks bạn
Reply
Cho mình hỏi khái niệm “overhead” ở đây nghĩa là gì vậy Ad?
Reply
Nếu ai tìm hiểu sâu về database hay các loại hash khác thì chắc cũng đều nghe qua
overhead. Thằng này nó đảm nhiệm nhiều việc dựa vào từng đối tượng.
Đối với database thì nó sẽ là không gian disk dùng cho thực thi 1 số câu lệnh sql như select,
insert, delete … Nên trong quá trình vận hành mysql sẽ thấy nó chậm đi vì phân vùng này đã
dùng hết rồi. Cần phải tối ưu nó.
Còn đối với Sorted Set hay hash table thì nó là không gian để lưu con trỏ đến bản ghi tiếp
theo …
Nên có thể hiểu 1 cách đơn giản overhead là khoảng không gian disk dùng để thực thi 1 số
việc gì đó.
Reply
bài viết chất lượng. thanks ad chia sẻ. Rất cần những bài kiểu này
Reply
thanks bạn
Reply
Bài hay chuyên sâu quá. Cho mình hỏi thằng Facebook làm sao lock được reques kết bạn, like
nhiều, share nhiều z. Thanks
Reply
Theo mình tìm hiểu thì facebook hay Github hay 1 số hệ thống lớn khác nữa. Họ vẫn áp
dụng rate limiting cho API của họ nhưng mà họ áp dụng theo cơ chế khác.
Họ sẽ tính count dựa vào tỉ lệ phần trăm của tổng thời gian cpu đã được sử dụng cho
reques.
1 số thuật toán về rate limiting mình giới thiệu ở bên trên thì chỉ 1 phần thôi, không phải tất
cả. Còn 1 số thuật toán khác nữa. Bạn có thể tham khảo thêm. Tuỳ vào đặc tả của hệ thống
mà nên chọn thuật toán nào cho phù hợp.
Reply
Leave a Reply
Your email address will not be published. Required felds are marked *
Comment
Name *
Email * Website
Save my name, email, and website in this browser for the next time I comment.
Post Comment
Media chia sẻ những kinh nghiệm, kiến thức, các case sudy từ các công ty lớn giúp engineer, designer có thể tạo
ra những ứng dụng, dịch vụ chuyên nghiệp hơn.