侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

设计一个支持超时和重试机制的并发数据加载器

2025-12-12 / 0 评论 / 4 阅读

题目

设计一个支持超时和重试机制的并发数据加载器

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

协程的取消与超时,结构化并发,异常处理,流控制,资源管理

快速回答

实现要点:

  • 使用withTimeoutOrNull处理单次请求超时
  • 结合retry和指数退避策略实现重试
  • 通过coroutineScope保证结构化并发
  • 使用SupervisorJob防止单个子协程失败影响整体
  • 正确管理资源(如关闭数据库连接)
## 解析

问题场景

在分布式系统中,网络请求可能因各种原因失败。需要实现一个数据加载器,能够:

  1. 并发请求多个数据源
  2. 单个请求超时自动取消
  3. 失败时按指数退避策略重试
  4. 资源异常时安全释放连接

核心实现方案

class DataLoader(private val maxRetries: Int = 3) {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    suspend fun loadConcurrently(sources: List<String>): Map<String, Result> =
        coroutineScope {
            sources.associateWith { source ->
                async { loadWithRetry(source) }
            }.mapValues { it.value.await() }
        }

    private suspend fun loadWithRetry(source: String): Result {
        var currentDelay = 1000L
        repeat(maxRetries) { attempt ->
            try {
                return withTimeoutOrNull(3000) {
                    fetchData(source)
                } ?: throw TimeoutCancellationException("Timeout for $source")
            } catch (e: Exception) {
                if (e is CancellationException) throw e
                if (attempt == maxRetries - 1) throw RetryFailedException(source, e)
                delay(currentDelay)
                currentDelay *= 2  // 指数退避
            }
        }
        throw IllegalStateException("Unreachable")
    }

    private suspend fun fetchData(source: String): Result {
        // 模拟网络请求或数据库操作
        if (Random.nextBoolean()) return Result.Success("Data from $source")
        throw IOException("Failed to fetch $source")
    }

    fun destroy() {
        scope.cancel("DataLoader terminated")
    }
}

sealed class Result {
    data class Success(val data: String) : Result()
    data class Failure(val error: Throwable) : Result()
}

关键原理说明

  • 结构化并发:通过coroutineScope确保所有子协程完成前不退出
  • 超时控制withTimeoutOrNull在超时时返回null并取消内部协程
  • 异常隔离SupervisorJob使单个子协程失败不影响其他协程
  • 资源安全:在destroy()中取消作用域,防止资源泄漏

最佳实践

  1. 重试策略:指数退避避免加重服务器压力
  2. 错误传播:将最终异常封装为Result.Failure而非抛出
  3. 线程调度:使用Dispatchers.IO优化阻塞IO操作
  4. 生命周期管理:在Android的ViewModel中调用destroy()

常见错误

错误示例后果修正方案
使用GlobalScope生命周期泄漏使用自定义作用域
直接捕获Exception误捕CancellationException显式排除取消异常
未处理协程取消资源未释放finally中关闭资源

扩展知识

  • 响应式重试:使用flow.retryWhen实现更灵活的重试逻辑
  • 熔断机制:集成Resilience4j实现故障熔断
  • 并行度控制:通过Semaphore限制并发请求数量
  • 测试策略:使用TestCoroutineDispatcher控制虚拟时间测试超时