Chào các bạn, bài viết trước mình đã giới thiệu về cơ chế Polling. Qua bài viết này, mình sẽ tận dụng khả năng của Golang để triển khai cơ chế Polling (Short PollingLong Polling).

Đây là bài blog thuộc series System Design của mình, nếu bạn quan tâm đến các vấn đề liên quan đến thiết kế hệ thống phần mềm, hãy theo dõi series này để cập nhật những kiến thức mới nhất nhé!

Short polling

Trong cách tiếp cận này, phía downstream sẽ thực hiện gửi request đến upstream sau mỗi khoảng thời gian nhất định. Đây là cách tiếp cận đơn giản nhất.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/brianvoe/gofakeit/v7"
)

func upstream() string {
	return gofakeit.RandomString([]string{"", gofakeit.Name()})
}

func downstream(ctx context.Context) {
	// Create a timer with 1 second interval
	interval := 1 * time.Second
	timer := time.NewTimer(interval)
	defer timer.Stop()
	// Start polling until the context is done
	for {
		select {
		case <-ctx.Done():
			return
		case <-timer.C:
			result := upstream()
			if result != "" {
				fmt.Println("Got result:", result)
			} else {
				fmt.Println("Got empty result")
			}
			timer.Reset(interval)
		}
	}
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	downstream(ctx)
}

Nào, chúng ta hay điểm qua các phần đáng chú ý trong đoạn code trên nhé:

  1. upstream(): Hàm này sẽ trả về một giá trị ngẫu nhiên, giả lập việc lấy dữ liệu từ upstream. Nếu giá trị trả về là rỗng, ta sẽ in ra thông báo “Got empty result”, ngược lại ta sẽ in ra giá trị đó.
  2. downstream(): Hàm này sẽ thực hiện Polling với interval là 1 giây. Hãy chú ý tới vòng lặp for, ở đây mình sẽ dùng select để chờ đến khi timer.C hoặc ctx.Done() được gửi tín hiệu. Nếu timer.C được gửi tín hiệu, ta sẽ gọi hàm upstream() và in ra kết quả, sau đó reset lại timer để chờ đến lần polling tiếp theo. Nếu ctx.Done() được gửi tín hiệu, ta sẽ kết thúc vòng lặp.

Rất đơn giản phải không nào? Bạn có thể thay đổi giá trị của interval để thay đổi tần suất polling. Trong thực tế, triển khai Upstream có thể là một API hoặc một hệ thống khác phức tạp hơn, nhưng tựu chung lại thì bản chất cơ chế Polling vẫn là như vậy.

Long polling

Với cơ chế Long Polling, phía Downstream sẽ gửi request đến Upstream và chờ đợi kết quả. Nếu không có kết quả nào được trả về, Downstream sẽ chờ đến khi có kết quả hoặc hết thời gian timeout.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/brianvoe/gofakeit/v7"
)

func upstream() string {
	// Try to get a random name 5 times
	// Non-empty represents a new data, otherwise old data
	for i := 0; i < 5; i++ {
		result := gofakeit.RandomString([]string{"", gofakeit.Name()})
		if result != "" {
			return result
		}
		// Simulate a delay (waiting for new data)
		time.Sleep(time.Second)
	}
	return ""
}

func downstream(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			return
		// Polling upstream data with long polling technique
		default:
			result := upstream()
			if result != "" {
				fmt.Println("Got result:", result)
			} else {
				fmt.Println("Got empty result")
			}
		}
	}
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	downstream(ctx)
}

Trong đoạn code trên, mình đã thay đổi cách thức polling của upstream và downstream với một số điểm đáng chú ý sau:

  1. upstream(): Hàm này sẽ thử lấy dữ liệu từ upstream 5 lần, nếu không có dữ liệu nào được trả về, ta sẽ trả về giá trị rỗng. Điều này giả lập việc downstream sẽ chờ đợi kết quả từ Upstream trong một khoảng thời gian nhất định. Với Go, ta có thể tận dụng Go’s runtime với hàm time.Sleep() để giả lập việc chờ đợi, điều này sẽ không tốn tài nguyên hệ thống như việc sử dụng vòng lặp ở cơ chế short polling.
  2. downstream(): Hàm này sẽ thực hiện Polling với cơ chế Long Polling. Chú ý tới vòng lặp for, ở đây mình sử dụng select để chờ đến khi ctx.Done() được gửi tín hiệu. Nếu không có tín hiệu nào được gửi, ta sẽ gọi hàm upstream() và in ra kết quả.

Với cách tiếp cận này, downstream sẽ chờ đợi kết quả từ Upstream trong một khoảng thời gian nhất định, nhưng khoảng thời gian này sẽ được cấu hình ở phía Upstream, không phải ở downstream. Điều này giúp giảm tải cho hệ thống downstream, cũng như upstream.

Kết luận

Như vậy, chúng ta đã học cách thực hiện triển khai cơ chế Polling với Golang. Đây chỉ là những cách tiếp cận cơ bản mà thôi, nhưng chúng nêu lên được bản chất của cơ chế Polling, bao gồm Short Polling và Long Polling. Ngoài ra, chúng ta cũng có thể mở rộng các cách triển khai khác dựa trên bản chất của cơ chế Polling, qua đó các bạn sẽ hiểu vì sao mà Polling lại được sử dụng rộng rãi trong thực tế.

Hy vọng bài viết này sẽ giúp ích cho các bạn trong việc hiểu rõ hơn về cơ chế Polling. Nếu có bất kỳ thắc mắc hoặc góp ý nào, hãy để lại comment bên dưới nhé. Chào tạm biệt và hẹn gặp lại ở bài viết tiếp theo 🖐

Tham khảo

  • Mình có chia sẻ code trong bài viết ở đây nhé: Source code