现场采集端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 }

 

posted @ 2025-12-02 17:59  上清风  阅读(5)  评论(0)    收藏  举报