从Redis连接丢失中恢复

时间:2014-07-23 23:34:40

标签: c# redis stackexchange.redis

我正在寻找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)之前,这种方法仍然很好,直到我得到“没有连接可用于服务此操作”,并且事情无法恢复。

请让我知道可以从间歇性连接问题中恢复的正确/推荐代码是什么。

2 个答案:

答案 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%确定这是处理此问题的最佳方法。如果有人知道如何改进这一点,请告诉我。