移动开发中的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)。
  • 幂等性 — 安全重试的关键要求:重复请求不应引起副作用。
  • Circuit Breaker — 在服务长时间不可用时停止重试以节省资源的机制。

什么是Retry Policy?

Retry Policy — 是一种软件策略,定义客户端在网络请求失败时的行为:哪些错误应重试、重试多少次、以什么延迟以及何时停止尝试。在移动应用中,由于移动网络的不稳定性和服务器端可能的临时故障,重试策略至关重要。

基本的Retry Policy包含三个参数:最大重试次数(maxRetries)、初始延迟(baseDelay)和延迟增加策略(backoff strategy)。此外,可以指定需要以重试响应的HTTP状态码列表,以及中断所有尝试的超时时间。

根据Martin Kleppmann的“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时最重要的决定。重复非幂等的HTTP 401请求可能导致帐户被锁定,而重复HTTP 400可能导致数据重复。始终显式配置要重试的代码列表。

主要重试策略

Fixed interval — 最简单的策略:每次重试在相同时间间隔后执行。例如,延迟2秒时,应用在2、2、2秒后重试请求。Fixed interval实现简单且可预测,但在大规模故障时对服务器产生均匀负载。

Incremental interval — 延迟随每次重试线性增加:第一次重试在1秒后,第二次在2秒后,第三次在3秒后,依此类推。这种策略在重复故障时为服务器提供更多恢复时间,但对于同时失败的大量客户端仍然可预测。

策略延迟公式累计时间(3次尝试)应用
Fixeddelay = D3 × D简单场景,本地超时
Incrementaldelay = N × D6 × D逐步降低负载
Exponentialdelay = D × 2^N7 × D大规模故障,云服务
Exponential + Jitterdelay = random(0, D × 2^N)可变高负载,微服务

策略的选择取决于应用的性质。对于移动设备上数据同步的后台任务,带jitter的指数策略是最优的——以最小的服务器和用户设备负载提供最高的成功概率。

Exponential Backoff与Jitter

Exponential backoff — 每次尝试之间延迟加倍的策略。如果初始延迟为1秒,则延迟序列为1、2、4、8、16秒。这为服务器提供了指数级增长的恢复时间。

Jitter — 防止多个客户端同步重复请求的随机延迟偏差(thundering herd问题)。没有jitter,一千个具有相同Retry Policy的客户端将同时重复请求,在服务器上产生峰值负载。Jitter在时间上分散重试。

在Kotlin中使用协程实现

Kotlin协程可以在不阻塞主线程的情况下实现带jitter的exponential backoff。kotlinx-coroutines中的retry函数接受重试条件和包含请求体的代码块,自动管理延迟和尝试次数。

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函数接受一个包含网络调用的lambda,并以exponential backoff和jitter执行它。如果错误不可重试(non-retriable)或超过最大尝试次数,函数返回错误。延迟乘以0.5到1.5之间的随机系数,以实现重试的均匀分布。

Circuit Breaker与放弃重试

Circuit Breaker — 一种设计模式,在服务长时间不可用时防止无限重复请求。当错误数量超过阈值时,Circuit Breaker进入OPEN状态并立即返回错误而不执行请求,让服务器有时间恢复。

在移动应用中,当API因计划维护或运营商网络故障而不可用时,Circuit Breaker特别有用。没有它,应用将把电池和流量浪费在无限重试上,恶化用户体验并缩短设备的电池寿命。

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
        }
    }
}

Kotlin中的Circuit Breaker实现包含错误计数器和恢复定时器。protect方法在执行被阻止的请求之前检查当前状态,并在故障时更新错误计数器。达到failureThreshold阈值后,所有请求立即被拒绝,直到timeoutMs过期。

移动应用中的Retry Policy

移动网络具有使Retry Policy特别重要的特性。Wi-Fi和移动数据之间的切换、地铁和隧道中的信号丢失、运营商级别的临时封锁——所有这些场景都会导致请求失败,而重试可以成功处理这些失败。

在Android上,Retrofit库和OkHttp通过Interceptor提供内置的RetryPolicy机制。在iOS上,问题通过URLSessionConfiguration和自定义委托解决。对于跨平台开发,Ktor(KMP)包含内置的retry支持,具有可配置的策略。

在iOS上使用Combine实现

Combine — Apple的响应式编程框架。Combine中的retry操作符在出错时重复发布者指定次数,但不允许配置重试之间的延迟。对于完整的Retry Policy,使用catchflatMap带延迟的自定义组合。

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()
    }
}

Combine中Publisher的retryWithBackoff扩展通过递归调用(减少计数器和加倍延迟)实现exponential backoff。delay操作符在重试之间创建暂停,catch捕获错误并决定是重试还是返回failure。

Retry Policy的常见错误

第一个错误——在不检查幂等性的情况下重试请求。如果服务器已创建资源但因网络故障未返回确认,重复请求将创建重复数据。对于POST请求,始终在标头中使用幂等键(Idempotency-Key),或仅对GET、PUT和DELETE应用retry。

第二个错误——无限重试(retry forever)。始终设置最大尝试次数(移动应用为3-5次)和所有尝试的总超时时间。无限重试会消耗电池并在服务器上产生寄生负载,特别是在数据库迁移或API更改时。

第三个错误——忽略应用的上下文。如果用户关闭了应用或进入后台模式,活动的Retry Policy应正确取消。使用带有SupervisorScope的协程或具有UI生命周期的Combine,在屏幕关闭时自动取消重试。

第四个错误——不记录重试尝试。没有记录,您将不知道有多少请求被重试、出现了哪些错误以及您的Retry Policy有多有效。添加指标:重试次数、重试后的成功率、延迟分布。这些数据将有助于为特定应用调整最优策略参数。

常见问题

在移动应用中应该重试请求多少次?

大多数场景的最佳重试次数为3-5次。后台同步允许5-7次,交互式请求(例如提交表单)——不超过3次。更多重试不会增加成功概率,但会消耗用户的电池和流量。

用简单的话说,什么是exponential backoff?

Exponential backoff——重试尝试之间延迟加倍:1秒、2、4、8、16,依此类推。如果服务器过载,第一次重试之间的短暂暂停使其能够快速响应,而每次后续尝试中逐渐增加的暂停则为服务器提供越来越多的恢复时间。

哪些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,在Android上使用NetworkInterceptor(OkHttp),在iOS上使用URLProtocol(URLSession)模拟网络故障。设置参数:错误频率、不可用持续时间和响应代码。使用MockWebServer(OkHttp)或OHHTTPStubs(iOS)的单元测试在没有真实网络的情况下检查重试逻辑。

总结

  • Retry Policy — 在临时故障时自动重试网络请求的策略,具有可配置的延迟和尝试次数参数。
  • 带Jitter的Exponential backoff — 移动应用的基本策略,可减少大规模故障时的服务器负载并防止thundering herd效应。
  • 幂等性 — 安全重试非GET请求的强制条件:没有它,重试会创建重复数据或产生不良副作用。
  • Circuit Breaker补充了Retry Policy,防止服务长时间不可用时无限重试,节省设备资源。
  • 错误分类 — 分为retriable(503、502、timeout)和non-retriable(400、401、403)对于重试策略的正确运行至关重要。
  • 最多3-5次重试用于交互场景,最多7次用于后台同步——根据Google Developer Relations的数据,这是移动应用的最佳值。
  • 建议 — 为移动应用中的所有网络请求实施带有exponential backoff、Circuit Breaker和记录的Retry Policy。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读