题目
设计一个支持超时和重试机制的并发数据加载器
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
协程的取消与超时,结构化并发,异常处理,流控制,资源管理
快速回答
实现要点:
- 使用
withTimeoutOrNull处理单次请求超时 - 结合
retry和指数退避策略实现重试 - 通过
coroutineScope保证结构化并发 - 使用
SupervisorJob防止单个子协程失败影响整体 - 正确管理资源(如关闭数据库连接)
问题场景
在分布式系统中,网络请求可能因各种原因失败。需要实现一个数据加载器,能够:
- 并发请求多个数据源
- 单个请求超时自动取消
- 失败时按指数退避策略重试
- 资源异常时安全释放连接
核心实现方案
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()中取消作用域,防止资源泄漏
最佳实践
- 重试策略:指数退避避免加重服务器压力
- 错误传播:将最终异常封装为
Result.Failure而非抛出 - 线程调度:使用
Dispatchers.IO优化阻塞IO操作 - 生命周期管理:在Android的ViewModel中调用
destroy()
常见错误
| 错误示例 | 后果 | 修正方案 |
|---|---|---|
使用GlobalScope | 生命周期泄漏 | 使用自定义作用域 |
直接捕获Exception | 误捕CancellationException | 显式排除取消异常 |
| 未处理协程取消 | 资源未释放 | 在finally中关闭资源 |
扩展知识
- 响应式重试:使用
flow.retryWhen实现更灵活的重试逻辑 - 熔断机制:集成Resilience4j实现故障熔断
- 并行度控制:通过
Semaphore限制并发请求数量 - 测试策略:使用
TestCoroutineDispatcher控制虚拟时间测试超时