贝利信息

c# StackExchange.Redis 的连接管理和并发策略

日期:2026-01-24 00:00 / 作者:星降
ConnectionMultiplexer 必须全局单例复用,严禁每次新建或Scoped注入;IDatabase可随时获取无需缓存;异步操作必须await,禁用.Result/.Wait();读多写少时可用CommandFlags.DemandSlave分流至从节点。

ConnectionMultiplexer 是单例,不是每次 new

StackExchange.Redis 的核心是 ConnectionMultiplexer,它本身线程安全、内置连接池和自动重连,**必须全局复用一个实例**。多次 new ConnectionMultiplexer 会导致 socket 耗尽、TIME_WAIT 暴涨、CPU 升高,甚至 Redis 服务端拒绝新连接。

常见错误是把 ConnectionMultiplexer 塞进 using 或注入为 Scoped 服务——这等于每请求建一次连接,完全违背设计初衷。

Database 实例可随意获取,无需缓存

IDatabase 是轻量级句柄,不持有连接,每次调用 connection.GetDatabase() 都是 O(1) 开销。没必要把它存成字段或缓存起来——反而容易在多库(如 db0/db1)场景下搞混上下文。

注意:如果用了 defaultDatabase 参数(比如 GetDatabase(1)),它只影响后续命令的默认 db 编号,不会改变底层 multiplexer 行为。

异步 API 必须用 async/await,别用 .Result 或 .Wait()

StackExchange.Redis 的所有 *Async 方法(如 StringGetAsyncHashSetAsync)底层基于 ValueTask,**同步等待(.Result / .Wait())极易引发死锁**,尤其在 ASP.NET Core 同步上下文受限环境里。

典型现象:接口偶尔卡住、线程池饥饿、请求堆积。这不是 Redis 慢,是线程被自己锁死了。

高并发*意 CommandFlags 和连接负载均衡

默认情况下,所有命令都走主节点(即使你配了哨兵或集群)。遇到读多写少场景,可以利用 CommandFlags 把只读命令发到从节点,减轻主节点压力:

await db.StringGetAsync("key", CommandFlags.DemandSlave);

但这有前提:Redis 配置了 slave-read-only yes,且 multiplexer 连接字符串里启用了 allowAdmin=true(仅开发/测试环境建议开启,生产慎用)。

真正难处理的,是跨多个 Redis 实例做分布式锁或事务协调时,ConnectionMultiplexer 的健康状态感知和故障转移延迟——它不会主动通知你“这个节点已永久失联”,得靠你结合 IsConnectedGetStatus() 和业务超时做兜底。