StackExchange.Redis连接字符串错误会导致线程阻塞5秒而非报错,需设connectTimeout/syncTimeout、用ConnectAsync()、全局单例ConnectionMultiplexer、正确判断RedisValue.IsNull、批量操作用MGET、复杂逻辑用Lua、Cluster下禁用SELECT。

StackExchange.Redis 连接字符串写错会直接卡死,不是报错
默认情况下,ConnectionMultiplexer.Connect() 遇到无法解析的地址、端口不通或 DNS 失败,会阻塞线程长达数秒(默认 5 秒),而不是立刻抛异常。这在 ASP.NET Core 启动或高并发场景下极易引发雪崩。
- 必须显式设置
connectTimeout和syncTimeout,例如:"localhost:6379,connectTimeout=2000,syncTimeout=1000" - 不要用
Connect(),改用异步ConnectAsync(),并在 await 后检查IsConnected - 连接对象是线程安全且应长期复用的——全局单例一个
ConnectionMultiplexer实例,别每次操作都 new - 若 Redis 服务不可达,
GetDatabase()仍会成功返回,但第一次调用StringGet()等命令时才真正触发连接并超时
string 类型读写最常用,但 Get/Set 默认不处理 null 值
IDatabase.StringGet() 返回 RedisValue,它不是 string,也不是可空引用类型;当 key 不存在时返回的是 RedisValue.Null,不是 null 或 default(string)。直接强制转 string 会得到空字符串,掩盖了“key 不存在”这个关键状态。
- 正确判断是否存在:用
if (value.IsNull),而不是if (value == null)或string.IsNullOrEmpty(value) - 存 null 字符串?不行。
StringSet("k", null)会静默失败(返回 false),必须传RedisValue.Null才能显式删 key - 需要序列化对象?别自己拼 JSON 后塞 string —— 用
HashGetAll()或封装好的JsonSerializer更稳妥,避免 UTF-8 字节 vs 字符串长度歧义
批量操作别手写 for 循环,用管道(Pipeline)或 Lua 脚本
连续调用 10 次 StringGet() 是 10 次网络往返;用 StringGet(key1), StringGet(key2)... 仍是串行。真正省 RTT 的方式只有两个:
- 用
db.StringGet(new RedisKey[] { k1, k2, k3 })批量拉取,底层走 Redis 的MGET命令 - 复杂逻辑(比如“查 A,如果存在则删 B,再设 C”)必须用
db.ScriptEvaluate()+ Lua,否则 CAS 场景下竞态不可避免 - 别在循环里反复调用
GetDatabase()——它开销极小,但反复获取不如提前存个局部变量 - 注意:批量命令中任一 key 不存在,不影响其他结果,但返回数组对应位置是
RedisValue.Null
ASP.NET Core 里注入 IDatabase 时,别直接注册为 Singleton
IDatabase 本身不是线程安全的“连接”,而是轻量级工厂类;真正该单例的是 ConnectionMultiplexer。如果你把 IDatabase 注册成 Singleton,它会在应用启动时就绑定到某个 database index(默认 0),后续所有请求都固定在这个库上,无法按需切换 db。
- 推荐做法:注册
ConnectionMultiplexer为 Singleton,然后在 Service 中通过构造函数注入它,在方法内调用_mux.GetDatabase(dbIndex) - 如果项目只用 db0,可以封装一个
IAppCache接口,内部持有IDatabase并隐藏细节 - 特别注意:Redis Cluster 模式下不支持
SELECT命令,GetDatabase(1)会直接抛异常,此时必须用 key hash tag(如{user1001}:profile)保证相关 key 落在同一节点
连接池、超时、null 判断、批量语义、依赖生命周期——这些点不写在文档首页,但每个都会让你在凌晨三点盯着日志发呆。











