+5

Throttling API trong Golang: Quản lý Tốc Độ Truy Cập

Sau một thời gian "đắm chìm" trong nuxtJs và laravel thì gần đây mình có chuyển qua làm thêm về golang. Kinh nghiệm cũng chưa có nhiều nên hôm nay mạn phép chia sẻ với mọi người về một vấn đề cũng khá cơ bản thôi rất mong được nhận được sự góp ý từ mọi người. Nếu có gạch đá thì xin nhẹ nhàng chứ đừng gần tết dương mọi người lên đây toàn nói lời yêu thương thì có th lại bảo tôi viết bài như *** =))). Thôi không dài dòng nữa vào vấn đề chính thôi nhé.

Giới Thiệu

Trong các ứng dụng web hiện đại, việc quản lý tốc độ truy cập (throttling) đối với API là một yếu tố quan trọng để ngăn chặn việc sử dụng quá mức và bảo vệ hệ thống khỏi tải cao không cần thiết. Trong bài viết này, chúng ta sẽ tìm hiểu về cách triển khai throttling API trong ngôn ngữ lập trình Go (Golang).

Throttling API là Gì?

Nói qua một chút về throttling API. Throttling API là quá trình kiểm soát số lượng yêu cầu được gửi đến một API trong một khoảng thời gian nhất định. Mục tiêu là ngăn chặn người dùng hoặc ứng dụng từ việc gửi quá nhiều yêu cầu trong một khoảng thời gian ngắn, giữ cho hệ thống ổn định và đảm bảo chất lượng dịch vụ.

Triển Khai Throttling API trong Golang

Bước 1: Sử Dụng Thư Viện "github.com/juju/ratelimit"

Cài đặt thư viện này thông qua câu lệnh sau:

go get github.com/juju/ratelimit

Mình nói qua một chút về thư viện này. Thư viện "github.com/juju/ratelimit" trong Golang là một thư viện cung cấp cơ chế kiểm soát tốc độ sử dụng giới hạn tốc độ dựa trên gói hôp (token bucket). Cơ chế này giúp quản lý số lượng sự kiện có thể xảy ra trong một khoảng thời gian nhất định. Cơ chế token bucket hoạt động như sau: Một "bucket" chứa một số lượng token cố định. Mỗi yêu cầu cần sử dụng một token. Nếu bucket không còn token, yêu cầu sẽ bị từ chối cho đến khi có đủ token. Sau mỗi khoảng thời gian nhất định, một số lượng token mới sẽ được thêm vào bucket. Có nhiều thư viện để giúp chúng ta xây dụng Throttling API nhưng ở đây mình chọn thư viện này vì nó có một vài ưu điểm sau:

  1. Dễ Sử Dụng: Thư viện cung cấp giao diện đơn giản để tạo ra một "bucket" và thực hiện các thao tác kiểm soát tốc độ.

  2. Hiệu Quả: Giải thuật token bucket là một phương pháp hiệu quả để kiểm soát tốc độ, đặc biệt là trong các kịch bản API throttling.

  3. Tùy Chỉnh: Bạn có thể dễ dàng tùy chỉnh giới hạn tốc độ bằng cách điều chỉnh số lượng token và khoảng thời gian cập nhật.

Bước 2: Triển Khai Middleware Throttle

Sau đây mình sẽ đưa ra một ví dụ đơn giản để minh họa cách sử dụng throttle api

package main

import (
	"fmt"
	"net/http"
	"sync"
	"time"

	"github.com/juju/ratelimit"
)

type IPThrottle struct {
	ips map[string]*ratelimit.Bucket
	mu  sync.Mutex
}

func NewIPThrottle() *IPThrottle {
	return &IPThrottle{
		ips: make(map[string]*ratelimit.Bucket),
	}
}

func (t *IPThrottle) GetBucket(ip string) *ratelimit.Bucket {
	t.mu.Lock()
	defer t.mu.Unlock()

	if bucket, exists := t.ips[ip]; exists {
		return bucket
	}

	// Tạo mới một bucket cho địa chỉ IP nếu chưa tồn tại
	bucket := ratelimit.NewBucket(time.Second, 5)
	t.ips[ip] = bucket

	return bucket
}

func ThrottleMiddleware(next http.Handler, ipThrottle *IPThrottle) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ip := r.RemoteAddr

		// Lấy bucket cho địa chỉ IP
		bucket := ipThrottle.GetBucket(ip)

		// Kiểm tra số lượng token trong bucket
		if bucket.TakeAvailable(1) < 1 {
			http.Error(w, "API rate limit exceeded", http.StatusTooManyRequests)
			return
		}

		// Gọi xử lý tiếp theo trong chuỗi middleware hoặc xử lý chính
		next.ServeHTTP(w, r)
	})
}

func APIHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "API response")
}

func main() {
	ipThrottle := NewIPThrottle()
	apiHandler := http.HandlerFunc(APIHandler)

	// Áp dụng middleware throttling với giới hạn là 5 yêu cầu mỗi giây cho mỗi địa chỉ IP
	throttledHandler := ThrottleMiddleware(apiHandler, ipThrottle)

	server := http.Server{
		Addr:    ":8080",
		Handler: throttledHandler,
	}

	fmt.Println("Server is listening on :8080")
	err := server.ListenAndServe()
	if err != nil {
		fmt.Println("Error:", err)
	}
}

