我正在寻找Redis连接丢失后恢复[在多线程环境中]的参考实现。到目前为止,找不到任何有意义的东西。
安装程序:我有一个Azure辅助角色,它在多个线程中运行相同的代码(ThreadProc)。最初,我有每个Redis操作之前的静态ConnectionMultiplexer和.GetDatabase()。这根本没有通过压力测试(一旦负载从低到中增加,就会出现大量的“无法连接”错误)。我把它改成了这个:
static readonly ConnectionMultiplexer _connection = ConnectionMultiplexer.Connect(...);
static readonly IDatabase _cache = _connection.GetDatabase();
void ThreadProc() // running in multiple threads
{
while (true)
{
// using _cache here
}
}
即使在高负载(每个工作者角色实例1000+ op / s)之前,这种方法仍然很好,直到我得到“没有连接可用于服务此操作”,并且事情无法恢复。
请让我知道可以从间歇性连接问题中恢复的正确/推荐代码是什么。
答案 0 :(得分:10)
编辑2015-05-02:虽然StackExchange.Redis客户端的更高版本明确应该处理这种“丢失连接” - >内部和自动地“重新连接”逻辑,我的测试已经超出任何真正的怀疑,他们无法成功地将其拉下来,因此至少在繁忙的环境中仍然需要这种事情。我将下面的代码从我的缓存层中抽出一段时间,最终导致成千上万的连接失败错误。我把它放回去了,那些都消失了。
编辑2015-02-24:此方法不再需要。最新版本的StackExchange.Redis
客户端正确处理断开连接 - 它们会自动重新连接,下面的解决方法只会干扰事情。为了历史目的将它保存在这里,但我的建议是忽略它。
以下是我的SimpleCacheRedis<T>
包装器中的一些方法,显示了我如何处理问题:
public async Task<TValue> GetAsync(string key, Func<Task<TValue>> missingFunc)
{
key = GetKey(key);
var value = default(TValue);
try
{
var db = _connection.GetDatabase();
var str = await db.StringGetAsync(key);
if (!str.IsNullOrEmpty)
{
value = _jsonSerializer.Deserialize<TValue>(str);
}
}
catch (RedisConnectionException ex)
{
HandleRedisConnectionError(ex);
}
catch (Exception ex)
{
_logger.Error("Error retrieving item '" + key +
"' from Redis cache; falling back to missingFunc(). Error = " + ex);
}
if (value == default(TValue))
{
present = false;
value = await missingFunc();
await PerformAddAsync(key, value);
}
return value;
}
private void HandleRedisConnectionError(RedisConnectionException ex)
{
_logger.Error("Connection error with Redis cache; recreating connection for the next try, and falling back to missingFunc() for this one. Error = " + ex.Message);
Task.Run(async () =>
{
try
{
await CreateConnectionAsync();
}
catch (Exception genEx)
{
_logger.Error("Unable to recreate redis connection (sigh); bailing for now: " + genEx.Message);
}
});
}
private async Task CreateConnectionAsync()
{
if (_attemptingToConnect) return;
var sw = new StringWriter();
try
{
_attemptingToConnect = true;
_connection = await ConnectionMultiplexer.ConnectAsync(_redisCs, sw);
}
catch (Exception ex)
{
_logger.Error("Unable to connect to redis async: " + ex);
_logger.Debug("internal log: \r\n" + sw);
throw;
}
finally
{
_attemptingToConnect = false;
}
}
基本上,如果因为RedisConnectionException
而发现我无法连接到Redis,我会分离出一个单独的async
任务来重新创建共享连接。当然,这个电话可能会失败,但无论如何,电话会在那段时间内失败。一旦成功,任何新的调用都将使用新的(重新)创建的连接。就像我上面说的那样,有点无聊。
我的情况可能与您的情况略有不同,因为我不是将Redis用作永久存储,而是将其作为缓存。这意味着丢失的redis连接的唯一影响是我需要从DB而不是从缓存中检索结果。所以我可以稍稍松懈地对待某些事情。
答案 1 :(得分:2)
好吧,我想我会回答我自己的问题,如果没有人愿意,虽然看起来很奇怪,但这是一个基本的用例。
以下是管理连接丢失的课程:
static class RedisConnectionManager
{
private static readonly Dictionary<string, IDatabase> _dictionary = new Dictionary<string, IDatabase>();
internal static IDatabase GetDatabase(string connectionString)
{
lock (_dictionary)
{
if (!_dictionary.ContainsKey(connectionString))
_dictionary.Add(connectionString, ConnectionMultiplexer.Connect(connectionString).GetDatabase());
if (!_dictionary[connectionString].Multiplexer.IsConnected)
{
_dictionary[connectionString].Multiplexer.Dispose();
_dictionary[connectionString] = ConnectionMultiplexer.Connect(connectionString).GetDatabase();
}
return _dictionary[connectionString];
}
}
}
此类处理多个连接字符串,因此如果您只有一个,则代码将更加简单。请注意明确的Multiplexer.Dispose()
电话。由于底层对象拥有物理TCP连接,因此您不能等到GC启动才能释放资源。到那时,根据您的负载,您可能会有数千个孤立的TCP连接。
此代码运行良好,但我仍然不能100%确定这是处理此问题的最佳方法。如果有人知道如何改进这一点,请告诉我。