侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

设计一个基于协程的并发安全缓存系统

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

题目

设计一个基于协程的并发安全缓存系统

信息

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

考点

协程并发控制,状态管理,异常处理,性能优化

快速回答

实现要点:

  • 使用 Mutex 保护共享状态避免竞态条件
  • 通过 async 启动并发任务并缓存 Deferred 对象
  • 实现缓存击穿防护:相同 key 的请求共享同一异步结果
  • 添加 TTL 机制和后台刷新逻辑
  • 使用 SupervisorJob 隔离异常避免全局崩溃
  • 通过 CoroutineStart.LAZY 延迟计算优化资源
## 解析

问题场景

在高并发系统中,需要实现一个线程安全的缓存,要求:1) 支持并发读写 2) 防止缓存击穿(大量并发请求穿透缓存)3) 支持 TTL 自动刷新 4) 异常隔离不影响整体服务。

核心实现方案

class CoroutineCache {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
    private val mutex = Mutex()
    private val cache = mutableMapOf<String, Any>()
    private val deferredMap = mutableMapOf<String, Deferred<Any>>()

    suspend fun <T : Any> compute(
        key: String,
        ttl: Duration = Duration.INFINITE,
        block: suspend () -> T
    ): T {
        // 1. 检查缓存有效性
        val cached = mutex.withLock { cache[key] }
        if (cached != null && !isExpired(key, ttl)) {
            @Suppress("UNCHECKED_CAST")
            return cached as T
        }

        // 2. 防止缓存击穿
        return mutex.withLock {
            // 双重检查锁定模式
            deferredMap[key]?.let { existing ->
                return@withLock existing.await() as T
            }

            // 3. 创建新异步任务
            val newDeferred = scope.async(start = CoroutineStart.LAZY) {
                try {
                    block().also { result ->
                        mutex.withLock {
                            cache[key] = result as Any
                            deferredMap.remove(key)
                        }
                    }
                } catch (e: Exception) {
                    mutex.withLock { deferredMap.remove(key) }
                    throw e
                }
            }

            deferredMap[key] = newDeferred
            newDeferred.start() // 显式启动避免重复计算
            newDeferred.await() as T
        }
    }

    private fun isExpired(key: String, ttl: Duration): Boolean {
        // 实现基于时间的过期检查(伪代码)
        return ttl != Duration.INFINITE && (currentTime - cacheTime[key]) > ttl
    }
}

原理说明

  • 并发控制Mutex 保护 cachedeferredMap 的读写操作,避免竞态条件
  • 缓存击穿防护:相同 key 的并发请求共享同一个 Deferred 对象,仅首次实际执行计算
  • 异常隔离SupervisorJob 确保单个任务失败不影响其他操作
  • 性能优化CoroutineStart.LAZY 延迟启动协程,避免不必要的计算

最佳实践

  • 使用 Dispatchers.IO 替代 Dispatchers.Default 处理阻塞 I/O 操作
  • 添加缓存淘汰策略(如 LRU)防止内存溢出
  • 实现 refreshAsync 方法后台异步刷新热门数据
  • 监控缓存命中率和延迟指标

常见错误

  • 死锁风险:在 mutex.withLock 内部调用挂起函数(需确保临界区内无挂起)
  • 内存泄漏:未清理 deferredMap 导致对象无法回收
  • 协程泄露:未使用 SupervisorJob 导致异常传播使整个 scope 取消
  • 类型安全:未正确处理泛型类型转换(示例中使用 @Suppress 需谨慎)

扩展知识

  • 缓存雪崩防护:为不同 key 添加随机过期时间偏移量
  • 响应式扩展:使用 Flow 实现缓存更新事件流
  • 分布式场景:结合 Redis 等外部存储实现多节点一致性
  • Structured Concurrency:通过 coroutineScope 子作用域管理生命周期