Chapter 1: Giới thiệu về quá trình thử lại trong Go

1.1 Hiểu về Sự Cần Thiết của Cơ Chế Thử Lại

Trong nhiều kịch bản tính toán, đặc biệt là khi xử lý các hệ thống phân tán hoặc giao tiếp mạng, các thao tác có thể thất bại do lỗi tạm thời. Các lỗi này thường là vấn đề tạm thời như sự bất ổn của mạng, sự không sẵn có tạm thời của một dịch vụ, hoặc thời gian chờ quá lâu. Thay vì thất bại ngay lập tức, hệ thống nên được thiết kế để thử lại các thao tác gặp phải các lỗi tạm thời này. Tiếp cận này cải thiện tính tin cậy và sự đàn hồi của hệ thống.

Cơ chế thử lại có thể trở nên quan trọng trong các ứng dụng nơi tính nhất quán và hoàn thiện của thao tác là cần thiết. Chúng cũng có thể giảm tỷ lệ lỗi mà người dùng cuối phải đối mặt. Tuy nhiên, việc triển khai một cơ chế thử lại đến với những thách thức, như quyết định thử lại bao lâu và sau bao lâu trước khi từ bỏ. Đó là nơi mà chiến lược backoff đóng một vai trò quan trọng.

1.2 Tổng quan về Thư viện go-retry

Thư viện go-retry trong Go cung cấp một cách linh hoạt để thêm logic thử lại vào ứng dụng của bạn với các chiến lược backoff đa dạng. Các tính năng chính bao gồm:

  • Tính mở rộng: Giống như gói http của Go, go-retry được thiết kế để có tính mở rộng thông qua middleware. Bạn cũng có thể viết các hàm backoff của riêng mình hoặc sử dụng các bộ lọc tiện ích đã được cung cấp.
  • Độc lập: Thư viện chỉ phụ thuộc vào thư viện chuẩn của Go và tránh sự phụ thuộc vào bên ngoài, giữ cho dự án của bạn nhẹ nhàng.
  • Đa luồng: Nó an toàn khi sử dụng đa luồng và có thể hoạt động với goroutines mà không cần thêm rắc rối nào.
  • Tích hợp với ngữ cảnh: Nó hỗ trợ ngữ cảnh Go tự nhiên cho thời gian chờ và hủy bỏ, tích hợp mượt mà với mô hình đa luồng của Go.

Chapter 2: Nhập Thư viện

Trước khi bạn có thể sử dụng thư viện go-retry, nó cần được nhập vào dự án của bạn. Điều này có thể được thực hiện bằng cách sử dụng go get, đó là lệnh Go để thêm các phụ thuộc vào mô-đun của bạn. Đơn giản mở cửa sổ terminal và thực thi:

go get github.com/sethvargo/go-retry

Lệnh này sẽ tải thư viện go-retry và thêm nó vào các phụ thuộc của dự án của bạn. Sau đó, bạn có thể nhập nó vào mã của mình giống như bất kỳ gói Go nào khác.

Chapter 3: Triển khai Logic Thử lại Cơ bản

3.1 Thử lại đơn giản với Backoff Cố định

Hình thức đơn giản nhất của logic thử lại liên quan đến chờ đợi một khoảng thời gian cố định giữa mỗi thử lại. Bạn có thể sử dụng go-retry để thực hiện thử lại với backoff cố định.

Dưới đây là một ví dụ về cách sử dụng backoff cố định với go-retry:

<!--Here comes the code translation-->

Trong ví dụ này, hàm retry.Do sẽ tiếp tục thử nghiệm hàm operation mỗi 1 giây cho đến khi thành công hoặc ngữ cảnh hết thời gian hoặc bị hủy.

3.2 Triển khai Backoff mũi tên

Backoff mũi tên tăng thời gian chờ giữa các lần thử lại theo cấp số nhân. Chiến lược này giúp giảm tải lên hệ thống và đặc biệt hữu ích khi xử lý các hệ thống quy mô lớn hoặc các dịch vụ điện toán đám mây.

Cách sử dụng backoff mũi tên với go-retry như sau:

<!--Here comes the code translation-->

Trong trường hợp backoff mũi tên, nếu backoff ban đầu được đặt là 1 giây, thử lại sẽ xảy ra sau 1 giây, 2 giây, 4 giây, vv, tăng tốc độ chờ đợi giữa các lần thử lại tiếp theo theo cấp số nhân.

