Adapter
SentinelAdapter.cs
using FreeRedis.Internal;
using FreeRedis.Internal.ObjectPool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FreeRedis
{
partial clast RedisClient
{
clast SentinelAdapter : BaseAdapter
{
readonly IdleBus _ib;
readonly ConnectionStringBuilder _connectionString;
readonly LinkedList _sentinels;
string _masterHost;
readonly bool _rw_splitting;
readonly bool _is_single;
public SentinelAdapter(RedisClient topOwner, ConnectionStringBuilder sentinelConnectionString, string[] sentinels, bool rw_splitting)
{
UseType = UseType.Sentinel;
TopOwner = topOwner;
_connectionString = sentinelConnectionString;
_sentinels = new LinkedList(sentinels?.Select(a =>
{
var csb = ConnectionStringBuilder.Parse(a);
csb.Host = csb.Host.ToLower();
return csb;
}).GroupBy(a => a.Host, a => a).Select(a => a.First()) ?? new ConnectionStringBuilder[0]);
_rw_splitting = rw_splitting;
_is_single = !_rw_splitting && sentinelConnectionString.MaxPoolSize == 1;
if (_sentinels.Any() == false) throw new ArgumentNullException(nameof(sentinels));
_ib = new IdleBus(TimeSpan.FromMinutes(10));
ResetSentinel();
#if isasync
_asyncManager = new AsyncRedisSocket.Manager(this);
#endif
}
bool isdisposed = false;
public override void Dispose()
{
foreach (var key in _ib.GetKeys())
{
var pool = _ib.Get(key);
TopOwner.Unavailable?.Invoke(TopOwner, new UnavailableEventArgs(pool.Key, pool));
}
isdisposed = true;
_ib.Dispose();
}
public override void Refersh(IRedisSocket redisSocket)
{
if (isdisposed) return;
var tmprds = redisSocket as DefaultRedisSocket.TempProxyRedisSocket;
if (tmprds != null) _ib.Get(tmprds._poolkey);
}
public override IRedisSocket GetRedisSocket(CommandPacket cmd)
{
var poolkey = GetIdleBusKey(cmd);
if (string.IsNullOrWhiteSpace(poolkey)) throw new RedisClientException($"【{_connectionString.Host}】Redis Sentinel is switching");
var pool = _ib.Get(poolkey);
var cli = pool.Get();
var rds = cli.Value.Adapter.GetRedisSocket(null);
var rdsproxy = DefaultRedisSocket.CreateTempProxy(rds, () => pool.Return(cli));
rdsproxy._poolkey = poolkey;
rdsproxy._pool = pool;
return rdsproxy;
}
public override TValue AdapterCall(CommandPacket cmd, Func parse)
{
return TopOwner.LogCall(cmd, () =>
{
RedisResult rt = null;
var protocolRetry = false;
using (var rds = GetRedisSocket(cmd))
{
try
{
rds.Write(cmd);
rt = rds.Read(cmd);
}
catch (ProtocolViolationException)
{
var pool = (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool;
rds.ReleaseSocket();
cmd._protocolErrorTryCount++;
if (cmd._protocolErrorTryCount 1) throw;
protocolRetry = true;
}
}
catch (Exception ex)
{
var pool = (rds as DefaultRedisSocket.TempProxyRedisSocket)._pool;
if (pool?.SetUnavailable(ex) == true)
{
}
throw;
}
}
if (protocolRetry) return AdapterCall(cmd, parse);
return parse(rt);
});
}
#if isasync
AsyncRedisSocket.Manager _asyncManager;
public override Task AdapterCallAsync(CommandPacket cmd, Func parse)
{
return TopOwner.LogCallAsync(cmd, async () =>
{
var asyncRds = _asyncManager.GetAsyncRedisSocket(cmd);
try
{
var rt = await asyncRds.WriteAsync(cmd);
return parse(rt);
}
catch (ProtocolViolationException)
{
var pool = (asyncRds._rds as DefaultRedisSocket.TempProxyRedisSocket)?._pool;
cmd._protocolErrorTryCount++;
if (pool != null && cmd._protocolErrorTryCount 1) throw;
return await AdapterCallAsync(cmd, parse);
}
}
});
}
#endif
string GetIdleBusKey(CommandPacket cmd)
{
if (cmd != null && (_rw_splitting || !_is_single))
{
var cmdset = CommandSets.Get(cmd._command);
if (cmdset != null)
{
if (!_is_single && (cmdset.Status & CommandSets.LocalStatus.check_single) == CommandSets.LocalStatus.check_single)
throw new RedisClientException($"Method cannot be used in {UseType} mode. You can set \"max pool size=1\", but it is not singleton mode.");
if (_rw_splitting &&
((cmdset.Tag & CommandSets.ServerTag.read) == CommandSets.ServerTag.read ||
(cmdset.Flag & [email protected]) == [email protected]))
{
var rndkeys = _ib.GetKeys(v => v == null || v.IsAvailable && v._policy._connectionStringBuilder.Host != _masterHost);
if (rndkeys.Any())
{
var rndkey = rndkeys[_rnd.Value.Next(0, rndkeys.Length)];
return rndkey;
}
}
}
}
return _masterHost;
}
int ResetSentinelFlag = 0;
internal void ResetSentinel()
{
if (ResetSentinelFlag != 0) return;
if (Interlocked.Increment(ref ResetSentinelFlag) != 1)
{
Interlocked.Decrement(ref ResetSentinelFlag);
return;
}
string masterhostEnd = _masterHost;
var allkeys = _ib.GetKeys().ToList();
for (int i = 0; i < _sentinels.Count; i++)
{
if (i > 0)
{
var first = _sentinels.First;
_sentinels.RemoveFirst();
_sentinels.AddLast(first.Value);
}
try
{
using (var sentinelcli = new RedisSentinelClient(_sentinels.First.Value))
{
var masterhost = sentinelcli.GetMasterAddrByName(_connectionString.Host);
var masterConnectionString = localTestHost(masterhost, RoleType.Master);
if (masterConnectionString == null) continue;
masterhostEnd = masterhost;
if (_rw_splitting)
{
foreach (var slave in sentinelcli.Salves(_connectionString.Host))
{
ConnectionStringBuilder slaveConnectionString = localTestHost($"{slave.ip}:{slave.port}", RoleType.Slave);
if (slaveConnectionString == null) continue;
}
}
foreach (var sentinel in sentinelcli.Sentinels(_connectionString.Host))
{
var remoteSentinelHost = $"{sentinel.ip}:{sentinel.port}";
if (_sentinels.Contains(remoteSentinelHost)) continue;
_sentinels.AddLast(remoteSentinelHost);
}
}
break;
}
catch { }
}
foreach (var spkey in allkeys) _ib.TryRemove(spkey, true);
Interlocked.Exchange(ref _masterHost, masterhostEnd);
Interlocked.Decrement(ref ResetSentinelFlag);
ConnectionStringBuilder localTestHost(string host, RoleType role)
{
ConnectionStringBuilder connectionString = _connectionString.ToString();
connectionString.Host = host;
connectionString.MinPoolSize = 1;
connectionString.MaxPoolSize = 1;
using (var cli = new RedisClient(connectionString))
{
if (cli.Role().role != role)
return null;
if (role == RoleType.Master)
{
//test set/get
}
}
connectionString.MinPoolSize = _connectionString.MinPoolSize;
connectionString.MaxPoolSize = _connectionString.MaxPoolSize;
_ib.TryRegister(host, () => new RedisClientPool(connectionString, null, TopOwner));
allkeys.Remove(host);
return connectionString;
}
}
bool RecoverySentineling = false;
object RecoverySentinelingLock = new object();
bool RecoverySentinel()
{
var ing = false;
if (RecoverySentineling == false)
lock (RecoverySentinelingLock)
{
if (RecoverySentineling == false)
RecoverySentineling = ing = true;
}
if (ing)
{
new Thread(() =>
{
while (true)
{
Thread.CurrentThread.Join(1000);
try
{
ResetSentinel();
if (_ib.Get(_masterHost).CheckAvailable())
{
if (!TopOwner.OnNotice(null, new NoticeEventArgs(NoticeType.Info, null, $"{_connectionString.Host.PadRight(21)} > Redis Sentinel switch to {_masterHost}", null)))
TestTrace.WriteLine($"【{_connectionString.Host}】Redis Sentinel switch to {_masterHost}", ConsoleColor.DarkGreen);
RecoverySentineling = false;
return;
}
}
catch (Exception ex21)
{
if (!TopOwner.OnNotice(null, new NoticeEventArgs(NoticeType.Info, null, $"{_connectionString.Host.PadRight(21)} > Redis Sentinel switch to {_masterHost}", null)))
TestTrace.WriteLine($"【{_connectionString.Host}】Redis Sentinel: {ex21.Message}", ConsoleColor.DarkYellow);
}
}
}).Start();
}
return ing;
}
}
}
}