فصل 1: مقدمه‌ای بر بازسازی در Go

1.1 درک نیاز به مکانیسم تلاش مجدد

در بسیاری از سناریوهای محاسباتی، به ویژه در ارتباط با سیستم‌های توزیع‌شده یا ارتباط شبکه، عملیات ممکن است به دلیل خطاهای عابرگذری شکست بخورد. این خطاها اغلب مسائل موقتی مانند ناپایداری شبکه، عدم دسترسی موقت به یک سرویس یا زمانبندی‌ها هستند. به جای شکست فوری، سیستم‌ها باید طراحی شوند تا عملیاتی که با این گونه خطاهای عابرگذری مواجه می‌شوند، دوباره تلاش کنند. این رویکرد اعتبار و انطباق را بهبود می‌بخشد.

مکانیسم‌های تلاش مجدد می‌توانند در برنامه‌ها که انطباق و کمالت عملیات ضروری است، حیاتی باشند. آن‌ها می‌توانند همچنین نرخ خطا را کاهش دهند که کاربران نهایی تجربه می‌کنند. با این حال، پیاده‌سازی یک مکانیسم تلاش مجدد با چالش‌هایی همراه است، مانند تصمیم‌گیری در مورد چقدر و چه مدت باید تلاش مجدد کرد قبل از تسلیم شدن. در اینجا استراتژی‌های backoff نقش مهمی ایفا می‌کنند.

1.2 مرور کتابخانه go-retry

کتابخانه go-retry در Go یک روش انعطاف‌پذیر برای اضافه کردن منطق تلاش مجدد به برنامه‌های شما با استفاده از انواع استراتژی‌های backoff ارائه می‌دهد. ویژگی‌های اصلی شامل:

  • قابلیت‌کاربری: مانند بسته http Go، go-retry برای کار با middleware طراحی شده است. شما حتی می‌توانید توابع backoff خود را بنویسید یا از فیلترهای مفید ارائه شده استفاده کنید.
  • استقلال: این کتابخانه تنها بر روی کتابخانه استاندارد Go وابسته است و از وابستگی‌های خارجی خودداری می‌کند تا پروژه شما را سبک نگه دارد.
  • همزمانی: برای استفاده همزمان ایمن است و می‌تواند با goroutine‌ها بدون هیچ مشکل اضافی کار کند.
  • آگاه از زمینه: این کتابخانه از زمینه‌های Go اصلی برای زمانبندی و لغو پشتیبانی می‌کند و به صورت یکپارچه با مدل همزمانی Go ادغام می‌شود.

فصل 2: وارد کردن کتابخانه‌ها

پیش از استفاده از کتابخانه go-retry، باید آن را به پروژه‌ی خود وارد کنید. این کار با استفاده از go get که دستور Go برای اضافه کردن وابستگی‌ها به ماژول شما است، قابل انجام است. به سادگی ترمینال خود را باز کنید و دستور زیر را اجرا کنید:

go get github.com/sethvargo/go-retry

این دستور کتابخانه go-retry را برای شما فراخوانی می‌کند و آن را به وابستگی‌های پروژه‌ی شما اضافه می‌کند. پس از آن، می‌توانید آن را همانند هر بسته‌ی Go دیگری به کد خود وارد کنید.

فصل 3: پیاده‌سازی منطق تلاش مجدد پایه

3.1 تلاش مجدد ساده با backoff ثابت

ساده‌ترین شکل منطق تلاش مجدد شامل انتظار برای مدت زمان ثابت بین هر تلاش مجدد است. شما می‌توانید از go-retry برای انجام تلاش مجدد با backoff ثابت استفاده کنید.

یک مثال از نحوه استفاده از backoff ثابت با go-retry به صورت زیر است:

package main

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

func main() {
    ctx := context.Background()
    
    // Create a new constant backoff
    backoff := retry.NewConstant(1 * time.Second)

    // Wrap your retry logic in a function that will be passed to retry.Do
    operation := func(ctx context.Context) error {
        // Your code here. Return retry.RetryableError(err) to retry or nil to stop.
        // Example:
        // err := someOperation()
        // if err != nil {
        //   return retry.RetryableError(err)
        // }
        // return nil

        return nil
    }
    
    // Use retry.Do with the desired context, backoff strategy and the operation
    if err := retry.Do(ctx, backoff, operation); err != nil {
        // Handle error
    }
}

در این مثال، تابع retry.Do تلاش می‌کند تا تابع operation را هر 1 ثانیه یک‌بار انجام دهد تا موفق شود یا زمانبندی انقضا یا لغو شود.

3.2 پیاده‌سازی backoff نمایی

استراتژی backoff نمایی زمان انتظار بین تلاش‌های مجدد را به صورت نمایی افزایش می‌دهد. این استراتژی به کاهش بار بر روی سیستم کمک می‌کند و ویژگی‌های ویژه‌ای را وقتی که با سیستم‌های بزرگ یا خدمات ابری سر و کار داریم ارائه می‌دهد.

نحوه استفاده از backoff نمایی با go-retry به صورت زیر است:

