- vừa được xem lúc

Rate Limiter in System Design. Phần 3 - Áp dụng vào thực tiễn với Golang

0 0 23

Người đăng: Kha Leo

Theo Viblo Asia

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:

  1. 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
  2. Số lượng request cho từng IP address là 5 requests mỗi giây
  3. 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é!

image.png

Rate limiter basic flow

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ả: image.png

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 RateLimiterTypeKey

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

Bình luận

Bài viết tương tự

- vừa được xem lúc

gRPC - Nó là gì và có nên sử dụng hay không?

Nhân một ngày rảnh rỗi, mình ngồi đọc lại RPC cũng như gRPC viết lại để nhớ lâu hơn. Vấn đề là gì và tại sao cần nó .

0 0 131

- vừa được xem lúc

Embedded Template in Go

Getting Start. Part of developing a web application usually revolves around working with HTML as user interface.

0 0 56

- vừa được xem lúc

Tạo Resful API đơn giản với Echo framework và MySQL

1. Giới thiệu.

0 0 60

- vừa được xem lúc

Sử dụng goquery trong golang để crawler thông tin các website Việt Nam bị deface trên mirror-h.org

. Trong bài viết này, mình sẽ cùng mọi người khám phá một package thu thập dữ liệu có tên là goquery của golang. Mục tiêu chính của chương trình crawler này sẽ là lấy thông tin các website Việt Nam bị deface (là tấn công, phá hoại website, làm thay đổi giao diện hiển thị của một trang web, khi người

0 0 237

- vừa được xem lúc

Tạo ứng dụng craw dữ liệu bing với Golang, Mysql driver

Chào mọi người . Lâu lâu ta lại gặp nhau 1 lần, để tiếp tục series chia sẻ kiến thức về tech, hôm nay mình sẽ tìm hiểu và chia sẻ về 1 ngôn ngữ đang khá hot trong cộng đồng IT đó là Golang.

0 0 75

- vừa được xem lúc

Golang: Rest api and routing using MUX

Routing with MUX. Let's create a simple CRUD api for a blog site. # All . GET articles/ .

0 0 54