Đừng để Server sập vì "Spam Click": Làm chủ Rate Limiter trong Laravel từ cơ bản đến thực chiến
Chào anh em cộng đồng Viblo!
Làm Backend, chắc hẳn anh em đã từng gặp cảnh: Đêm hôm khuya khoắt hệ thống bắn cảnh báo CPU 100%, RAM cạn kiệt. Lò dò lên check log thì phát hiện ra không phải do logic code mình viết dở, mà do... một thanh niên nào đó đang dùng tool spam API Login 1000 lần/giây, hoặc do một máy trạm phần cứng bị lỗi kẹt nút, liên tục gửi request đồng bộ giao dịch lên server.
Dù là hệ thống E-commerce đang chạy Flash Sale hay các API giao tiếp với thiết bị ngoại vi (như hệ thống cổng soát vé AFC), việc phơi API ra ngoài mà không có "khiên đỡ" chẳng khác nào mời trộm vào nhà.
Hôm nay, chúng ta sẽ cùng trang bị tấm khiên vững chắc mang tên Rate Limiter trong Laravel. Không chỉ dừng ở mức dùng cho có, mình sẽ đi sâu vào cách custom nó cho các hệ thống phức tạp!
1. Rate Limiter là gì? Tại sao nó là "Chốt chặn sinh tử"?
Nói một cách dễ hiểu, Rate Limiting (Giới hạn tốc độ) là kỹ thuật kiểm soát số lượng request (yêu cầu) mà một người dùng (hoặc một địa chỉ IP) được phép gửi đến server trong một khoảng thời gian nhất định.
Nếu vượt quá giới hạn này, server sẽ thẳng thừng từ chối phục vụ và trả về mã lỗi HTTP 429 - Too Many Requests thay vì cố gắng xử lý và dẫn đến quá tải.
Tại sao phải dùng?
- Chống Brute-force attacks: Ngăn chặn hacker dò password liên tục.
- Bảo vệ tài nguyên (Prevent DoS): Đảm bảo không một user "ngáo" nào có thể chiếm dụng toàn bộ kết nối DB của server.
- Kiểm soát chi phí: Nếu API của bạn gọi sang một dịch vụ bên thứ 3 tính tiền theo lượt (như gửi SMS OTP), việc giới hạn sẽ giúp công ty không bị "đốt" sạch tiền trong 1 đêm.
2. Cách dùng "Ăn liền" (Basic Usage)
Laravel cung cấp sẵn middleware throttle cực kỳ tiện lợi. Đa số fresher sẽ biết cách này:
// Giới hạn 60 request mỗi phút cho mỗi IP
Route::middleware('throttle:60,1')->group(function () {
Route::post('/login', [AuthController::class, 'login']);
Route::post('/register', [AuthController::class, 'register']);
});
Chỉ một dòng code, Laravel sẽ tự động tạo các key trong Cache (mặc định là file hoặc Redis)
dựa trên IP của người dùng để đếm số lượt truy cập. Khi đạt mốc 60, request thứ 61 sẽ bị chặn đứng với mã 429.
Tuy nhiên, ở quy mô hạng nặng, cách này lộ ra nhiều điểm yếu: Cứng nhắc, không phân biệt được user VIP và user thường, và khó maintain khi có hàng chục Route khác nhau.
3. Lột xác với Custom Rate Limiters (The Senior Way)
Từ Laravel 8+, framework đã giới thiệu facade RateLimiter. Kỹ thuật này cho phép chúng ta định nghĩa các "chiến lược" giới hạn linh hoạt ngay trong AppServiceProvider (hoặc RouteServiceProvider với các bản Laravel cũ hơn).
Tình huống thực chiến: API Đặt hàng Flash Sale
Giả sử bạn đang viết API /api/checkout cho một chương trình săn sale. Bạn muốn:
- User vãng lai (Guest): Chỉ được bấm đặt hàng 2 lần / 1 phút (chống spam bot).
- User đã login: Được bấm 10 lần / 1 phút.
- User VIP (Premium): Không giới hạn (hoặc giới hạn rất cao 100 lần / 1 phút).
Chúng ta sẽ khai báo logic này như sau:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// Định nghĩa một Rate Limiter tên là 'checkout_api'
RateLimiter::for('checkout_api', function (Request $request) {
// 1. Nếu là User VIP (Có role premium)
if ($request->user() && $request->user()->isVip()) {
// Cho phép 100 request / phút, định danh theo User ID
return Limit::perMinute(100)->by($request->user()->id);
}
// 2. Nếu là User đã đăng nhập bình thường
if ($request->user()) {
return Limit::perMinute(10)->by($request->user()->id);
}
// 3. Nếu là Khách (Guest) - Định danh bằng IP
return Limit::perMinute(2)->by($request->ip());
});
}
}
Và ở file routes/api.php, bạn chỉ việc gọi tên cái khiên này ra:
Route::post('/checkout', [CheckoutController::class, 'process'])
->middleware('throttle:checkout_api');
Code của bạn bây giờ siêu clean, logic phân quyền Rate Limit nằm gọn gàng ở một nơi duy nhất!
4. Những "Mảnh ghép" Nâng Cao Cần Biết
A. Fallback Limits (Giới hạn nhiều tầng)
Bạn muốn user gửi tối đa 5 request/phút NHƯNG tổng cộng không được quá 100 request/giờ? Laravel hỗ trợ return một mảng Limits:
RateLimiter::for('sms_otp', function (Request $request) {
return [
Limit::perMinute(5),
Limit::perHour(100),
];
});
Tuyệt chiêu này cực kỳ hữu ích cho các API gửi SMS OTP, vừa chống spam tức thời, vừa chống cạn kiệt hạn mức trong thời gian dài.
B. Hãy sử dụng Redis làm Cache Driver!
Mặc định, Rate Limiter đếm số lượng dựa trên Cache Driver của ứng dụng (thường là file). Nếu hệ thống của bạn chạy trên nhiều server (Load Balancing), việc lưu cache vào file nội bộ của từng server sẽ làm Rate Limiter bị "mù".
Ví dụ: Server A đếm được 59 request, nhưng Server B lại đếm từ 0.
Bắt buộc: Chuyển CACHE_DRIVER=redis trong file .env. Redis lưu trữ trên RAM tập trung, tốc độ đếm cực nhanh và đồng bộ trên toàn cụm server.
C. Tùy biến Response khi bị chặn
Khi user bị dính Rate Limit, Laravel ném ra ThrottleRequestsException. Bạn hoàn toàn có thể catch lỗi này trong bootstrap/app.php (hoặc app/Exceptions/Handler.php) để trả về JSON đẹp mắt hơn thay vì trang HTML mặc định của Laravel:
// Custom response báo lỗi cho Frontend
RateLimiter::for('global', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip())
->response(function () {
return response()->json([
'status' => 'error',
'message' => 'Bạn thao tác quá nhanh, vui lòng chờ một lát!',
'code' => 429
], 429);
});
});
5. Lời kết
Rate Limiter không chỉ là một công cụ bảo mật, nó còn là một phần của kiến trúc hệ thống, giúp bạn duy trì tính nhất quán, công bằng và ổn định (Reliability). Dù bạn viết hệ thống bán lẻ khổng lồ hay một API nhỏ nhận dữ liệu từ phần cứng, đừng bao giờ đẩy code lên Production mà quên cắm "throttle" vào các Route quan trọng.
Anh em ở công ty đang dùng công cụ nào để Rate Limit? Dùng sẵn của Laravel hay chặn từ tầng Nginx/API Gateway (như Kong)? Cùng thảo luận ở phần bình luận nhé!
Nếu thấy bài viết chất lượng, đừng quên cho mình 1 Upvote và Bookmark để áp dụng vào dự án. Chúc anh em tối ưu hệ thống mượt mà!
All Rights Reserved