StackExchange.Redis 生产使用需单例复用 ConnectionMultiplexer 并延迟初始化,禁用 Scoped 注册;优先用 StringGet() 而非 GetStringAsync() 以准确判空;密码/SSL/集群配置须用小写参数格式;IDatabase 可频繁获取,事务需同实例;订阅须用 GetSubscriber()。

StackExchange.Redis 是 C# 操作 Redis 的事实标准客户端,但“能连上”和“连得稳、用得对”是两回事。生产环境里最常见的不是连不上,而是连接泄漏、超时卡死、语义误判、序列化乱码——这些问题几乎都源于初始化方式和 API 选用不当。
ConnectionMultiplexer 必须单例复用,不能每次 new
每次调用 ConnectionMultiplexer.Connect() 都会新建 socket 连接池,短时间高频 new 会导致 SocketException: Too many open files 或 TimeoutException。它本身线程安全,设计就是供全局复用的。
正确做法是用 Lazy<connectionmultiplexer></connectionmultiplexer> 延迟初始化:
private static readonly Lazy<connectionmultiplexer> LazyConnection = new Lazy<connectionmultiplexer>(() =>
{
var config = "localhost:6379,connectTimeout=5000,syncTimeout=5000,abortConnect=false,keepAlive=30";
return ConnectionMultiplexer.Connect(config);
});
public static ConnectionMultiplexer Connection => LazyConnection.Value;</connectionmultiplexer></connectionmultiplexer>
-
abortConnect=false关键:Redis 启动慢或网络抖动时,不会让应用启动失败,而是后台自动重连 -
keepAlive=30防 NAT 超时断连,尤其在云环境或容器中必须加 - 别在 ASP.NET Core DI 里注册为
Scoped,生命周期太短,容易引发连接堆积
StringGet() 和 GetStringAsync() 别混用,优先选 StringGet()
GetStringAsync() 是扩展方法,内部调 StringGet() 再强转 .ToString()。看似省事,但掩盖了两个关键语义:
- 键不存在时,
StringGet()返回RedisValue.Null,而GetStringAsync()返回null string—— 无法区分“空值写入”和“键根本没设过” - 如果存的是 Protobuf、Image byte[] 等二进制数据,
GetStringAsync()会抛ArgumentException或静默乱码 - 性能无差别,但可维护性差:统一用
StringGet()+ 显式判断result.IsNull更可靠
密码、SSL、集群配置写法有硬约束,不能拼 URL
Redis 密码不能写成 redis://:pwd@localhost:6379,StackExchange.Redis 会把 : 当作端口分隔符解析失败,报 Invalid URI: Invalid port specified。
正确写法是参数形式(逗号分隔):
- 带密码:
"localhost:6379,password=yourpass,abortConnect=false" - 启用了 SSL/TLS:
"localhost:6380,ssl=true,allowAdmin=true"(注意端口通常变 6380) - Redis Cluster:
"node1:6379,node2:6379,node3:6379,allowAdmin=true",不用写cluster=true,客户端自动识别
所有配置项必须小写,大小写敏感;多个节点之间用英文逗号,不能有空格。
IDatabase 不是连接对象,GetDatabase() 可频繁调用
IDatabase db = Connection.GetDatabase(); 这行代码开销极小,它只是返回一个轻量代理,不建新连接。你可以在每个业务方法里放心调用,不需要缓存 IDatabase 实例。
但要注意:
-
GetDatabase(1)指定数据库编号,Redis 默认有 16 个 DB(0–15),但集群模式下不支持多 DB,会忽略编号 - 事务操作(
db.CreateTransaction())必须在同一个IDatabase实例上调用,跨实例会报错 - 订阅消息要用
Connection.GetSubscriber(),不能用IDatabase,这是完全独立的通道
真正难的不是第一次 set/get 成功,而是当 Redis 临时不可用、网络分区、密码改错、序列化器升级时,你的缓存逻辑是否静默降级、日志是否可追溯、连接是否自动恢复。这些细节藏在配置参数和 API 语义里,而不是示例代码的缩进里。











