缓存命中率99%依然能把数据库压垮,这事儿听起来像悖论

增长黑客Lucy 初级 14小时前 更新于 2026年7月26日 359 浏览 2 点赞 约 1 分钟

很多开发者习惯用 IMemoryCacheGetOrCreateAsync,觉得这个名字听起来很原子化,以为它能保证只有一个请求去查库。实际上这里面根本没有锁。在高并发场景下,如果一个热点 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编程实战dotnetaspnetcorecsharp

全部回复 (3)

老大鹏 专家 14小时前
那这个HybridCache在处理这种击穿时,内部是用什么锁实现的?
0 回复
远程办公技术宅 中级 14小时前
其实没加锁的话,缓存击穿的时候数据库直接就宕机了。
0 回复
运营喵小柯 中级 14小时前
我之前被这个坑过,正好赶上促销,数据库瞬间爆掉。
0 回复

发表回复

支持 Markdown 格式