diff --git a/.gitignore b/.gitignore index 1ae8164..9861725 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,6 @@ $RECYCLE.BIN/ .DS_Store *.nupkg /ctstone.Redis/nuget-pack-push.cmd + +# Visual studio stuff +.vs/ diff --git a/CSRedis/RedisSentinelManager.cs b/CSRedis/RedisSentinelManager.cs index f124f03..98cbf77 100644 --- a/CSRedis/RedisSentinelManager.cs +++ b/CSRedis/RedisSentinelManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; // http://redis.io/topics/sentinel-clients @@ -13,10 +14,13 @@ namespace CSRedis public class RedisSentinelManager : IDisposable { const int DefaultPort = 26379; + private const int NUMBER_OF_CONNECTIONS = 10; readonly LinkedList> _sentinels; string _masterName; int _connectTimeout; + LinkedList> _pools; RedisClient _redisClient; + Dictionary _hostMapping; /// /// Occurs when the master connection has sucessfully connected @@ -30,6 +34,7 @@ public class RedisSentinelManager : IDisposable public RedisSentinelManager(params string[] sentinels) { _sentinels = new LinkedList>(); + _pools = new LinkedList>(); foreach (var host in sentinels) { string[] parts = host.Split(':'); @@ -37,6 +42,37 @@ public RedisSentinelManager(params string[] sentinels) int port = Int32.Parse(parts[1]); Add(hostname, port); } + + } + + /// + /// Return a client from the connection pool + /// + /// a master redis client + public RedisClient GetClient() + { + RedisClient client; + client = GetClientFromPool(); + if (!IsMaster(client)) + { + Connect(_masterName, _connectTimeout); + client = GetClientFromPool(); + } + + return client; + } + + // return true if client is master + private bool IsMaster(RedisClient client) => client != null && client.Info("role") == "master"; + + private RedisClient GetClientFromPool() + { + var first = _pools.FirstOrDefault(); + if (first != null) + { + return first.Item2.GetClient(); + } + return null; } /// @@ -48,6 +84,22 @@ public void Add(string host) Add(host, DefaultPort); } + /// + /// Add host mapping for the internal IP returned by Sentinel to the external IP + /// + /// Dictionary of sentinel host mapping between internal and external IPs + public void AddHostMapping(Dictionary hostMapping) + { + _hostMapping = hostMapping; + } + + private string MapHost(string host) + { + if (_hostMapping != null && _hostMapping.ContainsKey(host)) + return _hostMapping[host]; + return host; + } + /// /// Add a new sentinel host /// @@ -79,6 +131,14 @@ public string Connect(string masterName, int timeout = 200) throw new IOException("Could not connect to sentinel or master"); _redisClient.ReconnectAttempts = 0; + if(!_pools.Any(c=>c.Item1 == sentinel)) + _pools.AddFirst(Tuple.Create(sentinel, new RedisConnectionPool(_redisClient.Host, _redisClient.Port, NUMBER_OF_CONNECTIONS))); + else + { + var pool = _pools.Where(c => c.Item1 == sentinel).First(); + _pools.Remove(pool); + _pools.AddFirst(pool); + } return sentinel; } @@ -137,7 +197,7 @@ string SetMaster(string name, int timeout) if (master == null) continue; - _redisClient = new RedisClient(master.Item1, master.Item2); + _redisClient = new RedisClient(MapHost(master.Item1), master.Item2); _redisClient.Connected += OnConnectionConnected; if (!_redisClient.Connect(timeout)) continue; diff --git a/CSRedis/Types.cs b/CSRedis/Types.cs index d423696..16df9dc 100644 --- a/CSRedis/Types.cs +++ b/CSRedis/Types.cs @@ -403,12 +403,26 @@ public RedisServerInfo(SerializationInfo info, StreamingContext context) Port = info.GetInt32("port"); RunId = info.GetString("runid"); Flags = info.GetString("flags").Split(','); - PendingCommands = info.GetInt64("pending-commands"); + PendingCommands = Exists(info, "pending-commands")? info.GetInt64("pending-commands"): info.GetInt64("link-pending-commands"); LastOkPingReply = info.GetInt64("last-ok-ping-reply"); LastPingReply = info.GetInt64("last-ping-reply"); DownAfterMilliseconds = info.GetInt64("down-after-milliseconds"); } + /// + /// Check if key exists in SerializationInfo + /// + /// SerializationInfo object + /// The key to check + /// Return true if key exists in SerializationInfo object + protected static bool Exists(SerializationInfo info, string key) + { + foreach(var entry in info) + if (entry.Name == key) + return true; + return false; + } + /// /// Get or set Redis server name /// @@ -618,7 +632,8 @@ public class RedisSentinelInfo : RedisServerInfo public RedisSentinelInfo(SerializationInfo info, StreamingContext context) : base(info, context) { - SDownTime = info.GetInt64("s-down-time"); + if(Exists(info, "s-down-time")) + SDownTime = info.GetInt64("s-down-time"); LastHelloMessage = info.GetInt64("last-hello-message"); VotedLeader = info.GetString("voted-leader"); VotedLeaderEpoch = info.GetInt64("voted-leader-epoch");