3.3 Chiến lược Đợi Fibonacci

Chiến lược đợi Fibonacci sử dụng dãy Fibonacci để xác định thời gian chờ giữa các lần thử lại, đó có thể là một chiến lược tốt cho các vấn đề liên quan đến mạng nơi một thời gian chờ tăng dần là có lợi.

Việc triển khai chiến lược đợi Fibonacci với go-retry được minh họa dưới đây:

package main

import (
  "context"
  "time"
  "github.com/sethvargo/go-retry"
)

func main() {
    ctx := context.Background()

    // Tạo một chiến lược đợi Fibonacci mới
    backoff := retry.NewFibonacci(1 * time.Second)

    // Xác định một hoạt động cần thử lại
    operation := func(ctx context.Context) error {
        // Ở đây sẽ là logic để thực hiện hành động có thể thất bại và cần thử lại
        return nil
    }
    
    // Thực hiện hoạt động với đợi Fibonacci sử dụng retry.Do
    if err := retry.Do(ctx, backoff, operation); err != nil {
        // Xử lý lỗi
    }
}

Với đợi Fibonacci với giá trị ban đầu là 1 giây, các lần thử lại sẽ xảy ra sau 1s, 1s, 2s, 3s, 5s, v.v., theo chuỗi Fibonacci.

Chương 4: Các Kỹ Thuật Thử Lại Nâng Cao và Middleware

4.1 Sử Dụng Jitter trong Thử Lại

Khi triển khai logic thử lại, quan trọng là phải xem xét tác động của việc thử lại đồng thời đối với hệ thống, điều này có thể dẫn đến vấn đề đám đàn. Để giảm thiểu vấn đề này, chúng ta có thể thêm jitter ngẫu nhiên vào các khoảng thời gian chờ đợi. Kỹ thuật này giúp phân tán các cố gắng thử lại, giảm khả năng nhiều khách hàng thử lại đồng thời.

Ví dụ về thêm jitter:

b := retry.NewFibonacci(1 * time.Second)

// Trả về giá trị tiếp theo, +/- 500ms
b = retry.WithJitter(500 * time.Millisecond, b)

// Trả về giá trị tiếp theo, +/- 5% kết quả
b = retry.WithJitterPercent(5, b)

4.2 Đặt Số Lần Thử Lại Tối Đa

Trong một số tình huống, việc giới hạn số lần thử lại là cần thiết để ngăn cản các lần thử lại kéo dài và không hiệu quả. Bằng cách chỉ định số lần thử lại tối đa, chúng ta có thể kiểm soát số lượng cố gắng trước khi từ bỏ hoạt động.

Ví dụ về việc đặt số lần thử lại tối đa:

b := retry.NewFibonacci(1 * time.Second)

// Dừng sau 4 lần thử lại, khi lần thử lại thứ 5 thất bại
b = retry.WithMaxRetries(4, b)

4.3 Giới Hạn Thời Gian Chờ Đợi Cá Nhân

Để đảm bảo rằng các khoảng thời gian chờ đợi cá nhân không vượt quá một ngưỡng nhất định, chúng ta có thể sử dụng middleware CappedDuration. Điều này ngăn chặn việc tính toán các khoảng thời gian chờ đợi quá lâu, tạo tính dự đoán cho hành vi thử lại.

Ví dụ về việc giới hạn thời gian chờ đợi cá nhân:

b := retry.NewFibonacci(1 * time.Second)

// Đảm bảo giá trị tối đa là 2s
b = retry.WithCappedDuration(2 * time.Second, b)

4.4 Kiểm Soát Tổng Thời Gian Thử Lại

Trong các tình huống cần có một giới hạn về tổng thời gian cho toàn bộ quá trình thử lại, middleware WithMaxDuration có thể được sử dụng để chỉ định thời gian thực hiện tối đa. Điều này đảm bảo rằng quá trình thử lại không tiếp tục vô hạn, áp đặt một ngân sách thời gian cho các lần thử lại.

Ví dụ về kiểm soát tổng thời gian thử lại:

b := retry.NewFibonacci(1 * time.Second)

// Đảm bảo thời gian thử lại tối đa là 5s
b = retry.WithMaxDuration(5 * time.Second, b)