using StackExchange.Redis; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using WX.CRM.Model.Redis; namespace WX.CRM.DAL.Redis { public class RedisSettings { private static readonly Dictionary _settingList = null; readonly ConfigurationOptions configuration; readonly System.IO.TextWriter connectionMultiplexerLog; ConnectionMultiplexer connection; object connectionLock = new object(); public int Db { get; private set; } public ConfigurationOptions ConfigurationOptions { get { return configuration; } } public IRedisValueConverter ValueConverter { get; private set; } static RedisSettings() { _settingList = new Dictionary(); var section = RedisConfigurationSection.GetSection(); foreach (var redisSetting in section.RedisSettings) { _settingList.Add(redisSetting.Db, new RedisSettings(redisSetting.ConnectionString, redisSetting.Db)); WX.CRM.Common.LogHelper.Info("【RedisSettings实例】RedisSettings()._settingList.kyes:" + string.Join(",", _settingList.Keys.ToArray())); } } public static RedisSettings GetRedisSettings(RedisConfig config) { var conn = _settingList[Convert.ToInt32(config)]; if (conn == null) throw new Exception("配置错误"); return conn; } // events public Action OnConnectionOpen { private get; set; } public Action OnConnectionOpenFailed { private get; set; } public Action OnConnectAsyncFailed { private get; set; } public Action OnConfigurationChanged { private get; set; } public Action OnConfigurationChangedBroadcast { private get; set; } public Action OnConnectionFailed { private get; set; } public Action OnConnectionRestored { private get; set; } public Action OnErrorMessage { private get; set; } public Action OnHashSlotMoved { private get; set; } public Action OnInternalError { private get; set; } //public RedisSettings(string connectionString, int db = 0, IRedisValueConverter converter = null, Func tracerFactory = null, System.IO.TextWriter connectionMultiplexerLog = null) // : this(ConfigurationOptions.Parse(connectionString), db, converter, tracerFactory, connectionMultiplexerLog) //{ //} //public RedisSettings(ConfigurationOptions configuration, int db = 0, IRedisValueConverter converter = null, Func tracerFactory = null, System.IO.TextWriter connectionMultiplexerLog = null) //{ // this.configuration = configuration; // this.Db = db; // this.ValueConverter = converter ?? new JsonRedisValueConverter(); // this.CommandTracerFactory = tracerFactory; // this.connectionMultiplexerLog = connectionMultiplexerLog; //} private RedisSettings(string connectionString, int db = 0, System.IO.TextWriter connectionMultiplexerLog = null) : this(ConfigurationOptions.Parse(connectionString), db, connectionMultiplexerLog) { } private RedisSettings(ConfigurationOptions configuration, int db = 0, System.IO.TextWriter connectionMultiplexerLog = null) { this.configuration = configuration; this.Db = db; this.ValueConverter = new JsonRedisValueConverter(); this.connectionMultiplexerLog = connectionMultiplexerLog; } public ConnectionMultiplexer GetConnection() { if (connection == null || !connection.IsConnected) { lock (connectionLock) { if (connection != null && connection.IsConnected) return connection; if (connection != null) { connection.Close(false); connection.Dispose(); connection = null; } var tryCount = 0; var allowRetry = false; do { allowRetry = false; try { var sw = Stopwatch.StartNew(); var innerSw = Stopwatch.StartNew(); try { // Sometimes ConnectionMultiplexer.Connect is failed and issue does not solved https://github.com/StackExchange/StackExchange.Redis/issues/42 // I've created manualy Connect and control timeout. // I recommend set connectTimeout from 1000 to 5000. (configure your network latency) var tcs = new System.Threading.Tasks.TaskCompletionSource(); var connectThread = new Thread(_ => { #region try { var connTask = StackExchange.Redis.ConnectionMultiplexer.ConnectAsync(configuration, connectionMultiplexerLog) .ContinueWith(x => { innerSw.Stop(); if (x.IsCompleted) { try { if (!tcs.TrySetResult(x.Result)) { // already faulted x.Result.Close(false); x.Result.Dispose(); } } catch (Exception ex) { var ev = OnConnectAsyncFailed; if (ev != null) { ev(new OpenConnectionFailedEventArgs(configuration, innerSw.Elapsed, ex)); } tcs.TrySetException(ex); } } else if (x.IsFaulted) { var ev = OnConnectAsyncFailed; if (ev != null) { ev(new OpenConnectionFailedEventArgs(configuration, innerSw.Elapsed, x.Exception)); } } }); if (!connTask.Wait(this.configuration.ConnectTimeout)) { tcs.TrySetException(new TimeoutException("Redis Connect Timeout. Elapsed:" + sw.Elapsed.TotalMilliseconds + "ms")); } } catch (Exception ex) { tcs.TrySetException(ex); } #endregion }); connectThread.Start(); connection = tcs.Task.GetAwaiter().GetResult(); connection.IncludeDetailInExceptions = true; sw.Stop(); } catch (Exception ex) { #region sw.Stop(); try { var ev = OnConnectionOpenFailed; if (ev != null) { ev(new OpenConnectionFailedEventArgs(configuration, sw.Elapsed, ex)); } throw; } finally { connection = null; } #endregion } #region events try { var openEv = OnConnectionOpen; if (openEv != null) { openEv(new OpenConnectionEventArgs(configuration, sw.Elapsed)); } // attach events connection.ConfigurationChanged += connection_ConfigurationChanged; connection.ConfigurationChangedBroadcast += connection_ConfigurationChangedBroadcast; connection.ConnectionFailed += connection_ConnectionFailed; connection.ConnectionRestored += connection_ConnectionRestored; connection.ErrorMessage += connection_ErrorMessage; connection.HashSlotMoved += connection_HashSlotMoved; connection.InternalError += connection_InternalError; } catch { connection = null; throw; } #endregion } catch (TimeoutException) { if (tryCount < configuration.ConnectRetry) { tryCount++; allowRetry = true; } } } while (connection == null && allowRetry); } } return connection; } void connection_InternalError(object sender, InternalErrorEventArgs e) { var ev = OnInternalError; if (ev != null) ev(sender as ConnectionMultiplexer, e); } void connection_HashSlotMoved(object sender, HashSlotMovedEventArgs e) { var ev = OnHashSlotMoved; if (ev != null) ev(sender as ConnectionMultiplexer, e); } void connection_ErrorMessage(object sender, RedisErrorEventArgs e) { var ev = OnErrorMessage; if (ev != null) ev(sender as ConnectionMultiplexer, e); } void connection_ConnectionRestored(object sender, ConnectionFailedEventArgs e) { var ev = OnConnectionRestored; if (ev != null) ev(sender as ConnectionMultiplexer, e); } void connection_ConnectionFailed(object sender, ConnectionFailedEventArgs e) { var ev = OnConnectionFailed; if (ev != null) ev(sender as ConnectionMultiplexer, e); } void connection_ConfigurationChangedBroadcast(object sender, EndPointEventArgs e) { var ev = OnConfigurationChangedBroadcast; if (ev != null) ev(sender as ConnectionMultiplexer, e); } void connection_ConfigurationChanged(object sender, EndPointEventArgs e) { var ev = OnConfigurationChanged; if (ev != null) ev(sender as ConnectionMultiplexer, e); } public class OpenConnectionEventArgs : EventArgs { public ConfigurationOptions ConfigurationOption { get; private set; } public TimeSpan Duration { get; private set; } public OpenConnectionEventArgs(ConfigurationOptions configurationOptions, TimeSpan duration) { this.ConfigurationOption = configurationOptions; this.Duration = duration; } } public class OpenConnectionFailedEventArgs : EventArgs { public ConfigurationOptions ConfigurationOption { get; private set; } public TimeSpan Duration { get; private set; } public Exception Exception { get; private set; } public OpenConnectionFailedEventArgs(ConfigurationOptions configurationOption, TimeSpan duration, Exception exception) { this.ConfigurationOption = configurationOption; this.Duration = duration; this.Exception = exception; } } } }