实用网络站
白蓝主题五 · 清爽阅读
首页  > 服务器维护

Kotlin协程中使用retry实现网络请求重试

在服务器维护过程中,经常遇到外部接口不稳定、ref="/tag/72/" style="color:#E3A3CF;font-weight:bold;">网络抖动导致的请求失败。比如某个定时任务需要调用第三方API获取数据,但偶尔因为对方服务短暂不可用而中断。这时候直接抛异常显然不够友好,更合理的做法是加个重试机制。

协程中的retry怎么用

Kotlin协程提供了 retry 相关的扩展能力,虽然标准库没有直接叫 retry() 的函数,但我们可以通过 supervisorScope 配合循环和延迟来实现可靠的重试逻辑。

比如写一个通用的重试方法,最多尝试三次,每次间隔500毫秒:

suspend fun <T> retry(maxRetries: Int, block: suspend () -> T): T {
    var failure: Throwable? = null
    repeat(maxRetries + 1) {
        try {
            return block()
        } catch (e: Throwable) {
            failure = e
            delay(500L)
        }
    }
    throw failure!!
}

然后在实际调用时这样使用:

val result = retry(3) {
    httpClient.get<String>("https://api.example.com/status")
}

结合指数退避更稳妥

如果每次都等同样时间,可能刚好撞上对方服务高峰期。可以改成指数退避策略,第一次等500毫秒,第二次1秒,第三次2秒:

var attempts = 0
repeat(maxRetries + 1) {
    try {
        return block()
    } catch (e: Throwable) {
        if (++attempts >= maxRetries) throw e
        delay((500L shl attempts).coerceAtMost(10000)) // 最大不超过10秒
    }
}

这种写法在面对突发性负载或短暂熔断时特别有用,避免雪崩效应。

注意别把错误请求一直重试

有些错误重试也没意义,比如401未授权或者404找不到资源。可以在捕获异常后判断类型,只对网络超时、连接失败这类临时问题重试:

catch (e: IOException) {
    // 网络层问题,值得重试
} catch (e: HttpException) {
    if (e.response().code() in setOf(500, 502, 503)) {
        // 服务端内部错误,可以重试
    } else {
        throw e // 其他HTTP错误直接抛出
    }
}

这样既保证了容错性,又不会无意义地浪费资源。

线上系统难免遇到各种波动,加上一层智能重试,能让服务稳定不少。特别是在做微服务间通信时,这点小改进往往能减少一大半告警。