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

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

آشنایی با پکیج Context در زبان Go

احتمالا تا به حال پیش آمده است که سرویسی که نوشته اید درخواستی را به یک سرویس خارجی ارسال کند و با خطای timeout مواجه شوید. در مقاله پیشین گفتیم که یکی از راه حل های بهتر کردن این شرایط استفاده از retry-pattern است. با الگوی تلاش مجدد شما می توانید یک درخواست را چندین بار تکرار کنید تا بتوانید پاسخ مورد نظر خود را از سرور دریافت کنید.

نکته خیلی مهمی که در مورد الگوی تلاش مجدد وجود دارد این است که این راه حل در صورتی مشکل را حل می کند که خطای پیش آمده از بستر شبکه باشد و بصورت گذرا باشد، اما اگر سرویس خارجی مورد نظر از دسترس خارج شده باشد یا مشکل بستر شبکه گذرا نباشد، الگوی تلاش مجدد فقط باعث اتلاف بیشتر منابع سیستم می شود. فرض کنید شما الگو را برای هر درخواست ۵ بار تکرار کنید و هر بار ۱۲۰ ثانیه بطور متوسط طول بکشد که خطای timeout دریافت شود، این مساله باعث می شود که منابع سیستم به شدت تلف شود، مخصوصا اگر تعداد این درخواست ها زیاد باشد. یک راه برای پیشگیری از این مشکل استفاده از Context است. کتابخانه Context در سال ۲۰۱۴ تسوط Sameer Ajmani معرفی شد و از نسخه ۱.۷ بصورت یک پکیج استاندارد Go شناخته شد. یک Context یک ضرب الاجل (deadline) است که شما می توانید آن را به یک پروسه پاس بدهید. این پروسه متوجه می شود که اگر موعد Context فرا برسد باید خارج شود و کنترل برنامه را به قسمتی که از آنجا فراخوانده شده برگرداند. این قابلیت برای ارتباط با API های خارجی، دیتابیس و دستورات سیستم می تواند بسیار مفید باشد.

Samir: I have a Go joke, but it's missing Context.

TODO The following supposes that the reader knows about goroutines and channels ..

همین استدلال اشتباه در نقاط حیاتی سیستم مشکلاتی را به وجود می آورد. یکی از این نقاط حیاتی سرویس های درگاه های پرداخت اینرنتی (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
}

منابع