+6

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

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


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí