侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

设计一个基于CompletableFuture的异步缓存系统,支持过期自动刷新和防缓存穿透

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

题目

设计一个基于CompletableFuture的异步缓存系统,支持过期自动刷新和防缓存穿透

信息

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

考点

CompletableFuture高级用法, 并发编程, 缓存设计模式, 函数式编程, 异常处理

快速回答

实现要点:

  • 使用ConcurrentHashMap存储CompletableFuture保证原子性
  • 通过CompletableFuture.supplyAsync()异步加载数据
  • 利用ScheduledExecutorService实现定期刷新
  • 采用completeOnTimeout()处理超时降级
  • 使用Optional包装空值防止缓存穿透
  • 异常处理通过exceptionally()实现降级
## 解析

核心设计原理

通过CompletableFuture的异步特性和ConcurrentHashMap的原子操作实现:

  1. 线程安全computeIfAbsent()保证单Key单线程加载
  2. 异步刷新:定时任务异步重建缓存,不阻塞请求
  3. 防穿透:空值用Optional.empty()缓存
  4. 降级机制:超时/异常时返回旧值或默认值

代码实现示例

public class AsyncCache<K, V> {
    private final ConcurrentHashMap<K, CompletableFuture<Optional<V>>> cache = new ConcurrentHashMap<>();
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
    private final Function<K, V> loader;
    private final long refreshMillis;

    public AsyncCache(Function<K, V> loader, long refreshMillis) {
        this.loader = loader;
        this.refreshMillis = refreshMillis;
    }

    public CompletableFuture<Optional<V>> get(K key) {
        return cache.computeIfAbsent(key, k -> {
            CompletableFuture<Optional<V>> future = loadAsync(k);
            scheduleRefresh(key, future);
            return future;
        });
    }

    private CompletableFuture<Optional<V>> loadAsync(K key) {
        return CompletableFuture.supplyAsync(() -> {
                V value = loader.apply(key);
                return Optional.ofNullable(value);
            })
            .completeOnTimeout(Optional.empty(), 500, TimeUnit.MILLISECONDS) // 超时降级
            .exceptionally(ex -> {
                System.err.println("Load failed: " + ex.getMessage());
                return Optional.empty(); // 异常降级
            });
    }

    private void scheduleRefresh(K key, CompletableFuture<Optional<V>> future) {
        scheduler.schedule(() -> {
            if (!future.isDone() || future.isCompletedExceptionally()) return;

            future.thenAcceptAsync(result -> {
                if (result.isPresent()) {
                    cache.put(key, loadAsync(key)); // 异步刷新
                }
            }, scheduler);
        }, refreshMillis, TimeUnit.MILLISECONDS);
    }
}

最佳实践

  • 资源隔离:为加载任务和刷新任务使用独立线程池
  • 背压控制:通过Semaphore限制并发加载数
  • 缓存清理:添加LRU机制防止内存泄漏
  • 监控:记录缓存命中率/加载耗时等指标

常见错误

错误类型后果解决方案
未处理加载异常Future永久异常状态使用exceptionally()兜底
直接缓存null缓存穿透风险Optional包装空值
同步刷新阻塞请求延迟飙升确保刷新操作完全异步
未限制刷新并发资源耗尽风险添加刷新队列和限流

扩展知识

  • 响应式整合:可返回Mono/Flux接入Spring WebFlux
  • 分布式扩展:结合Redis Pub/Sub实现集群级缓存刷新
  • 高级优化
    • 使用StampedLock提升读性能
    • 采用ForkJoinPool实现工作窃取
    • 添加二级缓存(Caffeine + Redis)
  • 对比框架:相比Caffeine/Guava Cache的优势:
    • 更灵活的回调链(thenCompose()等)
    • 直接支持响应式编程
    • 更细粒度的超时控制