Redis缓存写入失败不报错需检查异步返回值和连接状态:必须await StringSet等方法,判断返回值及IsConnected;空值缓存防穿透;并发用StringIncrement而非read-modify-write;高频写绕过IDistributedCache直连。
Redis缓存写入失败但没报错?检查 StackExchange.Redis 的异步返回值和连接状态
很多 c# 项目用 stackexchange.redis 写缓存时,调用 stringset 后数据没进 redis,也不抛异常——其实是异步任务被静默丢弃了。这个库默认所有写操作都返回 task,但如果你直接调用不 await、也不检查返回值,失败就真“石沉大海”。
- 必须
await或至少.Wait()(同步场景)StringSet等方法,否则可能根本没发出去 - 连接断开时
StringSet可能返回false而非抛异常,得主动判断返回值 - 确认
ConnectionMultiplexer.IsConnected为true再写,否则会走内部重连逻辑,延迟高且行为不可控 - 别在
using块里创建新ConnectionMultiplexer实例——它本该是单例,频繁新建会导致 socket 耗尽
缓存穿透导致 DB 崩了?用 StringSet + 过期时间兜底空值
用户查一个永远不存在的 ID(比如 /user/999999999),每次请求都击穿缓存直查 DB,QPS 上去就挂。这不是 Redis 配置问题,是业务层没做空值缓存。
- DB 查不到结果时,仍要调用
StringSet,但 value 设为特殊标记(如"null"),并设较短过期时间(比如 2 分钟) - 避免用
null本身作 value——IDatabase.StringGet返回RedisValue.Null,容易和网络超时、连接失败混淆 - 别给空值设太长 TTL,否则脏数据滞留;也别设太短(如 1 秒),起不到抗并发穿透作用
- 如果用
MemoryCache做本地二级缓存,注意它不支持空值过期,得自己封装逻辑
并发写同个 key 导致数据错乱?别依赖 StringSet 的原子性来覆盖
StringSet 单次调用确实是原子的,但“先读再算再写”的模式(read-modify-write)不是。比如计数器自增、用户积分累加,多个请求同时读到旧值,各自加 1 再写回去,最后只 +1 而非 +N。
- 计数类场景改用
Database.StringIncrement,它由 Redis 服务端保证原子性 - 复杂对象更新(如用户信息 JSON)不要先
StringGet解析、改字段、再StringSet—— 改用Hash结构,用HashSet更新单个字段 - 真需要条件写入时,用
StringSet的When.NotExists或 Lua 脚本,但脚本要严格测试超时和错误返回路径 - 注意
StringSet默认不启用Flags参数,When和Expire必须显式传入,漏写就退化成普通覆盖
.NET 6+ 下 IDistributedCache 封装太慢?绕过它直用 IConnectionMultiplexer
用 Microsoft.Extensions.Caching.StackExchangeRedis 注册的 IDistributedCache,每次 SetAsync 都多一层序列化 + key 拼接 + 异常包装,实测比直连慢 15%~30%,尤其小对象高频写。
- 高频写场景(如埋点、实时统计)建议跳过
IDistributedCache,DI 注入IConnectionMultiplexer后用GetDatabase()直取 - 自己处理序列化:小对象用
System.Text.Json(别用Newtonsoft.Json),大对象考虑MessagePack - key 命名别拼字符串,用
$"user:{id}"即可,但注意冒号不是必须,纯字母数字更利于 Redis cluster 分片 - 别在
IDistributedCache和直连之间混用同一组 key——它们的序列化格式、前缀规则可能不同,读写错乱
最麻烦的从来不是“怎么写进 Redis”,而是“怎么确保写对了、写及时了、写安全了”。连接生命周期、空值策略、并发语义、抽象层开销——每个点漏掉一环,线上就少一道保险。











