csharp/2881099/csredis/src/CSRedisCore/RedisSentinelManager.cs

RedisSentinelManager.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

// http://redis.io/topics/sentinel-clients

namespace CSRedis
{
    /// 
    /// 哨兵主机转换委托
    /// 
    /// 哨兵返回的主机信息
    /// 客户端可连接的主机信息
    public delegate Tuple SentinelMasterConverter(Tuple master);

    /// 
    /// Represents a managed connection to a Redis master instance via a set of Redis sentinel nodes
    /// 
    public clast RedisSentinelManager : IDisposable
    {
        const int DefaultPort = 26379;
        readonly LinkedList _sentinels;
        string _masterName;
        int _connectTimeout;
        RedisClient _redisClient;
        bool _readOnly;

        /// 
        /// Occurs when the master connection has sucessfully connected
        /// 
        public event EventHandler Connected;

        /// 
        /// Create a new RedisSentinenlManager
        /// 
        /// Sentinel addresses (host:ip)
        public RedisSentinelManager(bool readOnly, params string[] sentinels)
        {
            _readOnly = readOnly;
            _sentinels = new LinkedList();
            foreach (var host in sentinels)
            {
                string[] parts = host.Split(':');
                string hostname = parts[0].Trim();
                int port = Int32.Parse(parts[1]);
                Add(hostname, port);
            }
        }

        /// 
        /// Add a new sentinel host using default port
        /// 
        /// Sentinel hostname
        public void Add(string host)
        {
            Add(host, DefaultPort);
        }

        /// 
        /// Add a new sentinel host
        /// 
        /// Sentinel hostname
        /// Sentinel port
        public void Add(string host, int port)
        {
            foreach (var sentinel in _sentinels)
            {
                if (sentinel.Item1 == host && sentinel.Item2 == port)
                    return;
            }
            _sentinels.AddLast(Tuple.Create(host, port));
        }

        /// 
        /// Obtain connection to the specified master node
        /// 
        /// Name of Redis master
        /// Connection timeout (milliseconds)
        /// host:port of Master server that responded
        public string Connect(string masterName, int timeout = 200)
        {
            _masterName = masterName;
            _connectTimeout = timeout;

            if (_readOnly == false)
            {
                string masterEndPoint = SetMaster(masterName, timeout);
                if (masterEndPoint == null)
                    throw new IOException("Could not connect to sentinel or master");

                _redisClient.ReconnectAttempts = 0;
                return masterEndPoint;
            }

            string slaveEndPoint = SetSlave(masterName, timeout);
            if (slaveEndPoint == null)
                throw new IOException("Could not connect to sentinel or slave");

            _redisClient.ReconnectAttempts = 0;
            return slaveEndPoint;
        }

        /// 
        /// Execute command against the master, reconnecting if necessary
        /// 
        /// Command return type
        /// Command to execute
        /// Command result
        public T Call(Func redisAction)
        {
            if (_masterName == null)
                throw new InvalidOperationException("Master not set");

            try
            {
                return redisAction(_redisClient);
            }
            catch (IOException)
            {
                Next();
                Connect(_masterName, _connectTimeout);
                return Call(redisAction);
            }
        }

        /// 
        /// Release resources held by the current RedisSentinelManager
        /// 
        public void Dispose()
        {
            if (_redisClient != null)
                _redisClient.Dispose();
        }

        /// 
        /// 哨兵主机转换委托
        /// 
        /// 客户端可识别的主机转换委托
        public SentinelMasterConverter SentinelMasterConverter { get; set; }


        string SetMaster(string name, int timeout)
        {
            for (int i = 0; i < _sentinels.Count; i++)
            {
                if (i > 0)
                    Next();

                using (var sentinel = Current())
                {
                    try
                    {
                        if (!sentinel.Connect(timeout))
                            continue;
                    }
                    catch (Exception)
                    {
                        continue;
                    }

                    var master = sentinel.GetMasterAddrByName(name);
                    if (master == null)
                        continue;

                    if (_redisClient != null)
                        _redisClient.Dispose();

                    if (SentinelMasterConverter != null)
                        master = SentinelMasterConverter(master);

                    _redisClient = new RedisClient(master.Item1, master.Item2);
                    _redisClient.Connected += OnConnectionConnected;

                    try
                    {
                        if (!_redisClient.Connect(timeout))
                            continue;

                        var role = _redisClient.Role();
                        if (role.RoleName != "master")
                            continue;

                        //测试 write
                        var testid = Guid.NewGuid().ToString("N");
                        _redisClient.StartPipe();
                        _redisClient.Set(testid, 1);
                        _redisClient.Del(testid);
                        _redisClient.EndPipe();

                        foreach (var remoteSentinel in sentinel.Sentinels(name))
                            Add(remoteSentinel.Ip, remoteSentinel.Port);

                    }
                    catch (Exception ex)
                    {
                        Trace.WriteLine(ex.Message);
                        Console.WriteLine(ex.Message);
                        continue;
                    }

                    return master.Item1 + ':' + master.Item2;
                }

            }
            return null;
        }

        string SetSlave(string name, int timeout)
        {
            for (int i = 0; i < _sentinels.Count; i++)
            {
                if (i > 0)
                    Next();

                using (var sentinel = Current())
                {
                    try
                    {
                        if (!sentinel.Connect(timeout))
                            continue;
                    }
                    catch (Exception)
                    {
                        continue;
                    }

                    var slaves = sentinel.Slaves(name);
                    if (slaves == null)
                        continue;

                    foreach (var slave in slaves)
                    {
                        if (_redisClient != null)
                            _redisClient.Dispose();
                        _redisClient = new RedisClient(slave.Ip, slave.Port);
                        _redisClient.Connected += OnConnectionConnected;

                        try
                        {
                            if (!_redisClient.Connect(timeout))
                                continue;

                            var role = _redisClient.Role();
                            if (role.RoleName != "slave")
                                continue;

                            foreach (var remoteSentinel in sentinel.Sentinels(name))
                                Add(remoteSentinel.Ip, remoteSentinel.Port);

                        }
                        catch (Exception ex)
                        {
                            Trace.WriteLine(ex.Message);
                            Console.WriteLine(ex.Message);
                            continue;
                        }

                        return slave.Ip + ':' + slave.Port;
                    }
                }

            }
            return null;
        }

        RedisSentinelClient Current()
        {
            return new RedisSentinelClient(_sentinels.First.Value.Item1, _sentinels.First.Value.Item2);
        }

        void Next()
        {
            var first = _sentinels.First;
            _sentinels.RemoveFirst();
            _sentinels.AddLast(first.Value);
        }

        void OnConnectionConnected(object sender, EventArgs args)
        {
            if (Connected != null)
                Connected(this, new EventArgs());
        }
    }
}