package main

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

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

    // Create a new exponential backoff
    backoff := retry.NewExponential(1 * time.Second)

    // Provide your retry-able operation
    operation := func(ctx context.Context) error {
        // Implement the operation as previously shown
        return nil
    }
    
    // Use retry.Do for executing the operation with exponential backoff
    if err := retry.Do(ctx, backoff, operation); err != nil {
        // Handle error
    }
}

در مورد backoff نمایی، اگر backoff اولیه را 1 ثانیه تنظیم کنیم، تلاش‌های مجدد بعد از 1 ثانیه، 2 ثانیه، 4 ثانیه و غیره انجام خواهند شد که زمان انتظار بین تلاش‌های متوالی به‌طور نمایی افزایش می‌یابد.

3.3 استراتژی بازگشت فیبوناچی

استراتژی بازگشت فیبوناچی از دنباله فیبوناچی برای تعیین زمان انتظار بین تلاش‌های دوباره‌سازی استفاده می‌کند که می‌تواند یک استراتژی خوب برای مسائل مربوط به شبکه باشد که یک زمان انتظار به تدریج افزایش یابنده برای مسائل مربوط به شبکه مفید است.

در زیر اجرای استراتژی بازگشت فیبوناچی با go-retry نشان داده شده است:

package main

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

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

    // ایجاد یک بازگشت فیبوناچی جدید
    backoff := retry.NewFibonacci(1 * time.Second)

    // تعریف یک عملیات برای دوباره‌سازی
    operation := func(ctx context.Context) error {
        // در اینجا منطق انجام عملیاتی که ممکن است شکست بخورد و نیاز به دوباره‌سازی دارد، قرار دارد
        return nil
    }
    
    // انجام عملیات به همراه بازگشت فیبوناچی با استفاده از retry.Do
    if err := retry.Do(ctx, backoff, operation); err != nil {
        // رفع خطا
    }
}

با یک بازگشت فیبوناچی با مقدار اولیه ۱ ثانیه، دوباره‌سازی‌ها پس از ۱ ثانیه، ۱ ثانیه، ۲ ثانیه، ۳ ثانیه، ۵ ثانیه و ... انجام خواهند شد، طبق دنباله فیبوناچی.

فصل ۴: تکنیک‌های پیشرفته دوباره‌سازی و میان‌افزار

۴.۱ بهره‌گیری از گسسته در دوباره‌سازی‌ها

در هنگام اجرای منطق دوباره‌سازی، مهم است که اثر انجام دوباره‌سازی‌های همزمان بر روی یک سیستم مدنظر قرار گیرد که می‌تواند منجر به مسأله گله‌ای ناگهانی شود. برای کاهش این مسأله، می‌توانیم انگشتان تصادفی به فواصل دوباره‌سازی اضافه کنیم. این تکنیک به کاهش تلاش‌های دوباره‌سازی همزمان کلاینت‌های چندگانه کمک می‌کند.

مثال اضافه کردن انگشتان تصادفی:

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

// بازگشت مقدار بعدی، +/- 500ms
b = retry.WithJitter(500 * time.Millisecond, b)

// بازگشت مقدار بعدی، +/- 5% از نتیجه
b = retry.WithJitterPercent(5, b)

۴.۲ تعیین تعداد دوباره‌سازی حداکثر

در برخی حالات، ضروری است که تعداد دوباره‌سازی‌ها را محدود کنیم تا از دوباره‌سازی‌های طولانی و بی‌اثر جلوگیری شود. با تعیین تعداد دوباره‌سازی‌های حداکثر می‌توانیم تعداد تلاش‌ها را قبل از تسلیم شدن در عملیات کنترل کنیم.

مثال تعیین تعداد دوباره‌سازی حداکثر:

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

// متوقف شدن پس از 4 دوباره‌سازی، هنگامی که تلاش 5 امی شکست خورده است
b = retry.WithMaxRetries(4, b)

۴.۳ محدودیت مدت‌های بازگشت فردی

برای اطمینان از اینکه مدت‌های بازگشت فردی یک حدی خاص را نتواند بیشتر کند، می‌توانیم از میان‌افزار CappedDuration استفاده کنیم. این باعث می‌شود که فواصل بازگشت بیشتر از حد زمانی خاص محاسبه نشود و پیش‌بینی‌پذیری رفتار دوباره‌سازی را افزایش دهد.

مثال محدودیت مدت‌های بازگشت فردی:

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

// اطمینان از اینکه مقدار حداکثر ۲ ثانیه باشد
b = retry.WithCappedDuration(2 * time.Second, b)

۴.۴ کنترل مدت‌زمان دوباره‌سازی کلی

در حالت‌هایی که نیاز به محدودیت مدت‌زمان کلی برای کل فرایند دوباره‌سازی وجود دارد، می‌توانیم از میان‌افزار WithMaxDuration برای مشخص کردن یک حداکثر زمان اجرا کلی استفاده کنیم. این باعث می‌شود که فرایند دوباره‌سازی به طور نامحدود ادامه نیابد و یک بودجه زمانی برای دوباره‌سازی اعمال شود.

مثال کنترل مدت‌زمان دوباره‌سازی کلی:

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

// اطمینان از اینکه حداکثر زمان دوباره‌سازی کلی ۵ ثانیه است
b = retry.WithMaxDuration(5 * time.Second, b)