高并发缓存方案:4个技巧让性能提升50倍

5 阅读1分钟

概述

读取缓存比读取数据库实在是快的太多了,所以在软件系统开发中,缓存通常是用来加快数据访问的非常有效的手段。

实现方案

下面是高性能并发的实现方案的精简版代码:

public async Task<string> GetDataAsync(string key)
{
    // 1️⃣ 缓存命中 → 直接返回(90%+的请求走这里)
    var cached = await _cache.GetAsync(key);
    if (cached != null) return cached;
 
    // 2️⃣ 布隆过滤器 → 拦截不存在的key
    if (!_bloomFilter.MightContain(key)) return null;
 
    // 3️⃣ Per-Key锁 → 只锁当前key
    var semaphore = _keyLocks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
    await semaphore.WaitAsync();
    
    try
    {
        // 4️⃣ Double-Check → 避免重复查询
        cached = await _cache.GetAsync(key);
        if (cached != null) return cached;
 
        // 5️⃣ 查询数据库
        var data = await _database.QueryAsync(key);
        
        // 6️⃣ 写缓存(含空值保护)
        await _cache.SetAsync(
            key, 
            data ?? "nil", 
            data != null ? TimeSpan.FromMinutes(30) : TimeSpan.FromMinutes(5)
        );
        
        return data;
    }
    finally
    {
        semaphore.Release();
    }
}

总结

这个方案是单体应用方案,它的核心思想是使用细粒度的 per-key 锁,并通过布隆过滤器前置过滤,实现高并发缓存读写,如果面对的是分布式应用,则又复杂得多了。

• Per-key 锁、双重检查,只有相同的 key 才会互斥,不同的 key 可以并发执行,同时避免热点数据过期瞬间的缓存击穿
• 布隆过滤器、缓存空值(nil),应对查询不存在的数据,防止缓存穿透
• 可扩展使用随机TTL,避免大量key同时过期,导致缓存雪崩

最后,如果本文对你有帮助,不妨👍,谢谢!