idistributedcache 是官方分布式缓存抽象接口,非具体实现,需通过 di 注册 redis/sql server/内存等提供者;其 getstring 方法用指定键从缓存获取字符串值。

直接说结论:IDistributedCache 不是“拿来存个字符串就完事”的工具,它不自动序列化、不支持滑动过期、不处理空值兜底、也不解决击穿雪崩——所有这些都得你代码里显式控制,否则上线后就是数据库告警连响。
为什么 GetStringAsync 总是返回 null?
这不是 Redis 没连上,大概率是序列化/反序列化断在了中间环节。
-
IDistributedCache.SetStringAsync只接受string,底层调的是UTF8Encoding.GetBytes();一旦原始字符串含非法 UTF-8 字节(比如某些二进制序列化结果、损坏的 JSON),GetStringAsync解码失败就会静默返回null - 别用
JsonSerializer.Serialize(new { })直接传给SetStringAsync—— 缺少JsonSerializerOptions配置时,record、循环引用、私有 setter 都可能让输出变成无效 JSON - 开发阶段建议加一层封装:统一用
JsonSerializer.SerializeToUtf8Bytes(obj)存,再用JsonSerializer.Deserialize<t>(bytes)</t>取,绕过字符串编解码歧义
SetStringAsync 设了 SlidingExpiration 却没效果?
不是你代码写错了,是 StackExchange.Redis 提供者根本无视这个参数。
-
DistributedCacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(5))在 Redis 实现里被静默忽略,只有AbsoluteExpirationRelativeToNow或AbsoluteExpiration真正生效 - 想实现滑动效果,必须每次读取后手动调
RefreshAsync(key),且要确保 key 当前真实存在于 Redis 中(否则会抛RedisConnectionException) - 更稳妥的做法是:用绝对过期 + 业务层定时刷新逻辑,或直接改用 StackExchange.Redis 原生命令
db.StringSet(key, value, expiry, When.Always, CommandFlags.DemandMaster)
并发查 DB 导致缓存击穿?别手写 lock
在分布式环境下,lock 只锁当前进程,对其他实例完全无效,反而拖慢线程池。
- 用 Redis 原生命令实现原子占位:
db.StringSet(key, "placeholder", TimeSpan.FromSeconds(10), When.NotExists),成功才去查 DB 并写入最终值 - 占位失败时,不要忙等,可设短时退避(如
await Task.Delay(50))后重试最多 3 次,超时则 fallback 到 DB - 务必为占位 key 设置极短 TTL(如 10 秒),避免死锁;同时对真实数据 key 设置合理绝对过期时间(如 15 分钟)
IDistributedCache 连不上 Redis 就崩?
默认配置下,StackExchange.Redis 空闲 60 秒就断开连接,而 IDistributedCache 实现没做重连兜底,首次请求失败就直接抛异常。
- 注册时必须显式配置:
options.ConfigurationOptions.AbortOnConnectFail = false,否则首次连接失败直接炸在ConfigureServices - 加上
ConnectRetry = 3和ConnectTimeout = 10000(生产环境建议 ≥10s),避免网络抖动误判 - 最关键的是:别让
IDistributedCache成为单点瓶颈——高并发下它复用同一个ConnectionMultiplexer,建议封装一层带 fallback 的调用,比如 Redis 失败时自动降级到IMemoryCache或直连 DB
最常被忽略的一点:IDistributedCache 的 key 是全局共享的,"user_123" 这种裸 key 在灰度发布、多租户、AB 测试场景下必然混库。必须把环境标识、租户 ID、版本号编码进 key,比如 "prod_tenantA_user_123_v2",否则缓存污染比没缓存还难排查。











