88using System ;
99using System . Collections . Generic ;
1010using System . Linq ;
11+ using System . Threading ;
1112using ServiceStack ;
1213using ServiceStack . Logging ;
1314
@@ -22,15 +23,18 @@ public class RedisSentinel : IRedisSentinel
2223 public static string DefaultMasterName = "mymaster" ;
2324 public static string DefaultAddress = "127.0.0.1:26379" ;
2425
26+ private object oLock = new object ( ) ;
27+
2528 private readonly string masterName ;
2629 public string MasterName
2730 {
2831 get { return masterName ; }
2932 }
3033
3134 private int failures = 0 ;
35+ private int totalFailures = 0 ;
3236 private int sentinelIndex = - 1 ;
33- private List < string > sentinels ;
37+ internal RedisEndpoint [ ] SentinelHosts { get ; private set ; }
3438 private RedisSentinelWorker worker ;
3539 private static int MaxFailures = 5 ;
3640
@@ -39,33 +43,51 @@ public string MasterName
3943 public Action < Exception > OnWorkerError { get ; set ; }
4044 public Action < string , string > OnSentinelMessageReceived { get ; set ; }
4145
42- public Dictionary < string , string > IpAddressMap { get ; set ; }
46+ public Dictionary < string , string > IpAddressMap { get ; set ; }
47+
48+ public TimeSpan WaitBetweenSentinelLookups { get ; set ; }
49+ public TimeSpan MaxWaitBetweenSentinelLookups { get ; set ; }
50+ public TimeSpan SentinelWorkerTimeout { get ; set ; }
51+
52+ public bool ResetWhenSubjectivelyDown { get ; set ; }
53+ public bool ResetWhenObjectivelyDown { get ; set ; }
4354
4455 public RedisSentinel ( string sentinelHost = null , string masterName = null )
4556 : this ( new [ ] { sentinelHost ?? DefaultAddress } , masterName ?? DefaultMasterName ) { }
4657
4758 public RedisSentinel ( IEnumerable < string > sentinelHosts , string masterName = null )
4859 {
49- this . sentinels = sentinelHosts != null ? sentinelHosts . ToList ( ) : null ;
50- if ( sentinelHosts == null || sentinels . Count == 0 )
60+ this . SentinelHosts = sentinelHosts != null
61+ ? sentinelHosts . Map ( x => x . ToRedisEndpoint ( defaultPort : RedisNativeClient . DefaultPortSentinel ) ) . ToArray ( )
62+ : null ;
63+
64+ if ( SentinelHosts == null || SentinelHosts . Length == 0 )
5165 throw new ArgumentException ( "sentinels must have at least one entry" ) ;
5266
5367 this . masterName = masterName ?? DefaultMasterName ;
5468 IpAddressMap = new Dictionary < string , string > ( ) ;
5569 RedisManagerFactory = ( masters , slaves ) => new PooledRedisClientManager ( masters , slaves ) ;
70+ ResetWhenObjectivelyDown = true ;
71+ ResetWhenSubjectivelyDown = true ;
72+ SentinelWorkerTimeout = TimeSpan . FromMilliseconds ( 100 ) ;
73+ WaitBetweenSentinelLookups = TimeSpan . FromMilliseconds ( 250 ) ;
74+ MaxWaitBetweenSentinelLookups = TimeSpan . FromSeconds ( 60 ) ;
5675 }
5776
5877 /// <summary>
5978 /// Initialize Sentinel Subscription and Configure Redis ClientsManager
6079 /// </summary>
6180 public IRedisClientsManager Start ( )
6281 {
63- GetValidSentinel ( ) ;
82+ lock ( oLock )
83+ {
84+ GetValidSentinel ( ) ;
6485
65- if ( this . RedisManager == null )
66- throw new ApplicationException ( "Unable to resolve sentinels!" ) ;
86+ if ( this . RedisManager == null )
87+ throw new ApplicationException ( "Unable to resolve sentinels!" ) ;
6788
68- return this . RedisManager ;
89+ return this . RedisManager ;
90+ }
6991 }
7092
7193 public Func < string , string > HostFilter { get ; set ; }
@@ -102,12 +124,12 @@ public SentinelInfo ResetClients()
102124
103125 private IRedisClientsManager CreateRedisManager ( SentinelInfo sentinelInfo )
104126 {
105- var redisManager = RedisManagerFactory (
106- ConfigureHosts ( sentinelInfo . RedisMasters ) ,
107- ConfigureHosts ( sentinelInfo . RedisSlaves ) ) ;
127+ var masters = ConfigureHosts ( sentinelInfo . RedisMasters ) ;
128+ var slaves = ConfigureHosts ( sentinelInfo . RedisSlaves ) ;
129+ var redisManager = RedisManagerFactory ( masters , slaves ) ;
108130
109- // var hasRedisResolver = (IHasRedisResolver)redisManager;
110- // hasRedisResolver.RedisResolver = new RedisSentinelResolver(redisSentinel );
131+ var hasRedisResolver = ( IHasRedisResolver ) redisManager ;
132+ hasRedisResolver . RedisResolver = new RedisSentinelResolver ( this , masters , slaves ) ;
111133
112134 var canFailover = redisManager as IRedisFailover ;
113135 if ( canFailover != null && this . OnFailover != null )
@@ -129,12 +151,12 @@ private RedisSentinelWorker GetValidSentinel()
129151
130152 RedisException lastEx = null ;
131153
132- while ( this . RedisManager == null && ShouldRetry ( ) )
154+ while ( this . worker == null && ShouldRetry ( ) )
133155 {
134156 try
135157 {
136158 this . worker = GetNextSentinel ( ) ;
137- this . RedisManager = GetRedisManager ( ) ;
159+ GetRedisManager ( ) ;
138160 this . worker . BeginListeningForConfigurationChanges ( ) ;
139161 return this . worker ;
140162 }
@@ -144,25 +166,36 @@ private RedisSentinelWorker GetValidSentinel()
144166 OnWorkerError ( ex ) ;
145167
146168 lastEx = ex ;
147- if ( this . worker != null )
148- this . worker . Dispose ( ) ;
149-
150169 this . failures ++ ;
170+ this . totalFailures ++ ;
151171 }
152172 }
153173
154- throw new RedisException ( "RedisSentinel is not accessible" , lastEx ) ;
174+ this . failures = 0 ; //reset
175+ Thread . Sleep ( WaitBetweenSentinelLookups ) ;
176+
177+ throw new RedisException ( "No Redis Sentinels were available" , lastEx ) ;
155178 }
156179
157- public string GetMasterHost ( )
180+ public RedisEndpoint GetMaster ( )
158181 {
159182 var sentinelWorker = GetValidSentinel ( ) ;
183+ lock ( sentinelWorker )
184+ {
185+ var host = sentinelWorker . GetMasterHost ( masterName ) ;
186+ return host != null
187+ ? ( HostFilter != null ? HostFilter ( host ) : host ) . ToRedisEndpoint ( )
188+ : null ;
189+ }
190+ }
160191
192+ public List < RedisEndpoint > GetSlaves ( )
193+ {
194+ var sentinelWorker = GetValidSentinel ( ) ;
161195 lock ( sentinelWorker )
162196 {
163- var parts = sentinelWorker . GetMasterHost ( masterName ) ;
164- var host = "{0}:{1}" . Fmt ( parts [ 0 ] , parts [ 1 ] ) ;
165- return host ;
197+ var hosts = sentinelWorker . GetSlaveHosts ( masterName ) ;
198+ return ConfigureHosts ( hosts ) . Map ( x => x . ToRedisEndpoint ( ) ) ;
166199 }
167200 }
168201
@@ -173,29 +206,33 @@ public string GetMasterHost()
173206 /// <remarks>This will be true if the failures is less than either RedisSentinel.MaxFailures or the # of sentinels, whatever is greater</remarks>
174207 private bool ShouldRetry ( )
175208 {
176- return this . failures < Math . Max ( MaxFailures , this . sentinels . Count ) ;
209+ return this . failures < Math . Max ( MaxFailures , this . SentinelHosts . Length ) ;
177210 }
178211
179212 private RedisSentinelWorker GetNextSentinel ( )
180213 {
181- sentinelIndex ++ ;
214+ lock ( oLock )
215+ {
216+ sentinelIndex ++ ;
182217
183- if ( sentinelIndex >= sentinels . Count )
184- sentinelIndex = 0 ;
218+ if ( this . worker != null )
219+ {
220+ this . worker . Dispose ( ) ;
221+ this . worker = null ;
222+ }
185223
186- var sentinelWorker = new RedisSentinelWorker ( this , sentinels [ sentinelIndex ] )
187- {
188- OnSentinelError = OnSentinelError
189- } ;
224+ if ( sentinelIndex >= SentinelHosts . Length )
225+ sentinelIndex = 0 ;
226+
227+ var sentinelWorker = new RedisSentinelWorker ( this , SentinelHosts [ sentinelIndex ] )
228+ {
229+ OnSentinelError = OnSentinelError
230+ } ;
190231
191- return sentinelWorker ;
232+ return sentinelWorker ;
233+ }
192234 }
193235
194- /// <summary>
195- /// Raised if there is an error from a sentinel worker
196- /// </summary>
197- /// <param name="sender"></param>
198- /// <param name="e"></param>
199236 private void OnSentinelError ( Exception ex )
200237 {
201238 if ( this . worker != null )
@@ -205,27 +242,26 @@ private void OnSentinelError(Exception ex)
205242 if ( OnWorkerError != null )
206243 OnWorkerError ( ex ) ;
207244
208- // dispose the worker
209- this . worker . Dispose ( ) ;
210-
211- // get a new worker and start looking for more changes
212245 this . worker = GetNextSentinel ( ) ;
213246 this . worker . BeginListeningForConfigurationChanges ( ) ;
214247 }
215248 }
216249
217250 public SentinelInfo GetSentinelInfo ( )
218251 {
219- return GetValidSentinel ( ) . GetSentinelInfo ( ) ;
252+ var sentinelWorker = GetValidSentinel ( ) ;
253+ lock ( sentinelWorker )
254+ {
255+ return sentinelWorker . GetSentinelInfo ( ) ;
256+ }
220257 }
221258
222259 public void Dispose ( )
223260 {
224- if ( worker != null )
225- {
226- worker . Dispose ( ) ;
227- worker = null ;
228- }
261+ if ( worker == null ) return ;
262+
263+ worker . Dispose ( ) ;
264+ worker = null ;
229265 }
230266 }
231267}
@@ -236,8 +272,9 @@ public class SentinelInfo
236272 public string [ ] RedisMasters { get ; set ; }
237273 public string [ ] RedisSlaves { get ; set ; }
238274
239- public SentinelInfo ( string masterName , List < string > redisMasters , List < string > redisSlaves )
275+ public SentinelInfo ( string masterName , IEnumerable < string > redisMasters , IEnumerable < string > redisSlaves )
240276 {
277+ MasterName = masterName ;
241278 RedisMasters = redisMasters != null ? redisMasters . ToArray ( ) : new string [ 0 ] ;
242279 RedisSlaves = redisSlaves != null ? redisSlaves . ToArray ( ) : new string [ 0 ] ;
243280 }
0 commit comments