Trong đoạn mã trên, IPThrottle là một cấu trúc để theo dõi số lượng yêu cầu từng địa chỉ IP và ThrottleMiddleware sử dụng IPThrottle để áp dụng giới hạn truy cập cho mỗi địa chỉ IP. Số lượng yêu cầu tối đa cho mỗi địa chỉ IP được đặt là 5 yêu cầu mỗi giây, nhưng bạn có thể điều chỉnh giới hạn này theo nhu cầu của bạn.

Hàm GetBucket() trong ví dụ trên được sử dụng để lấy hoặc tạo mới một bucket cho mỗi địa chỉ IP. Dưới đây mình sẽ giải thích từng phần của hàm này:

  1. t.mu.Lock() và defer t.mu.Unlock(): Sử dụng sync.Mutex (t.mu) để đảm bảo rằng cập nhật và kiểm tra t.ips là an toàn từ nhiều goroutines. Lock được sử dụng để bắt đầu một khối kín và Unlock được gọi tự động khi hàm kết thúc (thông qua defer) để đảm bảo rằng khóa sẽ được giải phóng.
  2. Kiểm tra xem bucket đã tồn tại chưa: if bucket, exists := t.ips[ip]; exists { return bucket } Trước khi tạo mới một bucket, hàm kiểm tra xem đã có bucket nào được liên kết với địa chỉ IP này chưa bằng cách kiểm tra t.ips[ip]. Nếu tồn tại, nó trực tiếp trả về bucket đó mà không tạo mới một bucket mới
  3. Tạo mới một bucket mới nếu không tồn tại:
bucket := ratelimit.NewBucket(time.Second, 5)
t.ips[ip] = bucket

Nếu không có bucket nào tồn tại cho địa chỉ IP, hàm tạo mới một bucket mới (ratelimit.NewBucket) với một khoảng thời gian cố định là 1 giây và giới hạn tốc độ là 5 yêu cầu mỗi giây. Bucket mới sau đó được thêm vào t.ips để giữ theo dõi. Cuối cùng chúng ta trả về 1 bucket.

Hàm ThrottleMiddleware trong ví dụ được sử dụng để áp dụng giới hạn tốc độ truy cập cho mỗi địa chỉ IP. Trong hàm này chúng ta thực hiện những bước sau:

  1. Lấy Địa Chỉ IP:
ip := r.RemoteAddr

r.RemoteAddr trả về địa chỉ IP của client. Trong trường hợp này, địa chỉ IP được sử dụng làm khóa để xác định và theo dõi số lượng yêu cầu từng địa chỉ IP.

  1. Lấy hoặc Tạo Một Bucket Cho Địa Chỉ IP:
bucket := ipThrottle.GetBucket(ip)

Gọi hàm GetBucket từ IPThrottle để lấy hoặc tạo mới một bucket cho địa chỉ IP. Hàm này sẽ kiểm tra xem đã có một bucket nào được liên kết với địa chỉ IP này chưa. Nếu có, nó trả về bucket đó; nếu không, nó tạo mới một bucket mới và liên kết nó với địa chỉ IP.

  1. Kiểm Tra Số Lượng Token Trong Bucket:
if bucket.TakeAvailable(1) < 1 {
    http.Error(w, "API rate limit exceeded", http.StatusTooManyRequests)
    return
}

Gọi TakeAvailable(1) để lấy một token từ bucket và kiểm tra xem có đủ token để xử lý yêu cầu không. Nếu không có đủ token, trả về lỗi HTTP 429 (Too Many Requests) để thông báo rằng tốc độ truy cập đã vượt quá giới hạn

  1. Gọi Xử Lý Tiếp Theo:
next.ServeHTTP(w, r)

Nếu có đủ token, hàm gọi xử lý tiếp theo trong chuỗi middleware hoặc xử lý chính của ứng dụng để xử lý yêu cầu.

Tại Sao Throttling API Quan Trọng?

  • Bảo vệ Hệ Thống: Throttling giúp bảo vệ hệ thống khỏi tải áp lực lớn không cần thiết, đảm bảo rằng nguồn lực hệ thống không bị quá tải.
  • Bảo vệ Dịch Vụ: Quy định số lượng yêu cầu giúp duy trì chất lượng dịch vụ (QoS), đảm bảo rằng tất cả người dùng có trải nghiệm ổn định và công bằng.
  • Phòng Tránh Tấn Công: Throttling giúp ngăn chặn các hành động tấn công cơ bản như tấn công từ chối dịch vụ (DDoS) bằng cách giới hạn số lượng yêu cầu từ một nguồn.

Kết Luận

Trong bài viết này, chúng ta đã tìm hiểu về cách triển khai throttling API trong Golang sử dụng thư viện github.com/juju/ratelimit. Thông qua việc áp dụng middleware, bạn có thể dễ dàng tích hợp chức năng này vào ứng dụng web của mình để bảo vệ hệ thống và cung cấp trải nghiệm người dùng ổn định.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.