Trong hai phần trước mình đã nêu khái quát về Rate Limiter, các bạn có thể xem lại ở đây:
Ở phần 3 hôm nay mình sẽ trình bày cách triển khai một Rate limiter trong project thực tế
Prerequisite
Trong phạm vi bài viết hôm nay mình sẽ sử dụng:
- Backend: Golang và GIN và Rate limiter middleware library: gin-rate-limiter
- Client: NodeJs đi kèm proxy server
Mục tiêu
Lấy ví dụ thực tiễn, mình cần giải quyết một số requirement như sau:
- Giới hạn số lượng requests đến từ các IP Address, giả định số request quota cho mỗi IP Address là như nhau
- Số lượng request cho từng IP address là 5 requests mỗi giây
- Cách triển khai gin-rate-limiter cho mỗi một hoặc group các APIs/routes
Yêu cầu đơn giản như trên thôi. Mình bắt đầu nhé!
Setup project
Mình sẽ không đi từ đầu về Go và Gin nữa, chúng ta cần setup sẵn một Http Server Golang với GIN:
package main import ( "time" "github.com/gin-gonic/gin" "github.com/khaaleoo/gin-rate-limiter/core"
) func main() { r := gin.Default() r.GET("/me", func(c *gin.Context) { c.String(200, "me") }) r.Run(":3000")
}
Install thư viện gin-rate-limiter:
go get github.com/khaaleoo/gin-rate-limiter
Hoàn tất phần cài đặt, bây giờ chúng ta sẽ giải quyết từng use case ở trên 💪🏻
Use case 1 - IP Limiter
gin-rate-limiter hoạt động như một middleware của GIN. Chúng ta khởi tạo instance của middleware như sau:
rateLimiterOption := ratelimiter.RateLimiterOption{ Limit: 1, Burst: 2, Len: 1 * time.Second,
} // Create an IP rate limiter instance
rateLimiterMiddleware := ratelimiter.RequireRateLimiter(ratelimiter.RateLimiter{ RateLimiterType: ratelimiter.IPRateLimiter, Key: "iplimiter_maximum_requests_for_ip_test", Option: rateLimiterOption,
})
Có một vài parameters được khai báo ở trên, mình có thể giải thích như sau:
- 🔥 Burst: giới hạn số lượng token mà một actor (requester) có thể thực hiện
- ⌛️ Len: thời gian clean/reset bucket. Sau một khoảng thời gian x nếu client không thực hiện request thì reset mọi thứ về ban đầu
- 🔁 Limit: Số lượng y token được refresh sau mỗi giây
- 🐧 RateLimiterType: loại rate limiter middleware, ở đây mình chọn ratelimiter.IPRateLimiter
Apply middleware vào router /me ở trên:
r.GET("/me", rateLimiterMiddleware, func(c *gin.Context) { c.String(200, "me")
})
Full code phía HTTP server:
package main import ( "time" "github.com/gin-gonic/gin" "github.com/khaaleoo/gin-rate-limiter/core"
) func main() { r := gin.Default() rateLimiterOption := ratelimiter.RateLimiterOption{ Limit: 1, Burst: 2, Len: 1 * time.Second, } rateLimiterMiddleware := ratelimiter.RequireRateLimiter(ratelimiter.RateLimiter{ RateLimiterType: ratelimiter.IPRateLimiter, Key: "iplimiter_maximum_requests_for_ip_test", Option: rateLimiterOption, }) // Apply rate limiter middleware to a route r.GET("/limited-route", rateLimiterMiddleware, func(c *gin.Context) { c.String(200, "me") }) r.Run(":3000")
}
That's awesome. Bây giờ mình sẽ giả lập Client thực hiện http request đến server đã khởi tạo
^ Mình sẽ không đi sâu vào việc setup phía Client nhé, đơn thuần là khởi tạo 1 node application cơ bản và một Proxy server, ý tưởng như sau:
Giả lập 2 clients (A và B) thực hiện request đến server. Tất cả các request đều thực hiện bất đồng bộ, với successCount và errorCount là 2 biến để đếm số lượng request thành công và thất bại.
(async () => { try { console.time("concatenation"); console.log("🏇 Process start..."); await Promise.all([ ...Array(2) .fill(0) .map((_, i) => GetMe(i)), // from Client A ...Array(3) .fill(0) .map((_, i) => GetMeWithDifferentIPAddress(i)), // from Client B ]); } catch (err) { console.log({ err }); } finally { console.log("✅ Process end..."); console.timeEnd("concatenation"); console.log({ successCount, errorCount }); }
})();
Chạy ứng dụng trên và ta có kết quả:
Dựa vào kết quả chúng ta thấy:
- Thời gian thực hiện request là 61.677ms (<1s window len config).
- Backend server chỉ cho phép 1 client thực hiện tối đa 2 requests / 1 giây -> tổng cộng 4 requests của IP local và IP Proxy Server thực hiện thành công, còn request thứ 3 của IP Proxy Server thất bại do vượt quá quota cho phép
Chúng ta giải quyết xong yêu cầu của Use Case 1
Use case 2 - Điều chỉnh sổ lượng token
Đơn giản chúng ta chỉ cần thay đổi thông số của instance gin-rate-limiter middleware:
rateLimiterOption := ratelimiter.RateLimiterOption{ Limit: 1, Burst: 5, // maximum 5 requests Len: 1 * time.Second,
}
Use case 3 - Triển khai cho group of IPs/routes
- Chúng ta có thể apply middleware cho group of routes như sau:
rateLimiterOption := ratelimiter.RateLimiterOption{ Limit: 1, Burst: 2, Len: 1 * time.Second, } rateLimiterMiddleware := ratelimiter.RequireRateLimiter(ratelimiter.RateLimiter{ RateLimiterType: ratelimiter.IPRateLimiter, Key: "user_group", Option: rateLimiterOption,
}) user := router.Group("/user")
user.Use(rateLimiterMiddleware)
{ user.GET("/me", userAPIService.GetMeHdl(), ) user.PUT("/me", userAPIService.UpdateProfileHdl(), )
}
Hoặc apply từng rate limiter khác nhau cho các routes khác nhau, bằng cách khai báo nhiều instance.
Quan trọng
: các instance phân biệt nhau bằng cặp RateLimiterType và Key
rateLimiterGetOption := ratelimiter.RateLimiterOption{ Limit: 1, Burst: 10, Len: 1 * time.Second,
} rateLimiterUpdateOption := ratelimiter.RateLimiterOption{ Limit: 1, Burst: 5, Len: 1 * time.Second,
} getProfileMiddleware := ratelimiter.RequireRateLimiter(ratelimiter.RateLimiter{ RateLimiterType: ratelimiter.IPRateLimiter, Key: "get_profile", Option: rateLimiterGetOption,
}) updateProfileMiddleware := ratelimiter.RequireRateLimiter(ratelimiter.RateLimiter{ RateLimiterType: ratelimiter.IPRateLimiter, Key: "update_profile", Option: rateLimiterUpdateOption,
}) user := router.Group("/user")
{ user.GET("/me", getProfileMiddleware, userAPIService.GetMeHdl(), ) user.PUT("/me", updateProfileMiddleware, userAPIService.UpdateProfileHdl(), )
}
Kết
Trên đây mình đã apply Rate Limiter vào một ứng dụng cụ thể với ví dụ cơ bản nhất. Cảm ơn các bạn đã xem bài viết
References
- GIN Rate Limiter Middleware: https://github.com/khaaleoo/gin-rate-limiter