现场采集端Redis连接设计走读(ML)
今天走读过站采集程序框架中的RedisConnectionHelp.cs代码。
在.NET Winfrom客户端,StackExchange.Redis 提供了使用C#对Redis的一系列操作,这在源码文件顶部的引入:using StackExchange.Redis; 可以看出。
在采集端程序,可以看到设计了一个线程安全的懒汉式单例入口,确保应用里只有一个可用的 Redis 连接,并在断线时自动重新建立。代码如下:
1 private static readonly object Locker = new object(); 2 private static ConnectionMultiplexer _instance; 3 public static ConnectionMultiplexer Instance 4 { 5 get 6 { 7 if (_instance == null) 8 { 9 lock (Locker) 10 { 11 if (_instance == null || !_instance.IsConnected) 12 { 13 _instance = GetManager(); 14 } 15 } 16 } 17 return _instance; 18 } 19 }
目的:全局只维护一个可用的 Redis 连接对象,减少连接创建开销,并在多线程场景下避免重复创建。
Locker 是锁对象,用于在并发初始化时串行化,防止多个线程同时创建连接;
_instance 保存全局唯一的连接实例引用;
Instance 是只读静态属性,采用“双重检查锁”(Double-Checked Locking)实现惰性加载和高效并发:
- 外层检查 _instance == null ,避免每次访问都加锁,
- 进入 lock (Locker) 后再次检查,如果为 null 或当前连接不可用( !_instance.IsConnected ),则创建/重建连接,
- 始终返回当前可用的实例。
连接的实际创建在 GetManager(...) 中完成,并注册了一系列连接事件(失败、恢复、错误、配置变更等)
为什么这样写
- ConnectionMultiplexer 的创建成本较高,官方建议复用一个实例而不是频繁创建;单例能显著减少资源占用。
- 双重检查锁只在首次或断线重建时加锁,常规读取路径无需锁,性能更好。
- !_instance.IsConnected 这一步能在发现连接已断开时触发重建,提高连接可用性。
核心概念
- 懒汉式单例指“按需创建”的单例:只有第一次被访问时才创建实例,而不是在程序启动时就创建。
- “入口”就是统一的获取通道(这里是静态属性 Instance ),所有代码都通过它拿到同一个实例。
为什么用懒汉式
- 降低启动成本:首次真正需要 Redis 时才建立连接,避免无效开销。
- 高并发友好:双重检查锁把锁的成本限制在“首次/重建”阶段,普通读路径不加锁。
- 可用性增强:多了 !_instance.IsConnected 检查,连接断开时会自动重建。
与饿汉式对比
- 饿汉式:类加载时立刻创建实例,访问时不再判断;优点是简单,缺点是可能浪费资源。
- 懒汉式:首次访问才创建;优点是节省资源、启动更快,缺点是需要正确的并发控制(这里通过双重检查锁实现)
如果用饿汉式改写呢?
方案1:
1 private static readonly object Locker = new object(); 2 private static ConnectionMultiplexer _instance = GetManager(); 3 4 public static ConnectionMultiplexer Instance 5 { 6 get 7 { 8 if (!_instance.IsConnected) 9 { 10 lock (Locker) 11 { 12 if (!_instance.IsConnected) 13 { 14 _instance = GetManager(); 15 } 16 } 17 } 18 return _instance; 19 } 20 }
方案2:
1 private static readonly object Locker = new object(); 2 private static ConnectionMultiplexer _instance; 3 4 static RedisConnectionHelp() 5 { 6 _instance = GetManager(); 7 } 8 9 public static ConnectionMultiplexer Instance 10 { 11 get 12 { 13 if (!_instance.IsConnected) 14 { 15 lock (Locker) 16 { 17 if (!_instance.IsConnected) 18 { 19 _instance = GetManager(); 20 } 21 } 22 } 23 return _instance; 24 } 25 }
GetManager()方法解读
private static ConnectionMultiplexer GetManager(string connectionString = null) { connectionString = connectionString ?? RedisConnectionString; var config = new ConfigurationOptions { AbortOnConnectFail = false, AllowAdmin = true, ConnectTimeout = 15000, SyncTimeout = 5000, ResponseTimeout = 15000, Password = "MLMes2019",//Redis数据库密码 EndPoints = { connectionString } }; var connect = ConnectionMultiplexer.Connect(config); //注册如下事件:失败、恢复、错误、配置变更、槽位迁移、内部错误 connect.ConnectionFailed += MuxerConnectionFailed; connect.ConnectionRestored += MuxerConnectionRestored; connect.ErrorMessage += MuxerErrorMessage; connect.ConfigurationChanged += MuxerConfigurationChanged; connect.HashSlotMoved += MuxerHashSlotMoved; connect.InternalError += MuxerInternalError; return connect; }
返回的是ConnectionMultiplexer类型,Instance类型就是这个类型。使用这个类型也是为了能够使用 StackExchange.Redis提供的一系列用法。
代码解读
- 这个方法负责“创建一个可用的 Redis 连接并完成必要的事件注册”,返回一个 ConnectionMultiplexer ,
- 先构建一个 ConfigurationOptions (强类型配置对象),再用 ConnectionMultiplexer.Connect(config) 建立连接。
- 建好后,绑定一组连接事件(失败、恢复、错误、配置变更、槽位迁移、内部错误),用于监控与自愈。
为什么用 ConfigurationOptions
- 它是 StackExchange.Redis 客户端提供的强类型配置方式,等价于字符串连接串,但更清晰、可读、易维护。
- 支持细粒度选项: AbortOnConnectFail 、 AllowAdmin 、 ConnectTimeout 、 SyncTimeout 、 ResponseTimeout 、 Password 、 EndPoints 等,
- 也可以直接用连接串字符串,例如: ConnectionMultiplexer.Connect("127.0.0.1:6379,password=xxx,allowAdmin=true,abortConnect=false") ;两者只是配置入口不同。
主要配置项含义
- AbortOnConnectFail=false :首连失败不抛致命错误,后台持续重试,适合容错场景。
- AllowAdmin=true :允许执行管理类命令(如 FLUSHDB ),生产要慎用。
- ConnectTimeout=15000 :连接建立的最大等待毫秒数。
- SyncTimeout=5000 :同步操作的最大等待时间;超时会抛异常。
- ResponseTimeout=15000 :响应等待上限
- Password="XXXX" :认证密码,建议改为配置或安全存储而不是硬编码。
- EndPoints={connectionString} :可以是单个或多个地址,支持集群/哨兵等多节点。
事件绑定的意义
- ConnectionFailed :连接失败时触发,便于日志/报警,
- ConnectionRestored :连接恢复时触发,
- ErrorMessage :收到服务端错误消息时触发,
- ConfigurationChanged :配置变化(如拓扑变更)时触发,
- HashSlotMoved :集群槽位迁移(重平衡)时触发,
- InternalError :客户端库内部错误,
- 这些事件都是 StackExchange.Redis 提供的“可选”能力,不是必须,但用于观测与运维非常有价值。
概念认知问题:到底是谁与谁连接?
- .NET 进程中的客户端对象 ConnectionMultiplexer ,与远端 Redis 服务器进行 TCP 连接。
- 服务器地址来自 EndPoints (通常是 host:port ),比如 127.0.0.1:6379 ,
- 建连调用是 ConnectionMultiplexer.Connect(config)
通俗点的话来说,就是:
客户端是当前运行的 .NET 程序(用 StackExchange.Redis 库开发的过站采集程序),服务端是运行中的 Redis 服务器进程( redis-server ,本机或远端)。所谓“连接”,就是当前的过站采集程序与 Redis 服务器之间建立的网络连接(TCP)。
整体源码
1 using StackExchange.Redis; 2 using System; 3 using System.Collections.Concurrent; 4 using System.Configuration; 5 6 namespace RedisHelp 7 { 8 /// <summary> 9 /// ConnectionMultiplexer对象管理帮助类 10 /// </summary> 11 public static class RedisConnectionHelp 12 { 13 //系统自定义Key前缀 14 public static readonly string SysCustomKey = ConfigurationManager.AppSettings["redisKey"] ?? ""; 15 16 private static readonly string RedisConnectionString = ConfigurationManager.AppSettings["RedisExchangeHosts"] ?? "127.0.0.1:6379"; 17 private static readonly object Locker = new object(); 18 private static ConnectionMultiplexer _instance; 19 private static readonly ConcurrentDictionary<string, ConnectionMultiplexer> ConnectionCache = new ConcurrentDictionary<string, ConnectionMultiplexer>(); 20 21 /// <summary> 22 /// 单例获取 23 /// </summary> 24 public static ConnectionMultiplexer Instance 25 { 26 get 27 { 28 if (_instance == null) 29 { 30 lock (Locker) 31 { 32 if (_instance == null || !_instance.IsConnected) 33 { 34 _instance = GetManager(); 35 } 36 } 37 } 38 return _instance; 39 } 40 } 41 42 /// <summary> 43 /// 缓存获取 44 /// </summary> 45 /// <param name="connectionString"></param> 46 /// <returns></returns> 47 public static ConnectionMultiplexer GetConnectionMultiplexer(string connectionString) 48 { 49 if (!ConnectionCache.ContainsKey(connectionString)) 50 { 51 ConnectionCache[connectionString] = GetManager(connectionString); 52 } 53 return ConnectionCache[connectionString]; 54 } 55 56 //创建redis连接 57 private static ConnectionMultiplexer GetManager(string connectionString = null) 58 { 59 connectionString = connectionString ?? RedisConnectionString; 60 61 var config = new ConfigurationOptions 62 { 63 AbortOnConnectFail = false, 64 AllowAdmin = true, 65 ConnectTimeout = 15000, 66 SyncTimeout = 5000, 67 ResponseTimeout = 15000, 68 Password = "MLMes2019",//Redis数据库密码 69 EndPoints = { connectionString } 70 }; 71 72 73 var connect = ConnectionMultiplexer.Connect(config); 74 75 //注册如下事件:失败、恢复、错误、配置变更、槽位迁移、内部错误 76 connect.ConnectionFailed += MuxerConnectionFailed; 77 connect.ConnectionRestored += MuxerConnectionRestored; 78 connect.ErrorMessage += MuxerErrorMessage; 79 connect.ConfigurationChanged += MuxerConfigurationChanged; 80 connect.HashSlotMoved += MuxerHashSlotMoved; 81 connect.InternalError += MuxerInternalError; 82 83 return connect; 84 } 85 86 #region 事件 87 88 /// <summary> 89 /// 配置更改时 90 /// </summary> 91 /// <param name="sender"></param> 92 /// <param name="e"></param> 93 private static void MuxerConfigurationChanged(object sender, EndPointEventArgs e) 94 { 95 Console.WriteLine("Configuration changed: " + e.EndPoint); 96 } 97 98 /// <summary> 99 /// 发生错误时 100 /// </summary> 101 /// <param name="sender"></param> 102 /// <param name="e"></param> 103 private static void MuxerErrorMessage(object sender, RedisErrorEventArgs e) 104 { 105 Console.WriteLine("ErrorMessage: " + e.Message); 106 } 107 108 /// <summary> 109 /// 重新建立连接之前的错误 110 /// </summary> 111 /// <param name="sender"></param> 112 /// <param name="e"></param> 113 private static void MuxerConnectionRestored(object sender, ConnectionFailedEventArgs e) 114 { 115 Console.WriteLine("ConnectionRestored: " + e.EndPoint); 116 } 117 118 /// <summary> 119 /// 连接失败 , 如果重新连接成功你将不会收到这个通知 120 /// </summary> 121 /// <param name="sender"></param> 122 /// <param name="e"></param> 123 private static void MuxerConnectionFailed(object sender, ConnectionFailedEventArgs e) 124 { 125 Console.WriteLine("重新连接:Endpoint failed: " + e.EndPoint + ", " + e.FailureType + (e.Exception == null ? "" : (", " + e.Exception.Message))); 126 } 127 128 /// <summary> 129 /// 更改集群 130 /// </summary> 131 /// <param name="sender"></param> 132 /// <param name="e"></param> 133 private static void MuxerHashSlotMoved(object sender, HashSlotMovedEventArgs e) 134 { 135 Console.WriteLine("HashSlotMoved:NewEndPoint" + e.NewEndPoint + ", OldEndPoint" + e.OldEndPoint); 136 } 137 138 /// <summary> 139 /// redis类库错误 140 /// </summary> 141 /// <param name="sender"></param> 142 /// <param name="e"></param> 143 private static void MuxerInternalError(object sender, InternalErrorEventArgs e) 144 { 145 Console.WriteLine("InternalError:Message" + e.Exception.Message); 146 } 147 148 #endregion 事件 149 } 150 }

浙公网安备 33010602011771号