Retry Policy در توسعه موبایل — ماهیت، استراتژی‌ها و اصل

نویسنده: IT Sectr منتشر شده: 2026-03-11 زمان مطالعه: 10 دقیقه

Retry Policy — خط‌مشی درخواست‌های مجدد — مجموعه قوانینی که تعیین می‌کند چه زمانی و چگونه یک برنامه موبایل به طور خودکار فراخوانی‌های شبکه ناموفق را تکرار می‌کند. در اتصال ناپایدار یا خطاهای موقت سرور، یک خط‌مشی تکرار صحیح قابلیت اطمینان برنامه را بدون دخالت کاربر افزایش می‌دهد. طبق تحقیقات Google Developer Relations (2025)، پیاده‌سازی صحیح Retry Policy درصد درخواست‌های از دست رفته را 40-60% در برنامه‌های موبایل با عملیات شبکه مکرر کاهش می‌دهد.

نکات کلیدی

  • Retry Policy — استراتژی تکرار خودکار درخواست‌ها هنگام خرابی شبکه یا خطاهای موقت سرور.
  • Exponential backoff — روش افزایش تأخیر بین تکرارها برای کاهش بار روی سرور.
  • Jitter — انحراف تصادفی تأخیر که از تأثیر «ازدحام» (thundering herd) جلوگیری می‌کند.
  • Idempotency — الزام کلیدی برای تکرارهای ایمن: درخواست مجدد نباید عوارض جانبی ایجاد کند.
  • Circuit Breaker — مکانیسم توقف تکرارها در صورت عدم دسترسی طولانی مدت سرویس برای صرفه‌جویی در منابع.

Retry Policy چیست؟

Retry Policy — یک استراتژی نرم‌افزاری است که رفتار کلاینت را هنگام خرابی درخواست شبکه تعیین می‌کند: کدام خطاها را تکرار کند، چند بار، با چه تأخیری و چه زمانی تلاش‌ها را متوقف کند. در برنامه‌های موبایل، خط‌مشی تکرار به دلیل ناپایداری شبکه‌های موبایل و خرابی‌های موقت احتمالی در سمت سرور حیاتی است.

Retry Policy پایه شامل سه پارامتر است: حداکثر تعداد تکرار (maxRetries)، تأخیر اولیه (baseDelay) و استراتژی افزایش تأخیر (backoff strategy). علاوه بر این، می‌توان فهرستی از کدهای وضعیت HTTP که باید به آن‌ها با تکرار پاسخ داد و یک زمان انتظار برای قطع همه تلاش‌ها تعیین کرد.

طبق کتاب «Designing Data-Intensive Applications» مارتین کلبمن، 50% خرابی‌ها در سیستم‌های توزیع‌شده موقتی هستند و با یک تلاش مجدد برطرف می‌شوند. این امر Retry Policy را به یکی از مؤثرترین و ارزان‌ترین روش‌ها برای افزایش تحمل‌پذیری خطای برنامه موبایل بدون تغییر در معماری سرور تبدیل می‌کند.

کدام خطاها را باید تکرار کرد

خطاهای موقت (retriable) — تنها نوع خرابی که Retry Policy باید به آن واکنش نشان دهد. این موارد شامل قطع شدن زمان اتصال (SocketTimeoutException)، عدم دسترسی موقت سرور (HTTP 503, 502) و خطاهای DNS است. خطاهای دائمی — HTTP 400, 401, 403, 404 — تکرار آن‌ها بی‌معنی است، زیرا نشان‌دهنده مشکل در درخواست هستند، نه در شبکه یا سرور.

طبق تحقیقات AWS Architecture Blog، طبقه‌بندی صحیح خطاها به retriable و non-retriable مهم‌ترین تصمیم در طراحی Retry Policy است. تکرار یک درخواست غیر idempotent با HTTP 401 می‌تواند منجر به مسدود شدن حساب شود و تکرار HTTP 400 منجر به ایجاد داده‌های تکراری. همیشه فهرست کدهای تکرار را به صراحت پیکربندی کنید.

استراتژی‌های اصلی تکرار

Fixed interval — ساده‌ترین استراتژی: هر تکرار پس از یک بازه زمانی یکسان انجام می‌شود. مثلاً با تأخیر ۲ ثانیه، برنامه درخواست را پس از ۲، ۲، ۲ ثانیه تکرار می‌کند. Fixed interval در پیاده‌سازی ساده و قابل پیش‌بینی است، اما در خرابی‌های انبوه بار یکنواختی روی سرور ایجاد می‌کند.

