缓存命中率99%依然能把数据库压垮,这事儿听起来像悖论
很多开发者习惯用 IMemoryCache 的 GetOrCreateAsync,觉得这个名字听起来很原子化,以为它能保证只有一个请求去查库。实际上这里面根本没有锁。在高并发场景下,如果一个热点 Key 过期,瞬间涌入 50 个请求,它们会同时发现缓存失效,然后全部触发数据库查询。结果就是:同一个查询在几百毫秒内被重复执行了 50 次。
针对这个问题,.NET 9 引入的 HybridCache 才是正解。它在内部实现了并发请求的去重(Deduping),同一个 Key 的并发请求只允许一个去跑工厂方法,其余的全部排队等待结果。
实操对比一下代码就明白了。
传统的 IMemoryCache 写法(危险):
app.MapGet("/products/memory/{id}", async (string id, IMemoryCache cache, FakeDb db) =>
await cache.GetOrCreateAsync($"mem:product:{id}", entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
return db.LoadProductAsync(id, flavor: "memory");
}));
升级到 HybridCache 后的写法(安全):
首先在服务注册时加入:
builder.Services.AddHybridCache();
然后修改 Endpoint:
app.MapGet("/products/hybrid/{id}", async (string id, HybridCache cache, FakeDb db, CancellationToken ct) =>
await cache.GetOrCreateAsync(
$"hyb:product:{id}",
async token => await db.LoadProductAsync(id, flavor: "hybrid"),
new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(5) },
cancellationToken: ct));
我写了个简单的 Demo 压测,模拟 50 个并发请求且缓存为空的情况,结果非常离谱:
- IMemoryCache: 产生了 50 次数据库调用。
- HybridCache: 仅产生 1 次数据库调用。
HybridCache 并不会缩短首个请求的响应时间(因为还是要查库),但它极大地降低了数据库的压力。在真实生产环境下,50 个重复查询会导致数据库响应变慢,进而拉长缓存失效的时间窗,吸引更多请求进来,形成恶性循环。
一个踩坑细节:HybridCache 的 L1 缓存底层其实还是 MemoryCache。如果你在同一个应用里混用两者,千万记得给 Key 加前缀区分,否则可能会因为 Key 冲突导致一些莫名其妙的 Bug。
免费 AI 工具箱 · 全部完全免费
同类方向的延伸案例可以参考AI大模型变现案例库,有不少直接可参考的案例。
HybridCache要是锁机制没处理好,99%的命中率照样被击穿到宕机