GoCasts آموزش Go به زبان ساده

برای ثبت‌نام در بوتکمپ آموزش بکند و گولنگ اینجا را کلیک کنید

استفاده از الگوی retry برای ارتقا سطح پایداری سیستم

در این پست قرار است درباره یکی از راه حل های پایدارتر کردن سیستم صحبت کنیم. امروزه استفاده از سرویس های خارجی یا همان external services در برنامه های وب بسیار رایج است. برنامه های سمت سرور معمولا از این سرویس ها برای دریافت و ارسال اطلاعات استفاده می کنند. سرویس هایی مانند درگاه های پرداخت، درگاه های ارسال پیامک و ایمیل از جمله رایج ترین سرویس های خارجی مورد استفاده است.

استفاده از همین سرویس های خارجی باعث می شود که توجه به الگوهای پایداری سیستم های توزیعه شده (distributed systems) اهمیت یابد. زیرا سیستم های توزیع شده بر بستر شبکه قرار دارند و این بستر شبکه خود دارای مشکلاتی ست که برنامه نویسان کمتر به آن توجه می کنند. یکی از استدلال های غلط قابل اعتماد بودن بستر شبکه (network) است.

Fallacy #1: The network is reliable

همین استدلال اشتباه در نقاط حیاتی سیستم مشکلاتی را به وجود می آورد. یکی از این نقاط حیاتی سرویس های درگاه های پرداخت اینرنتی (ipg) هستند. گاها پیش می آید که به دلایل مختلف درخواست های پرداخت با خطای timeout و یا دیگر خطاهای مربتط با بستر شبکه برخورد می کنند. توجه به این خطاها و استفاده از الگوهایی برای جلوگیری از این معضلات، نقش خیلی مهمی در ارتقاء سطح پایداری سیستم (system stability) دارند.

در خیلی از موارد خطاهای بستر شبکه خطاهای گذرا (transient) هستند و در چند لحظه اتفاق افتاده و برطرف می شوند. برای اینکه بتوان سیستم را در مواجهه با این نوع خطاها مقاوم کرد می توان از الگوی تلاش مجدد retry pattern استفاد کرد.

بهتر است این پست را با نمایش کد برنامه نویسی در زبان Go به اتمام برسانیم. در مثال زیر قرار است با استفاده از تابع getResult از آدرس https://example.com اطلاعاتی گرفته شود.

func main() {
    data, err := getResult()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Print(data)
}

func getResult() ([]byte, error) {
    url := "https://example.com"
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    return body, nil
}

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

func main() {
    err := retry.Do(
        func() error {
            var gErr error
            body, gErr = getResult()
            if gErr != nil {
                return gErr
            }
            return nil
        }, retry.Attempts(3),
        retry.Delay(2000 * time.Millisecond),
    )
    if err != nil {
        log.Fatal(err)
    }
    fmt.Print(data)
}

func getResult() ([]byte, error) {
    url := "https://example.com"
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    return body, nil
}

منابع