Incremental interval — تأخیر با هر تکرار به صورت خطی افزایش می‌یابد: اولین تکرار پس از ۱ ثانیه، دومین پس از ۲، سومین پس از ۳ و غیره. این استراتژی در خرابی‌های مکرر زمان بیشتری برای بازیابی به سرور می‌دهد، اما همچنان برای بسیاری از کلاینت‌هایی که همزمان خراب می‌شوند قابل پیش‌بینی است.

استراتژیفرمول تأخیرزمان تجمعی (۳ تلاش)کاربرد
Fixeddelay = D۳ × Dسناریوهای ساده، قطعی‌های موضعی
Incrementaldelay = N × D۶ × Dکاهش تدریجی بار
Exponentialdelay = D × ۲^N۷ × Dخرابی‌های انبوه، سرویس‌های ابری
Exponential + Jitterdelay = random(0, D × ۲^N)متغیربار بالا، میکروسرویس‌ها

انتخاب استراتژی به ماهیت برنامه بستگی دارد. برای وظایف پس‌زمینه همگام‌سازی داده در دستگاه‌های موبایل، استراتژی exponential با jitter بهینه است — بالاترین احتمال موفقیت را با حداقل بار روی سرور و دستگاه کاربر می‌دهد.

Exponential Backoff و Jitter

Exponential backoff — استراتژی‌ای که در آن تأخیر بین تکرارها با هر تلاش دو برابر می‌شود. اگر تأخیر اولیه ۱ ثانیه باشد، دنباله تأخیرها ۱، ۲، ۴، ۸، ۱۶ ثانیه خواهد بود. این به سرور زمان نمایی فزاینده‌ای برای بازیابی می‌دهد.

Jitter — انحراف تصادفی تأخیر که از درخواست‌های مجدد همزمان از سوی کلاینت‌های متعدد جلوگیری می‌کند (مشکل thundering herd). بدون jitter، هزاران کلاینت با Retry Policy یکسان به طور همزمان درخواست‌ها را تکرار می‌کنند و بار اوجی روی سرور ایجاد می‌کنند. Jitter تکرارها را در زمان پخش می‌کند.

پیاده‌سازی در Kotlin با Coroutines

Coroutines در Kotlin امکان پیاده‌سازی exponential backoff با jitter بدون مسدود کردن رشته اصلی را فراهم می‌کند. تابع retry از kotlinx-coroutines شرط تکرار و بلوکی با بدنه درخواست را می‌پذیرد و به طور خودکار تأخیرها و تعداد تلاش‌ها را مدیریت می‌کند.

kotlin
suspend fun RetryPolicy.executeWithRetry(
    block: suspend () -> Result<T>
): Result<T> {
    var lastError: Throwable? = null
    repeat(maxRetries + 1) { attempt ->
        try {
            return block()
        } catch (e: Exception) {
            if (!isRetriable(e) || attempt == maxRetries) {
                return Result.failure(e)
            }
            val delay = (baseDelayMs * (1 shl attempt))
                .toLong()
            val jitteredDelay = (delay * (0.5 + Random.nextDouble())).toLong()
            delay(jitteredDelay)
            lastError = e
        }
    }
    return Result.failure(lastError!!)
}

تابع executeWithRetry یک لامبدا با فراخوانی شبکه می‌پذیرد و آن را با exponential backoff و jitter اجرا می‌کند. اگر خطا قابل تکرار نباشد (غیر retriable) یا حداکثر تعداد تلاش‌ها exceeded شود، تابع خطا برمی‌گرداند. تأخیر برای توزیع یکنواخت تکرارها در ضریب تصادفی ۰.۵ تا ۱.۵ ضرب می‌شود.

Circuit Breaker و چشم‌پوشی از تکرار

Circuit Breaker — الگوی طراحی که از درخواست‌های مجدد بی‌نهایت در صورت عدم دسترسی طولانی مدت سرویس جلوگیری می‌کند. هنگامی که تعداد خطاها از آستانه فراتر رود، Circuit Breaker به حالت OPEN می‌رود و بلافاصله بدون اجرای درخواست خطا برمی‌گرداند و به سرور زمان بازیابی می‌دهد.

در برنامه‌های موبایل، Circuit Breaker به ویژه در صورت عدم دسترسی API به دلیل نگهداری برنامه‌ریزی شده یا خرابی شبکه اپراتور مفید است. بدون آن، برنامه باتری و ترافیک را صرف تلاش‌های مجدد بی‌نهایت می‌کند و تجربه کاربری را بدتر کرده و عمر باتری دستگاه را کاهش می‌دهد.

نمودار حالت‌های Circuit Breaker

