c# 在高并发网站中,如何设计缓存策略和缓存失效机制

来源:这里教程网 时间:2026-02-21 17:42:33 作者:

缓存该用
MemoryCache
还是
IDistributedCache

单机部署时用

MemoryCache
足够快,但高并发网站通常多实例部署,各节点内存不共享。此时若只依赖
MemoryCache
,同一数据在不同节点可能缓存不同值,甚至一个节点更新了缓存,其他节点仍读旧值——这就是典型的缓存不一致问题。

必须用

IDistributedCache
作为主缓存层(如 Redis 或 SQL Server 后端),
MemoryCache
只能作为二级本地缓存(即“缓存穿透防护 + 热点加速”用途)。

Redis 实现的
IDistributedCache
是首选:支持原子操作、过期策略、发布/订阅失效通知
避免用 SQL Server 做分布式缓存后端——写入延迟高、连接压力大、不支持批量删除 本地
MemoryCache
缓存时间建议 ≤ 10 秒,且仅用于高频读、低更新频率的热点数据(如配置项、地区字典)

缓存键设计要防冲突、可预测、带业务上下文

缓存键不是随便拼个字符串就行。常见错误是直接用

"/api/user/" + userId
当键,结果不同环境(测试/生产)、不同租户、不同 API 版本全挤在一个键空间里,导致误击或污染。

推荐格式:

"{env}:{tenant}:{version}:{resource}:{id}"
,例如
"prod:acme:v2:user:12345"

必须包含环境标识(
env
),避免测试缓存污染生产
多租户系统必须含
tenant
,否则 A 公司数据可能被 B 公司接口读到
API 版本变更时,键自动隔离,无需手动清理旧版本缓存 禁止在键中拼接未转义的用户输入(如用户名),防止注入或键超长;用
Convert.ToBase64String(Encoding.UTF8.GetBytes(...))
安全编码

失效机制不能只靠自然过期,得有主动踢出 + 失效广播

纯依赖

absoluteExpiration
slidingExpiration
极其危险:数据已更新,但缓存还剩 30 秒才过期,这期间所有请求都读脏数据。

必须组合三种方式:

写时主动删除:更新 DB 后立刻调用
_cache.RemoveAsync("key")
(注意:不是
SetAsync
覆盖,删除更安全)
关键路径加失效广播:用 Redis Pub/Sub 或自建消息队列,当订单状态更新时发消息
"order:status:updated:12345"
,所有节点监听并删对应缓存
过期时间设为“上限”而非“默认”:比如商品价格缓存设 5 分钟过期,但每次更新都主动删;过期只是兜底,不是主力机制

特别注意:不要在事务内调用缓存删除——如果事务回滚,缓存却已删,会导致短暂不一致。应在事务成功提交后再删缓存(可用

Transaction.CommitAsync
后挂回调,或投递到可靠消息队列)。

应对缓存穿透、雪崩、击穿的实操要点

高并发下这三个问题不是理论风险,是真实会炸服务的故障点。

穿透:查不存在的
userId = -1
或随机 ID,缓存没命中,DB 每次都扛压。解决:对空结果也缓存(
SetAsync("user:-1", null, new TimeSpan(0, 0, 30))
),但需加前缀区分(如
"empty:user:-1"
),避免和正常 null 冲突
击穿:热点 key(如首页 banner)过期瞬间大量请求打穿缓存。解决:用
GetOrCreateAsync
+
lock
或 Redis 的
SETNX
保证只有一个线程回源加载,其余等待
雪崩:大量 key 设相同过期时间(如整点刷新),到期后集体失效。解决:过期时间加随机偏移(
TimeSpan.FromMinutes(5 + new Random().NextDouble() * 2)
),且关键数据改用永不过期 + 主动更新

最易被忽略的是:缓存客户端本身也是资源瓶颈。别让

IDistributedCache
的连接池耗尽——Redis 连接字符串必须配
connectTimeout=5000
syncTimeout=2000
abortConnect=false
,并启用连接池复用。

相关推荐