سه حالت Circuit Breaker: CLOSED (عملکرد عادی، درخواست‌ها اجرا می‌شوند)، OPEN (رد، درخواست‌ها مسدود می‌شوند) و HALF_OPEN (درخواست آزمایشی برای بررسی بازیابی). پس از مدت زمان مشخصی در حالت OPEN، سوئیچ به HALF_OPEN می‌رود و یک درخواست را اجرا می‌کند — در صورت موفقیت به CLOSED برمی‌گردد، در صورت شکست — به OPEN.

kotlin
class CircuitBreaker(
    private val failureThreshold: Int = 3,
    private val timeoutMs: Long = 30000
) {
    private var state = State.CLOSED
    private var failureCount = 0
    private var lastFailureTime: Long = 0

    suspend fun T.protect(block: suspend () -> T): T {
        checkState()
        return try {
            val result = block()
            onSuccess()
            result
        } catch (e: Exception) {
            onFailure()
            throw e
        }
    }
}

پیاده‌سازی Circuit Breaker در Kotlin شامل شمارنده خطا و تایمر بازیابی است. متد protect وضعیت فعلی را قبل از اجرای درخواست مسدود شده بررسی می‌کند و شمارنده خطا را در هنگام خرابی به‌روز می‌کند. پس از رسیدن به آستانه failureThreshold، همه درخواست‌ها تا پایان timeoutMs بلافاصله رد می‌شوند.

Retry Policy در برنامه‌های موبایل

شبکه‌های موبایل ویژگی‌هایی دارند که Retry Policy را به ویژه مهم می‌کند. تغییر بین Wi-Fi و داده موبایل، از دست دادن سیگنال در مترو و تونل‌ها، مسدودسازی‌های موقت در سطح اپراتور — همه این سناریوها منجر به خرابی درخواست‌هایی می‌شوند که با تلاش‌های مجدد با موفقیت مدیریت می‌شوند.

در اندروید، کتابخانه Retrofit و OkHttp از طریق Interceptor مکانیزم داخلی RetryPolicy را فراهم می‌کنند. در iOS، این کار از طریق URLSessionConfiguration و نمایندگی سفارشی حل می‌شود. برای توسعه بین پلتفرمی، Ktor (KMP) شامل پشتیبانی داخلی retry با استراتژی‌های قابل تنظیم است.

پیاده‌سازی در iOS با Combine

Combine — فریم‌ورک اپل برای برنامه‌نویسی واکنش‌گرا. عملگر retry در Combine در صورت خطا publisher را تعداد مشخصی تکرار می‌کند، اما اجازه پیکربندی تأخیر بین تکرارها را نمی‌دهد. برای Retry Policy کامل، از ترکیب سفارشی catch و flatMap با تأخیر استفاده می‌شود.

swift
extension Publisher {
    func retryWithBackoff(
        retries: Int = 3,
        baseDelay: TimeInterval = 1.0
    ) -> AnyPublisher<Output, Failure> {
        return self.catch { error -> AnyPublisher in
            guard retries > 0 else {
                return Fail(error).eraseToAnyPublisher()
            }
            return Just(())
                .delay(for: .seconds(baseDelay), scheduler: DispatchQueue.main)
                .flatMap { self.retryWithBackoff(
                    retries: retries - 1,
                    baseDelay: baseDelay * 2
                ) }
                .eraseToAnyPublisher()
        }
        .eraseToAnyPublisher()
    }
}

افزونه retryWithBackoff برای Publisher در Combine exponential backoff را از طریق فراخوانی بازگشتی با کاهش شمارنده و دو برابر کردن تأخیر پیاده‌سازی می‌کند. عملگر delay مکثی بین تکرارها ایجاد می‌کند و catch خطا را گرفته و تصمیم می‌گیرد دوباره تلاش کند یا failure برگرداند.

اشتباهات رایج در Retry Policy

اشتباه اول — تکرار درخواست‌ها بدون بررسی idempotency. اگر سرور منبعی ایجاد کرده اما به دلیل خرابی شبکه تأییدیه برنگردانده باشد، درخواست مجدد یک کپی تکراری ایجاد می‌کند. برای درخواست‌های POST همیشه از کلید idempotent (Idempotency-Key) در هدر استفاده کنید یا retry را فقط برای GET، PUT و DELETE اعمال کنید.

اشتباه دوم — تکرار بی‌نهایت (retry forever). همیشه حداکثر تعداد تلاش (۳-۵ برای برنامه‌های موبایل) و یک بازه زمانی کلی برای همه تلاش‌ها تعیین کنید. تکرارهای بی‌نهایت باتری را خالی کرده و بار انگلی روی سرور ایجاد می‌کنند، به ویژه هنگام مهاجرت پایگاه داده یا تغییر API.

اشتباه سوم — نادیده گرفتن زمینه برنامه. اگر کاربر برنامه را بست یا به حالت پس‌زمینه رفت، Retry Policy فعال باید به درستی لغو شود. از coroutines با SupervisorScope یا Combine با چرخه عمر UI برای لغو خودکار تکرارها هنگام بسته شدن صفحه استفاده کنید.

اشتباه چهارم — عدم ثبت تلاش‌های مجدد. بدون ثبت نمی‌دانید چند درخواست تکرار شده، چه خطاهایی رخ داده و Retry Policy شما چقدر مؤثر است. معیارهایی اضافه کنید: تعداد تکرارها، موفقیت پس از تکرار، توزیع تأخیرها. این داده‌ها به تنظیم پارامترهای بهینه استراتژی برای برنامه خاص کمک می‌کنند.

سؤالات متداول

چند بار درخواست را در برنامه موبایل تکرار کنیم؟

تعداد بهینه تکرار — ۳-۵ تلاش برای اکثر سناریوها. برای همگام‌سازی پس‌زمینه ۵-۷ تلاش مجاز است، برای درخواست‌های تعاملی (مثلاً ارسال فرم) — بیش از ۳ تلاش. تعداد بیشتر تکرارها احتمال موفقیت را افزایش نمی‌دهد، اما باتری و ترافیک کاربر را مصرف می‌کند.

Exponential backoff به زبان ساده چیست؟

Exponential backoff — دو برابر شدن تأخیر بین تلاش‌های مجدد: ۱ ثانیه، ۲، ۴، ۸، ۱۶ و غیره. اگر سرور بیش از حد بارگذاری شده باشد، مکث کوتاه بین اولین تکرارها به آن اجازه می‌دهد سریع پاسخ دهد و مکث رو به افزایش با هر تلاش بعدی زمان بیشتری برای بازیابی به سرور می‌دهد.

کدام کدهای HTTP را باید تکرار کرد؟

فقط خطاهای موقت را تکرار کنید: 408 (Request Timeout)، 429 (Too Many Requests)، 502 (Bad Gateway)، 503 (Service Unavailable)، 504 (Gateway Timeout). خطاهای 4xx (به جز 408 و 429) نشان‌دهنده مشکلات مشتری هستند — تکرار آن‌ها بی‌معنی است و می‌تواند برای داده‌های کاربر خطرناک باشد.

تفاوت Retry Policy با Circuit Breaker چیست؟

Retry Policy تکرار یک درخواست را در صورت خرابی مدیریت می‌کند. Circuit Breaker وضعیت اتصال به سرویس را مدیریت می‌کند: با انباشت خطاها مدار را باز می‌کند (OPEN) و درخواست‌های جدید را مجاز نمی‌کند. Retry در سطح یک فراخوانی، Circuit Breaker در سطح ادغام با سرویس کار می‌کند.

چگونه Retry Policy را در دستگاه‌های موبایل تست کنیم؟

برای تست Retry Policy از NetworkInterceptor (OkHttp) در اندروید و URLProtocol (URLSession) در iOS برای شبیه‌سازی خرابی شبکه استفاده کنید. پارامترها را تنظیم کنید: میزان خطا، مدت عدم دسترسی و کدهای پاسخ. تست‌های واحد با MockWebServer (OkHttp) یا OHHTTPStubs (iOS) منطق تکرار را بدون شبکه واقعی بررسی می‌کنند.

خلاصه

  • Retry Policy — استراتژی تکرار خودکار درخواست‌های شبکه در خرابی‌های موقت با پارامترهای قابل تنظیم تأخیر و تعداد تلاش.
  • Exponential backoff با jitter — استراتژی پایه برای برنامه‌های موبایل که بار سرور را در خرابی‌های انبوه کاهش داده و از اثر thundering herd جلوگیری می‌کند.
  • Idempotency — شرط اجباری برای تکرار ایمن درخواست‌های غیر GET: بدون آن، تکرار داده‌های تکراری یا عوارض جانبی ناخواسته ایجاد می‌کند.
  • Circuit Breaker مکمل Retry Policy است، از تکرارهای بی‌نهایت در صورت عدم دسترسی طولانی مدت سرویس جلوگیری کرده و منابع دستگاه را ذخیره می‌کند.
  • طبقه‌بندی خطاها — تقسیم به retriable (503، 502، timeout) و non-retriable (400، 401، 403) برای عملکرد صحیح خط‌مشی تکرار حیاتی است.
  • حداکثر ۳-۵ تکرار در سناریوهای تعاملی و تا ۷ برای همگام‌سازی پس‌زمینه — مقدار بهینه برای برنامه‌های موبایل طبق Google Developer Relations.
  • توصیه — Retry Policy را با exponential backoff، Circuit Breaker و ثبت وقایع برای همه درخواست‌های شبکه در برنامه موبایل پیاده‌سازی کنید.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید